【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)

  仅涉及后端,全部目录看顶部专栏,代码、文档、接口路径在:

【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客


全篇只介绍重点架构逻辑,具体编写看源代码就行,读起来也不复杂~

谨慎:源代码中有一些注释是错误的,有的注释意思完全相反,有的注释对不上号,我在阅读过程中就顺手更新了,并且在我不会的地方添加了新的注释,所以在读源代码过程中一定要谨慎啊!

目录

A1.ES检索

B1.ES基本搭建

B2.更新系统日志的ES存储搭建(关联No2-3)

C1.ElasticsearchRepository操作ES方式

C2.ElasticsearchOperations操作ES方式

C3.直接使用RestHighLevelClient操作ES方式(待更新)

B3.es分词查询(待更新)

剩余内容:消息中间件AMQP、定时任务等


A1.ES检索

ES学习可以看这篇文章,特别详细:ElasticSearch从入门到精通,史上最全(持续更新,未完待续,每天一点点)

我用的苹果本搭建的es和Kibana,安转时用brew安装一直报错(提示版本问题,我的本版本低了安装不了最新的),之后就直接去官网下载对应的7.3的版本的,然后直接在 bin 里面运行的,也挺方便的。

系统使用的是spring整合的es ,spring-data-elasticsearch,用的版本是Elasticsearch7.x这个版本较7.x之前使用上有些改动,虽然我没有用过之前的,但是为了防止使用混淆,还是了解了一下。

其中有一点要清楚的是spring-data-elasticsearch封装操作ES有两种方式,最终都是使用es本身的RestHighLevelClient进行的操作~

能使用RestHighLevelClient尽量使用它。

主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。

【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)_第1张图片

 具体的看这篇文章,挺清晰的:springboot2.3配置与使用elasticsearch7.x

B1.ES基本搭建

(1.ElasticsearchRepository;2.ElasticsearchOperations;3.RestHighLevelClient)

springboot本身就自带了spring-boot-starter-data-elasticsearch依赖包,我们就不用依赖了。

该系统使用的也是7.x以上版本的es,这个的配置项就不能直接使用spring的了,已经过时了,所以我们就只需要配置RestHighLevelClient的bean和ElasticsearchOperations的bean就好了,我们需要给出自定义的Properties,当然如果直接写在配置类里也行,就是不建议~

1.在frameword模块中创建Elasticsearch所需要的参数类,并在业务模块中的 yml 里配置参数项;
2.添加 elasticsearch 配置,创建RestHighLevelClient bean;

# 1.在frameword模块中创建Elasticsearch所需要的参数类,并在业务模块中的 yml 里配置参数项;

//参数类,详见:cn.lili.elasticsearch.config.ElasticsearchProperties

#举例 management-api 的yml配置 /lilishop-master/manager-api/src/main/resources/application.yml

lili:
  data:
    # 自定义 elasticsearch 配置
    elasticsearch:
      # cluster集群
      cluster-name: elasticsearch
      # 当前连接节点所属集群的配置信息
      cluster-nodes: 127.0.0.1:9200
      index:
        # 默认分片的副本数
        number-of-replicas: 0
        # 默认主分片数
        number-of-shards: 3
       # 索引前缀
      index-prefix: lili
      # 协议
      scheme: http
#          account:
#            username: elastic
#            password: LiLiShopES
//2.添加 elasticsearch 配置,创建RestHighLevelClient bean;

//详见:cn.lili.elasticsearch.config.ElasticsearchConfig

