Springboot2.2.9接入阿里云ES(带高亮查询)

最近比较忙,好久没更新博客了,今天抽个空记录一下使用springboot接入阿里云ES并带模糊高亮查询功能,闲话不多说,上干活。

引入POM依赖:

        <!-- ES config 接入阿里云ES -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.0</version><!--$NO-MVN-MAN-VER$-->
            <exclusions>
                <exclusion>
                    <groupId>org.elasticsearch</groupId>
                    <artifactId>elasticsearch</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.elasticsearch.client</groupId>
                    <artifactId>elasticsearch-rest-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.0</version><!--$NO-MVN-MAN-VER$-->
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.4.0</version><!--$NO-MVN-MAN-VER$-->
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.7</version><!--$NO-MVN-MAN-VER$-->
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.7</version><!--$NO-MVN-MAN-VER$-->
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.1</version><!--$NO-MVN-MAN-VER$-->
        </dependency>

properties文件内容添加如下:

## ES config
spring.elasticsearch.username=
spring.elasticsearch.password=
spring.elasticsearch.cluster_host=IP地址 或 阿里云内网域名 或 公网域名
spring.elasticsearch.cluster_port=9200
spring.es.env.flag=sit

创建实体类:

package com.xx.xx.es.entity;

import org.springframework.data.annotation.Id;
import lombok.Data;

/**
 * @author Jimmy Shan
 * @date 2020-11-26
 * @desc 示例DEMO 数据存储ES
 */
@Data
public class CusDemoInfoDocument {
    @Id
    private String id;// 主键ID
    private String demoName;// 名称
    private String demoCode;// 编码
    private String demoValue;//

    public CusDemoInfoDocument() {
    }

    public CusDemoInfoDocument(String id, String demoName, String demoCode, String demoValue) {
        this.id = id;
        this.demoName = demoName;
        this.demoCode = demoCode;
        this.demoValue = demoValue;
    }

    public CusDemoInfoDocument(String id) {
        this.id = id;
    }
}

创建工具类:

package com.xx.xx.common.util;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.HttpAsyncResponseConsumerFactory;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.xx.xx.common.Constant;
import com.xx.xx.es.entity.CusDemoInfoDocument;

/**
 * @author Jimmy Shan
 * @date 2020-11-30
 * @desc aliyun ES tools
 */
