【Elasticsearch】基于SpringBoot构建ES搜索服务

引言

通过之前的资料调研,在上篇文章中我们已经整合好Springboot+Elasticsearch6.5的demo。为了满足系统需求,我们会将所有数据入口接入Elasticsearch,然后通过ES去查询数据。所以,我们决定单独出一个搜索服务,接入ES,封装一些操作索引的基本方法,其他服务需要使用,直接引用该服务即可。

环境

SpringBoot:2.1.9.RELEASE

Elasticsearch:6.8.6

工程结构

【Elasticsearch】基于SpringBoot构建ES搜索服务_第1张图片

核心代码

有了上面的工程结构之后,就可以开始写代码了。下面展示一些核心代码:

1. 引入Maven依赖

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.8.6</version>
</dependency>

<!-- Java Low Level REST Client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>6.8.6</version>
</dependency>

<!-- Java High Level REST Client -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
    <version>6.8.6</version>
</dependency>	 

这里引入的依赖和Demo中的依赖有所不同,原因是版本不同,只引入两个client依赖的话,在调用es方法会报错,NoSuchMethod相关错误,所以,这里添加了org.elasticsearch依赖包。

2. 配置文件配置ES地址等相关信息

server:
  port: 6001
spring:
  application:
    name: middle-search
  profiles:
    active: dev
elasticsearch:
  nodes: ***

3. Elasticsearch客户端配置类

在配置类中,我们通过实现InitializingBean、DisposableBean去初始化和销毁客户端,在初始化过程中,我们通过配置文件读取ES的地址,并且设置请求头、连接超时时间等相关配置,核心代码如下:

/**
 * Es客户端配置
 *
 * @author : huzhiting
 * @date : 2020-09-07 11:22
 */
@Component
@Slf4j
public class EsClientConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
     

    private static final int CONNECT_TIMEOUT_MILLIS = 60 * 1000;
    private static final int SOCKET_TIMEOUT_MILLIS = 5 * 60 * 1000;
    private static final int CONNECTION_REQUEST_TIMEOUT_MILLIS = CONNECT_TIMEOUT_MILLIS;

    @Value("${elasticsearch.nodes}")
    private String nodes;

    /**
     * 实例化客户端
     */
    private RestHighLevelClient restHighLevelClient;

    private RestHighLevelClient buildClient() {
     
        try {
     
//            这里可以读取多个集群地址,用逗号分割即可,创建HttpHost对象数组
//            String[] cluster = nodes.split(",");
//            HttpHost[] hosts = new HttpHost[cluster.length];
//            for (int i = 0; i < cluster.length; i++) {
     
//                hosts[i] = HttpHost.create(cluster[i]);
//            }
            //我这里直接使用的域名,直接创建单个HttpHost对象了
            HttpHost hosts = HttpHost.create(nodes);
            //设置请求头
            BasicHeader[] basicHeaders = new BasicHeader[]{
     
                    new BasicHeader("Accept", "application/json; charset=UTF-8")
            };
            RestClientBuilder restClientBuilder = RestClient.builder(hosts);
            //设置连接超时等相关参数
            restClientBuilder.setDefaultHeaders(basicHeaders).setRequestConfigCallback((RequestConfig.Builder requestConfigBuilder) -> {
     
                requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
                requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS);
                requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS);
                return requestConfigBuilder;
            });
            restHighLevelClient = new RestHighLevelClient(restClientBuilder);
        } catch (Exception e) {
     
            log.error(e.getMessage());
        }
        return restHighLevelClient;
    }

    @Override
    public void afterPropertiesSet() {
     
        restHighLevelClient = buildClient();
    }

    @Override
    public void destroy() {
     
        try {
     
            if (restHighLevelClient != null) {
     
                restHighLevelClient.close();
            }
        } catch (final Exception e) {
     
            log.error("[EsClientConfig.destroy] [error] [Error closing ElasticSearch client:] ", e);
        }
    }

    @Override
    public RestHighLevelClient getObject() {
     
        return restHighLevelClient;
    }

    @Override
    public Class<?> getObjectType() {
     
        return RestHighLevelClient.class;
    }
}

在上述建立连接过程中,因为地址多了个"/",报了UnknownHostException,如果有遇到同样的问题,检查一下地址是否正确。

4. 分页查询索引方法

之前的示例工程中,贴出了新建索引和查询索引的相关代码,这里贴出分页查询索引的代码,以验证连接ES是否成功。

/**
 * 条件查询数据(分页)
 *
 * @param request
 * @return
 */