@Slf4j
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

    @Autowired
    private ElasticsearchProperties elasticsearchProperties;

    private RestHighLevelClient client;

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {

        RestClientBuilder restBuilder = RestClient
                .builder(this.getHttpHosts());

        //修改 HTTP 客户端通信,例如添加压缩或加密层。
        restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
                httpClientBuilder
                        //自定义连接存活策略
                        .setKeepAliveStrategy(getConnectionKeepAliveStrategy())
                        .setMaxConnPerRoute(10).
                        setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build()));
        String username = elasticsearchProperties.getAccount().getUsername();
        String password = elasticsearchProperties.getAccount().getPassword();

        //如果有认证用户,就添加用户信息
        if (username != null && password != null) {
            final CredentialsProvider credential = new BasicCredentialsProvider();
            credential.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

            //修改 HTTP 客户端通信,例如添加压缩或加密层。
            restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
                    httpClientBuilder
                            .setDefaultCredentialsProvider(credential)
                            //自定义连接存活策略
                            .setKeepAliveStrategy(getConnectionKeepAliveStrategy())
                            .setMaxConnPerRoute(10)
                            .setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(Runtime.getRuntime().availableProcessors()).build()));
        }

        //配置请求身份验证、超时和其他可以在请求级别设置的属性。
        restBuilder.setRequestConfigCallback(requestConfigBuilder ->
                requestConfigBuilder.setConnectTimeout(1000) //time until a connection with the server is established.
                        .setSocketTimeout(12 * 1000) //time of inactivity to wait for packets[data] to receive.
                        .setConnectionRequestTimeout(-1)); //time to fetch a connection from the connection pool 0 for infinite.

        // 创建ES客户端对象
        client = new RestHighLevelClient(restBuilder);
        return client;
    }

    /**
     * @Description: 配置集群服务器域名/ip
     * @param: []
     * @return: org.apache.http.HttpHost[]
     **/
    private HttpHost[] getHttpHosts() {
        List clusterNodes = elasticsearchProperties.getClusterNodes();
        HttpHost[] httpHosts = new HttpHost[clusterNodes.size()];
        for (int i = 0; i < clusterNodes.size(); i++) {
            String[] node = clusterNodes.get(i).split(":");
            httpHosts[i] = new HttpHost(node[0], Convert.toInt(node[1]), elasticsearchProperties.getScheme());
        }
        return httpHosts;
    }

    private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
        return (response, context) -> 2 * 60 * 1000;
    }

    /**
     * it gets called when bean instance is getting removed from the context if
     * scope is not a prototype
     * If there is a method named shutdown or close then spring container will try
     * to automatically configure them as callback methods when bean is being
     * destroyed
     */
    @PreDestroy
    public void clientClose() {
        try {
            this.client.close();
        } catch (IOException e) {
            log.error("es clientClose错误", e);
        }
    }

}

我们在 config 里面创建了 RestHighLevelClient bean ,同时 config 又实现了 AbstractElasticsearchConfiguration ,在这个里面又创建了ElasticsearchOperations。

之后使用 ElasticsearchOperations 正常注入使用就好啦,见下面B2

ElasticsearchRepository也可以正常实现使用,见下面B2

B2.更新系统日志的ES存储搭建(关联No2-3)

之前No2-3里面系统日志框架中我们已经写了 service 类了,现在需要将具体的日志存储给加上。

C1.ElasticsearchRepository操作ES方式

集成ElasticsearchRepository方式就相当于 mps 的BaseMapper,也需要对应的实体类,实体类里面需要使用@Document标注索引。然后直接创建service类继承ElasticsearchRepository,然后直接使用就可以。

这种方式对于增删改比较方便~

1.系统日志实体类;要加上@Document标注索引,否则使用时会报错

2.系统日志Repository类,实现ElasticsearchRepository类并泛型指定为系统日志实体类;

3.系统日志业务类;

//1.系统日志实体类;要加上@Document标注索引,否则使用时会报错

//详见:cn.lili.modules.permission.entity.vo.SystemLogVO

@Data
//加上了@Document注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词
//indexName 索引库的名称,个人建议以项目的名称命名;
@Document(indexName = "#{@elasticsearchProperties.indexPrefix}_" + EsSuffix.LOGS_INDEX_NAME)
@ToString
@NoArgsConstructor
public class SystemLogVO implements Serializable {

。。。
}
//2.系统日志Repository类,实现ElasticsearchRepository类;
//详见:cn.lili.modules.permission.entity.vo.SystemLogVO
/**
 * 日志
 *
 * @author paulG
 * @since 2021/12/13
 **/
public interface SystemLogRepository extends ElasticsearchRepository {

}
//2.系统日志业务类;

//详见:cn.lili.modules.permission.service.SystemLogService
//详见:cn.lili.modules.permission.serviceimpl.SystemLogServiceImpl

//举例,
@Service
public class SystemLogServiceImpl implements SystemLogService {

    @Autowired
    private SystemLogRepository systemLogRepository;

    /**
     * ES
     */
    @Autowired
    private ElasticsearchOperations restTemplate;

    @Override
    public void saveLog(SystemLogVO systemLogVO) {
        systemLogRepository.save(systemLogVO);
    }
。。。
}

此时就可以直接调用管理员登录接口 /manager/passport/user/login 了,调用过程中会经过系统日志切面类,进而在保存日志的线程类中执行系统日志save的业务。

登录成功后,就直接去 Kibana里面搜索查看,就能查看到当前日志啦

【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)_第2张图片

C2.ElasticsearchOperations操作ES方式