@Component
public class ElasticsearchUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchUtil.class);
    private static final RequestOptions COMMON_OPTIONS;
    @Value("${spring.elasticsearch.cluster_host}")
    private String clusterHost;
    @Value("${spring.elasticsearch.cluster_port}")
    private Integer clusterPort;
    @Value("${spring.elasticsearch.username}")
    private String username;
    @Value("${spring.elasticsearch.password}")
    private String password;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        // 默认缓存限制为100MB,此处修改为30MB。
        builder.setHttpAsyncResponseConsumerFactory(
                new HttpAsyncResponseConsumerFactory
                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

    /**
     * @desc 获取ES client
     */
    public RestHighLevelClient getEsClient() {
        Map<String, RestHighLevelClient> esClientMap = Constant.esClientMap;
        if (esClientMap == null || esClientMap.isEmpty()) {
            // 阿里云Elasticsearch集群需要basic auth验证。
            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            //访问用户名和密码,创建阿里云Elasticsearch实例时设置的用户名和密码,也是Kibana控制台的登录用户名和密码。
            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.username, this.password));
            // 通过builder创建rest client,配置http client的HttpClientConfigCallback。
            // 单击所创建的Elasticsearch实例ID,在基本信息页面获取公网地址,即为ES集群地址。
            RestClientBuilder builder = RestClient.builder(new HttpHost(this.clusterHost, this.clusterPort))
                    .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                        @Override
                        public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                        }
                    });
            // RestHighLevelClient实例通过REST low-level client builder进行构造。
            RestHighLevelClient highClient = new RestHighLevelClient(builder);
            esClientMap.put(Constant.ES_CLIENT, highClient);
        }
        return esClientMap.get(Constant.ES_CLIENT);
    }

    /**
     * @desc 创建索引
     */
    public void createIndex(String index) throws IOException {
        if(!existsIndex(index)) {
            CreateIndexRequest request = new CreateIndexRequest(index);
            CreateIndexResponse createIndexResponse = getEsClient().indices().create(request, COMMON_OPTIONS);
            LOGGER.info("createIndex: {}", JsonWare.beanToJson(createIndexResponse));
        }       
    }

    /**
     * @desc 判断索引是否存在
     */
    public boolean existsIndex(String index) throws IOException {
        GetIndexRequest request = new GetIndexRequest(index);
        boolean exists = getEsClient().indices().exists(request, COMMON_OPTIONS);
        LOGGER.info("existsIndex: {}", exists);
        return exists;
    }

    /**
     * @desc 判断记录是否存在
     */
    public boolean exists(String index, String type, CusDemoInfoDocument document) throws IOException {
        GetRequest getRequest = new GetRequest(index, type, document.getId());
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        getRequest.storedFields("_none_");
        boolean exists = getEsClient().exists(getRequest, COMMON_OPTIONS);
        LOGGER.info("exists: {}", exists);
        return exists;
    }

    /**
     * @desc 删除索引
     */
    public boolean deleteIndex(String index) {
        try {
            DeleteIndexRequest request = new DeleteIndexRequest(index);
            request.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
            AcknowledgedResponse deleteIndexResponse = getEsClient().indices().delete(request, RequestOptions.DEFAULT);
            return deleteIndexResponse.isAcknowledged();
        } catch (Exception e) {
            LOGGER.error("删除索引失败, index:{}", index);
            return false;
        }
    }

    /**
     * @desc 添加数据
     */
    public String addData(CusDemoInfoDocument document, String index, String type, String docId) {
        String id = null;
        try {
            //index_name为索引名称;type_name为类型名称,7.0及以上版本必须为_doc;doc_id为文档的id。
            // 同步执行,并使用自定义RequestOptions(COMMON_OPTIONS)。
            RestHighLevelClient highClient = getEsClient();
            IndexRequest request = new IndexRequest(index, type)
                    .id(docId).source(JsonWare.beanToJson(document), XContentType.JSON);
            request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);// 立即刷新
            IndexResponse response = highClient.index(request, COMMON_OPTIONS);
            id = response.getId();
            //highClient.close();
            LOGGER.info("索引:{}, 数据添加, 返回码:{}, id:{}", index, response.status().getStatus(), id);
        } catch (IOException e) {
            LOGGER.error("添加数据失败, index:{}, id:{}", index, id);
        }

        return id;
    }

    /**
     * @desc 修改数据
     */
    public String updateData(CusDemoInfoDocument document, String index, String type, String docId) {
        String id = null;
        try {
            RestHighLevelClient highClient = getEsClient();
            UpdateRequest request = new UpdateRequest(index, type, docId)
                    .doc(JsonWare.beanToJson(document), XContentType.JSON);
            request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
            UpdateResponse response = highClient.update(request, COMMON_OPTIONS);
            id = response.getId();
            //highClient.close();
            LOGGER.info("数据更新, 返回码:{}, id:{}", response.status().getStatus(), id);
        } catch (IOException e) {
            LOGGER.error("数据更新失败, index:{}, id:{}", index, id);
        }
        return id;
    }

    /**
     * @desc 批量插入数据
     */
    public boolean insertBatch(String index, String type, List<CusDemoInfoDocument> list) {
        BulkRequest request = new BulkRequest();
        request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        list.forEach(item -> request.add(new IndexRequest(index, type)
                .id(item.getId()).source(JsonWare.beanToJson(item), XContentType.JSON)));
        try {
            RestHighLevelClient highClient = getEsClient();
            BulkResponse bulk = highClient.bulk(request, COMMON_OPTIONS);
            bulk.status().getStatus();
            //highClient.close();
            LOGGER.info("索引:{}, 批量插入 {} 条数据成功!", index, list.size());
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
            LOGGER.error("索引:{}, 批量插入数据失败", index);
            return false;
        }

        return true;
    }

    /**
     * @desc 根据id删除数据
     */
    public boolean deleteById(String index, String type, String docId) {
        DeleteRequest request = new DeleteRequest(index, type, docId);
        request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        try {
            DeleteResponse response = getEsClient().delete(request, COMMON_OPTIONS);
            response.status().getStatus();
            LOGGER.info("索引:{}, 根据id {} 删除数据:{}", index, docId, JsonWare.beanToJson(response));
        } catch (Exception e) {
            LOGGER.error("根据id删除数据失败, index:{}, id:{}", index, docId);
            return false;
        }

        return true;
    }
}