public PageResult search(SearchRequest request, Integer page, Integer size) {
     
    PageResult pageResult = new PageResult();
    pageResult.setCurrent(page);
    pageResult.setSize(size);
    SearchResponse response;
    try {
     
        response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //从查询结果中获得想要数据
        SearchHits searchHits = response.getHits();
        //获得查询结果条数
        long total = searchHits.getTotalHits();
        pageResult.setTotal(Integer.parseInt(String.valueOf(total)));
        //获得查询结果并转换为map
        if (total > 0) {
     
            SearchHit[] searchHitsArr = searchHits.getHits();
            List<Map<String, Object>> resultMapList = convertResultToObject(searchHitsArr);
            pageResult.setPages(pageResult.getTotal() / pageResult.getSize() + 1);
            pageResult.setRecords(resultMapList);
        }
    } catch (IOException e) {
     
        log.error("[ESUtil.queryAll] [error] [fail to query, param is {}]", JSON.toJSON(request), e);
        return pageResult;
    }
    return pageResult;
}

上面的方法返回的PageResult对象是自己对ES返回的结果做了一次处理:

/**
 * 分页查询结果
 *
 * @author : huzhiting
 * @date : 2020-09-07 10:37
 */
@Data
public class PageResult {
     
    /**
     * 每页显示数量
     */
    private Integer size;
    /**
     * 当前页
     */
    private Integer current;
    /**
     * 总页数
     */
    private Integer pages;
    /**
     * 总条数
     */
    private Integer total;
    /**
     * 记录
     */
    private List<?> records;

    public PageResult() {
     
        this.records = Collections.emptyList();
        this.total = 0;
        this.size = 10;
        this.current = 1;
    }
}

另外需要注意的是,Es的分页是从0页开始计算的,所以我们需要对分页参数做一次处理再使用from和size方法传递,核心代码如下:

/**
 * 条件查询用户索引数据(分页)
 *
 * @param dto
 * @return
 */
 public PageResult list(UserIndexQueryDTO dto) {
     
    //构建SearchRequest对象
    SearchRequest searchRequest = new SearchRequest();
    //读取配置文件中的索引名称
    searchRequest.indices(INDEX_NAME);
    //读取配置文件中的索引类型(ES6一个索引下仅有一个类型)
    searchRequest.types(INDEX_TYPE);
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //构造条件查询对象
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    if (StringUtils.isNotBlank(dto.getGradeName())) {
     
        //termQuery 精确匹配
        boolQueryBuilder.filter(QueryBuilders.termQuery("gradeName", dto.getGradeName()));
    }
    if (StringUtils.isNotBlank(dto.getNickName())) {
     
        //wildcardQuery 模糊匹配
        boolQueryBuilder.filter(QueryBuilders.wildcardQuery("nickName", String.format("*%s*", dto.getNickName())));
    }
    if (StringUtils.isNotBlank(dto.getPhone())) {
     
        //wildcardQuery 模糊匹配
        boolQueryBuilder.filter(QueryBuilders.wildcardQuery("phone", String.format("*%s*", dto.getPhone())));
    }
    PageResult pageResult = baseIndexService.transformPage(dto.getPage(), dto.getSize());
    builder.query(boolQueryBuilder).from(pageResult.getCurrent()).size(pageResult.getSize());
    searchRequest.source(builder);
    return baseIndexService.queryIndexByCondition(searchRequest, dto.getPage(), dto.getSize());
}
/**
 * 分页参数转换
 * @param page
 * @param size
 * @return
 */
public PageResult transformPage(Integer page, Integer size) {
     
    PageResult pageResult = new PageResult();
    if (page == null || page < 0) {
     
        pageResult.setCurrent(0);
    } else {
     
        pageResult.setCurrent(page - 1);
    }
    if (size == null || size < 0) {
     
        pageResult.setSize(10);
    } else {
     
        pageResult.setSize(size);
    }
    return pageResult;
}

5. 分页查询索引结果

【Elasticsearch】基于SpringBoot构建ES搜索服务_第2张图片
除了上面的核心代码,项目中抽离的Service和ServiceImpl代码就不展示了,主要是对其他服务暴露接口的作用。

总结

这几天一直在学习Elasticsearch相关的东西,因为之前自己也没有真正接触过,所以接到搭建ES搜索服务的任务时压力还是很大的,什么都是从只是听过而没有用过开始。

好在从demo搭建到基础服务搭建,整个过程比较顺利,遇到的各种问题也都解决了,其他服务也顺利接入了es服务,接下来就是写相关业务代码了。

你可能感兴趣的:(【架构设计】,#,Spring,Boot,#,Elasticsearch,java,elasticsearch,spring,boot)