ElasticsearchOperations方式更简单,由于我们已经通过config 创建了该 bean,所以直接在业务类中注入该bean就行,然后直接使用~

这种方式对于查询较方便

还是在SystemLogServiceImpl系统日志业务类中使用,查询日志列表;

//详见:cn.lili.modules.permission.serviceimpl.SystemLogServiceImpl

@Service
public class SystemLogServiceImpl implements SystemLogService {

    @Autowired
    private SystemLogRepository systemLogRepository;

    /**
     * ES
     */
    @Autowired
    private ElasticsearchOperations restTemplate;


    @Override
    public IPage queryLog(String storeId, String operatorName, String key, SearchVO searchVo, PageVO pageVO) {
        pageVO.setNotConvert(true);
        IPage iPage = new Page<>();

        //查询条件构造器,可以往里面添加一些过滤或者其他条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        if (pageVO.getPageNumber() != null && pageVO.getPageSize() != null) {
            int pageNumber = pageVO.getPageNumber() - 1;
            if (pageNumber < 0) {
                pageNumber = 0;
            }
            Pageable pageable = PageRequest.of(pageNumber, pageVO.getPageSize());
            //添加分页条件
            nativeSearchQueryBuilder.withPageable(pageable);
            iPage.setCurrent(pageVO.getPageNumber());
            iPage.setSize(pageVO.getPageSize());
        }

        if (CharSequenceUtil.isNotEmpty(storeId)) {
            //添加查询条件
            nativeSearchQueryBuilder.withFilter(QueryBuilders.matchQuery("storeId", storeId));
        }

        if (CharSequenceUtil.isNotEmpty(operatorName)) {
            //添加查询条件
            nativeSearchQueryBuilder.withFilter(QueryBuilders.wildcardQuery("username", "*" + operatorName + "*"));
        }

        if (CharSequenceUtil.isNotEmpty(key)) {
            BoolQueryBuilder filterBuilder = new BoolQueryBuilder();
            filterBuilder.should(QueryBuilders.wildcardQuery("requestUrl", "*" + key + "*"))
                    .should(QueryBuilders.wildcardQuery("requestParam", "*" + key + "*"))
                    .should(QueryBuilders.wildcardQuery("responseBody", "*" + key + "*"))
                    .should(QueryBuilders.wildcardQuery("name", "*" + key + "*"));
            //添加查询条件
            nativeSearchQueryBuilder.withFilter(filterBuilder);
        }
        //时间有效性判定
        if (searchVo.getConvertStartDate() != null && searchVo.getConvertEndDate() != null) {
            BoolQueryBuilder filterBuilder = new BoolQueryBuilder();
            //大于方法
            filterBuilder.must(
                    QueryBuilders.rangeQuery("createTime")
                            .gte(DateUtil.format(searchVo.getConvertStartDate(), "dd/MM/yyyy"))
                            .lte(DateUtil.format(searchVo.getConvertEndDate(), "dd/MM/yyyy")).format("dd/MM/yyyy||yyyy"));
            //添加查询条件
            nativeSearchQueryBuilder.withFilter(filterBuilder);
        }

        if (CharSequenceUtil.isNotEmpty(pageVO.getOrder()) && CharSequenceUtil.isNotEmpty(pageVO.getSort())) {
            //添加排序条件
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(pageVO.getSort()).order(SortOrder.valueOf(pageVO.getOrder().toUpperCase())));
        } else {
            //添加排序条件
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        }

        //执行查询
        SearchHits searchResult = restTemplate.search(nativeSearchQueryBuilder.build(), SystemLogVO.class);

        iPage.setTotal(searchResult.getTotalHits());

        iPage.setRecords(searchResult.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()));
        return iPage;
    }

}

注意,这里在正常业务中需要添加两个 VO ,接收前端请求传过来的日期搜索参数SearchVO和分页查询参数PageVO

【如果现在添加了,会有一系列工具类需要处理,需要添加自定义DateUtil、StringUtils】

//日期搜索参数SearchVO,详见:cn.lili.common.vo.SearchVO
//分页查询参数PageVO,详见:cn.lili.common.vo.PageVO

 添加好controller、service后,调用接口执行~记得添加token

【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)_第3张图片

C3.直接使用RestHighLevelClient操作ES方式(待更新)

这种更适用于查询。并且是比较推荐的。会涉及到高亮等等

我们在后面商品查询时会用到,这里先略过

B3.es分词查询(待更新)

剩余内容:定时任务等

你可能感兴趣的:(lilishop商城学习,elasticsearch,spring,boot,后端)