看下Constant类内容:

/**
 * @author Jimmy Shan
 * @date 2020-08-07
 * @desc 常量类
 */
public class Constant {
    public static final Map<String, RestHighLevelClient> esClientMap = new HashMap<>();
    public static final String ES_CLIENT = "esClient";
    // ES查询限制数量
    public static final Integer ES_QUERY_PAGE_START = 0;
    public static final Integer ES_QUERY_COUNT = 20;

}

 

以上只是一些基本操作方式,下面 重点记录下 模糊高亮查询方法

本人这里自定义一个service服务,用来处理高亮查询

Service

package com.xx.xx.service;

import java.util.List;
import com.xx.xx.es.entity.CusDemoInfoDocument;/**
 * @author Jimmy Shan
 * @date 2020-09-28
 * @desc ES服务
 */
public interface EsService {
    /**
     * @desc 高连显示查询-DEMO
     */
    List<CusDemoInfoDocument> searchHighLightDocForDemo(String contents, String index, String type);
}

Service实现

    /**
     * @desc 高连显示查询-DEMO
     */
    @Override
    public List<CusDemoInfoDocument> searchHighLightDocForDemo(String contents,
            String index, String type) {
        try {
            // 获取链接RestHighLevelClient对象
            RestHighLevelClient highClient = esUtil.getEsClient();
            // 查询维度设置
            BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
            // 查询列,此处支持多列查询
            String[] field = {"id", "demoName", "demoCode", "demoValue"};
            // 多列匹配规则
            queryBuilder.must(QueryBuilders.multiMatchQuery(contents, field));

            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(queryBuilder);
            sourceBuilder.from(Constant.ES_QUERY_PAGE_START);// 分页起始位,下标从0开始
            sourceBuilder.size(Constant.ES_QUERY_COUNT);// 每次20条记录

            //设置高亮显示
            HighlightBuilder highlightBuilder = new HighlightBuilder()
                    .field("*").requireFieldMatch(false);
            highlightBuilder.preTags("<font color='#dd4b39'>");
            highlightBuilder.postTags("</font>");
            sourceBuilder.highlighter(highlightBuilder);

            SearchRequest searchRequest = new SearchRequest(index);
            searchRequest.types(type);
            searchRequest.source(sourceBuilder);
            SearchResponse response = highClient.search(searchRequest, RequestOptions.DEFAULT);
            LOGGER.info("highClient response : {}", JsonWare.beanToJson(response));

            // 遍历结果
            List<CusDemoInfoDocument> list = new ArrayList<>();
            for(SearchHit hit : response.getHits()) {
                if (response.getHits().getHits().length <= 0) {
                    return null;
                }

                CusDemoInfoDocument cusDoc = JsonWare.jsonToBean(hit.getSourceAsString(),
                        CusDemoInfoDocument.class);
                // 处理高亮片段
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                HighlightField nameField = highlightFields.get("demoValue");
                if(nameField != null){
                    Text[] fragments = nameField.fragments();
                    String fragmentString = fragments[0].string();
                    cusDoc.setDemoValue(fragmentString);
                }
                list.add(cusDoc);
            }

            if (!CollectionUtils.isEmpty(list)) {
                return list;
            }
            return null;
        } catch (Exception e) {
            LOGGER.error("searchHighLightDocForDemo happen exception: {}", e.getMessage(), e);
            return null;
        }
    }

好了,主要方法都已完成,现在让我们写个测试类试一试。

ControllerTest

@Controller
@RequestMapping("/api")
public class TestController extends AbstractController{

    @Autowired
    private EsService esService;
    @Autowired
    private ElasticsearchUtil esUtil;

    /**
     * @author Jimmy Shan
     * @date 2020-11-26
     * @desc 测试ES保存
     */
    @ResponseBody
    @RequestMapping(value = "/demoEsSaveTest", method = RequestMethod.POST)
    public String demoEsSaveTest(HttpServletRequest request, HttpServletResponse response) {
        String id = request.getParameter("id");
        String demoName = request.getParameter("demoName");
        String demoCode = request.getParameter("demoCode");
        String demoValue = request.getParameter("demoValue");
String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.addData(new CusDemoInfoDocument( id, demoName, demoCode, demoValue), index, type, id); LOGGER.info("TestController --> demoEsSaveTest"); return "The data of ES was save success."; } /** * @author Jimmy Shan * @date 2020-11-26 * @desc 测试ES读取 */ @ResponseBody @RequestMapping(value = "/demoEsGetTest", method = RequestMethod.POST) public List<CusDemoInfoDocument> demoEsGetTest(HttpServletRequest request, HttpServletResponse response) { String demoValue = request.getParameter("demoValue"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; List<CusDemoInfoDocument> demoList = esService.searchHighLightDocForDemo(demoValue, index, type); LOGGER.info("TestController --> demoEsGetTest result ==> {}", JsonWare.beanToJson(demoList)); return demoList; } /** * @author Jimmy Shan * @date 2020-11-26 * @desc 测试ES 根据ID删除 */ @ResponseBody @RequestMapping(value = "/demoEsDelTest", method = RequestMethod.POST) public String demoEsDelTest(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.deleteById(index, type, id); LOGGER.info("TestController --> demoEsDelTest"); return "Delete Success."; } /** * @author Jimmy Shan * @date 2020-11-30 * @desc 测试ES 根据ID更新 */ @ResponseBody @RequestMapping(value = "/demoEsUpdateByIdTest", method = RequestMethod.POST) public String demoEsUpdateByIdTest(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.updateData(new CusDemoInfoDocument(id, "这里只是测试更新name", "这里只是测试更新code", "这里只是测试更新val"), index, type, id); LOGGER.info("TestController --> demoEsUpdateByIdTest"); return "Update Success."; } /** * @author Jimmy Shan * @date 2020-11-30 * @desc 测试ES 删除索引 */ @ResponseBody @RequestMapping(value = "/demoEsDelIdxTest", method = RequestMethod.POST) public String demoEsDelIdxTest(HttpServletRequest request, HttpServletResponse response) { String index = "cusdemoinfoidx"; esUtil.deleteIndex(index); return "delete index Success."; } }

好了,我们将程序部署到阿里云ECS环境中,用postman可以调试一下,以上方法,本人是经过验证的,可以和阿里云ES互通及各项操作。

PS:本人在阿里云ES管理端,开启了自动创建及删除索引功能,这个是个好东西,可以不用每次单独去创建索引,直接使用addData添加数据,自动

会创建对应的索引。

 

还是那句老话,发出来供大家一起学习及讨论,如觉得好可转载,但请注明原创地址,万分感谢。

 

posted @ 2020-12-05 22:41  JimmyShan  阅读(1133)  评论(2编辑  收藏  举报