elasticsearch7.0.1-Java RestClient API【伸手党福利】【基于Springboot2.1.3集成。非常详细的注释可直接在生产环境中使用】

3 篇文章 0 订阅
2 篇文章 0 订阅

直接忽略安装教程,网上一搜一大把,安装起来也比较简单。但是网上对于Java RestClient 能够集成很好的没有。接下来我将根据官方API集成了一套Java RestClient API操作模板。

环境说明:

SpringBoot2.1.3 + JDK1.8 + Maven(Springboot自带不好使用,建议按照自己的方式封装)

需要源代码的可以加我微信[JornTang]

application.yml

elasticsearch: 
  # ip地址,多个使用,分隔
  ipAddrs: 127.0.0.1:9200,127.0.0.1:9201
  client: 
    # 连接目标url最大超时
    connectTimeOut: 5000
    # 等待响应(读数据)最大超时
    socketTimeOut: 6000
    # 从连接池中获取可用连接最大超时时间
    connectionRequestTime: 3000
    # 连接池中的最大连接数
    maxConnectNum: 30
    # 连接同一个route最大的并发数
    maxConnectPerRoute: 10

pom.xml

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.0.1</version>
</dependency>

直接上代码。【ps: 代码待重构】

1、创建RestClientConfig配置类

/**
 * elasticsearch 配置
 */
@Configuration
public class RestClientConfig {
    
    /**
     * elasticsearch 连接地址多个地址使用,分隔
     */
    @Value("${elasticsearch.ipAddrs}")
    private String[] ipAddrs;
    
    /**
      * 连接目标url最大超时
     */
    @Value("${elasticsearch.client.connectTimeOut}")
    private Integer connectTimeOut;
    
    /**
      * 等待响应(读数据)最大超时
     */
    @Value("${elasticsearch.client.socketTimeOut}")
    private Integer socketTimeOut;
    
    /**
      * 从连接池中获取可用连接最大超时时间
     */
    @Value("${elasticsearch.client.connectionRequestTime}")
    private Integer connectionRequestTime;
    
    /**
      * 连接池中的最大连接数
     */
    @Value("${elasticsearch.client.maxConnectNum}")
    private Integer maxConnectNum;
    
    /**
      * 连接同一个route最大的并发数
     */
    @Value("${elasticsearch.client.maxConnectPerRoute}")
    private Integer maxConnectPerRoute;

    @Bean
    public HttpHost[] httpHost(){
        HttpHost[] httpHosts = new HttpHost[ipAddrs.length];
        for (int i = 0; i < ipAddrs.length; i++) {
            String[] ipAddr = ipAddrs[i].split(":");
            httpHosts[i] = new HttpHost(ipAddr[0], Integer.valueOf(ipAddr[1]), "http");
        }
        return httpHosts;
    }

    @Bean(initMethod="init",destroyMethod="close")
    public ElasticRestClientFactory getFactory(){
        return ElasticRestClientFactory.
                build(httpHost(), connectTimeOut, socketTimeOut, connectionRequestTime, maxConnectNum, maxConnectPerRoute);
    }

    @Bean
    @Scope("singleton")
    public RestClient getRestClient(){
        return getFactory().getClient();
    }

    @Bean
    @Scope("singleton")
    public RestHighLevelClient getRestHighClient(){
        return getFactory().getRestHighClient();
    }

}

 2、创建ElasticRestClientFactory工厂类

public class ElasticRestClientFactory {
    
    private static Logger log = LoggerFactory.getLogger(ElasticRestClientFactory.class);
    
    // 连接目标url最大超时
    public static int CONNECT_TIMEOUT_MILLIS = 3000;
    // 等待响应(读数据)最大超时
    public static int SOCKET_TIMEOUT_MILLIS = 6000;
    // 从连接池中获取可用连接最大超时时间
    public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 2000;
    
    // 连接池中的最大连接数
    public static int MAX_CONN_TOTAL =15;
    // 连接同一个route最大的并发数
    public static int MAX_CONN_PER_ROUTE = 10;

    private static HttpHost[] HTTP_HOST;
    private RestClientBuilder builder;
    private RestClient restClient;
    private RestHighLevelClient restHighLevelClient;

    private static ElasticRestClientFactory restClientFactory = new ElasticRestClientFactory();

    private ElasticRestClientFactory(){}

    public static ElasticRestClientFactory build(HttpHost[] httpHost, Integer maxConnectNum, Integer maxConnectPerRoute){
        HTTP_HOST = httpHost;
        MAX_CONN_TOTAL = maxConnectNum;
        MAX_CONN_PER_ROUTE = maxConnectPerRoute;
        return  restClientFactory;
    }

    public static ElasticRestClientFactory build(HttpHost[] httpHost,Integer connectTimeOut, Integer socketTimeOut,
                                              Integer connectionRequestTime,Integer maxConnectNum, Integer maxConnectPerRoute){
        HTTP_HOST = httpHost;
        CONNECT_TIMEOUT_MILLIS = connectTimeOut;
        SOCKET_TIMEOUT_MILLIS = socketTimeOut;
        CONNECTION_REQUEST_TIMEOUT_MILLIS = connectionRequestTime;
        MAX_CONN_TOTAL = maxConnectNum;
        MAX_CONN_PER_ROUTE = maxConnectPerRoute;
        return  restClientFactory;
    }


    public void init(){
        builder = RestClient.builder(HTTP_HOST);
        setConnectTimeOutConfig();
        setMutiConnectConfig();
        restClient = builder.build();
        restHighLevelClient = new RestHighLevelClient(builder);
        log.info("Elasticsearch highLevelRestClient init successful");
    }
    // 配置连接延时时间
    public void setConnectTimeOutConfig(){
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
            requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS);
            requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS);
            return requestConfigBuilder;
        });
    }
    // 使用异步httpclient时设置并发连接数
    public void setMutiConnectConfig(){
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(MAX_CONN_TOTAL);
            httpClientBuilder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE);
            return httpClientBuilder;
        });
    }
    
    public RestClient getClient(){
        return restClient;
    }

    public RestHighLevelClient getRestHighClient(){
        return restHighLevelClient;
    }

    public void close() {
        if (restClient != null) {
            try {
                restClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        log.info("Elasticsearch highLevelRestClient closed");
    }
}
3、创建QueryResult查询结果封装类

public class QueryResult {
    
    /**
      *  查询是否成功true=成功,false=失败
     */
    private boolean succesful;
    /**
      *  查询耗时
     */
    private long took;
    /**
      *  是否超时
     */
    private boolean timedout;
    /**
      *  查询总数
     */
    private long hitsTotal;
    /**
      *  最高评分
     */
    private float maxScore;
    /**
      * 分片信息 
     * total : 分片总数
     * successful : 成功查询的分片数
     * skipped" : 跳过查询的分片数
     * failed" : 失败查询的分片数
     */
    private Map<String,Integer> shardsInfo;
    /**
      *  查询结果
     */
    private List<Map<String,Object>> hitsBody;
    
    public QueryResult() {
    }
    
    public QueryResult(boolean succesful) {
        this.succesful = succesful;
    }
    
    public boolean getSuccesful() {
        return succesful;
    }
    public void setSuccesful(boolean succesful) {
        this.succesful = succesful;
    }
    public long getTook() {
        return took;
    }
    public void setTook(long took) {
        this.took = took;
    }
    public boolean isTimedout() {
        return timedout;
    }
    public void setTimedout(boolean timedout) {
        this.timedout = timedout;
    }
    public long getHitsTotal() {
        return hitsTotal;
    }
    public void setHitsTotal(long hitsTotal) {
        this.hitsTotal = hitsTotal;
    }
    public float getMaxScore() {
        return maxScore;
    }
    public void setMaxScore(float maxScore) {
        this.maxScore = maxScore;
    }
    public Map<String, Integer> getShardsInfo() {
        return shardsInfo;
    }
    public void setShardsInfo(Map<String, Integer> shardsInfo) {
        this.shardsInfo = shardsInfo;
    }
    public List<Map<String, Object>> getHitsBody() {
        return hitsBody;
    }
    public void setHitsBody(List<Map<String, Object>> hitsBody) {
        this.hitsBody = hitsBody;
    }
}
4、创建ElasticQueryDSLTemplates模板类【基于Query DSL】后续会增加基于SQL一套模板方法

/**
 * elasticsearch restClient 工具类
 * @author dell
 * 更多API请参考官网https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html
 */
@Component
public class ElasticQueryDSLTemplates {
    
    private static Logger log = LoggerFactory.getLogger(ElasticQueryDSLTemplates.class);
    
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    
    /**######################################## Index API  #################################################
     * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-supported-apis.html
     * 
     */
    
    /**
      * 创建索引
     * @param indexName
     * @param builder
     * @return
     * @throws IOException
     */
    public boolean createIndex(String indexName, Builder builder) throws IOException {
        Assert.notNull(indexName, "索引名称不能为空");
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        request.settings(Settings.builder() 
                .put("index.number_of_shards", 3) // 分片
                .put("index.number_of_replicas", 1) //副本
                .put("refresh_interval", "10s")
            );
        
        // 创建fullText属性
        Map<String, Object> fullText = new HashMap<>();
        fullText.put("type", "text");
        fullText.put("analyzer", "ik_max_word"); // 可选ik_max_word、ik_smart
        fullText.put("search_analyzer", "ik_smart"); // 可选ik_max_word、ik_smart
        fullText.put("term_vector", "with_positions_offsets"); // 全文检索fvh设置
        
        // 创建fondCode属性
        Map<String, Object> fondCode = new HashMap<>();
        fondCode.put("type", "keyword");
        
        Map<String, Object> properties = new HashMap<>();
        properties.put("fullText", fullText);
        properties.put("fondCode", fondCode);
        
        Map<String, Object> mapping = new HashMap<>();
        mapping.put("properties", properties);
        request.mapping(mapping);
        
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        boolean acknowledged = createIndexResponse.isAcknowledged();
        return acknowledged;
    }
    
    /**
      * 删除索引
     * @param indexName 索引名称
     * @return
     * @throws IOException 
     */
    public boolean deleteIndex(String indexName) throws IOException {
        Assert.notNull(indexName, "索引名称不能为空");
        DeleteIndexRequest request = new DeleteIndexRequest(indexName);
        AcknowledgedResponse deleteIndexResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        return deleteIndexResponse.isAcknowledged();
    }
    
    /**
      * 判断索引是否存在
     * @param indexName 索引名称
     * @return
     * @throws IOException 
     */
    public boolean existsIndex(String indexName) throws IOException {
        Assert.notNull(indexName, "索引名称不能为空");
        GetIndexRequest request = new GetIndexRequest(indexName);
        return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }
    
    /**
      * 文本分析
     * @param analyzer 分析器
     * @param analyzeText 分析文本
     * @return
     * @throws IOException
     */
    public List<AnalyzeResponse.AnalyzeToken> analyzeText(String analyzer, String analyzeText) throws IOException {
        Assert.notNull(analyzer, "分析器不能为空");
        Assert.notNull(analyzeText, "分析文本不能为空");
        AnalyzeRequest analyzeRequest = new AnalyzeRequest();
        analyzeRequest.analyzer(analyzer);        
        analyzeRequest.text(analyzeText);
        AnalyzeResponse response = restHighLevelClient.indices().analyze(analyzeRequest, RequestOptions.DEFAULT);
        return response.getTokens();
    }
    
    /**######################################## Index Document API  #################################################
     * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-supported-apis.html
     * 
     */
    
    /**
      * 添加索引文档
     * @param indexName 索引名称
     * @param id 文档id
     * @param docMap 文档map
     * @return
     * @throws IOException 
     */
    public boolean addDocument(String indexName, String id, Map<String, Object> docMap) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(id, "索引文档ID不能为空");
            Assert.notNull(docMap, "索引文档docMap不能为空");
            IndexRequest indexRequest = new IndexRequest(indexName).id(id).source(docMap);
            indexRequest.opType(DocWriteRequest.OpType.CREATE);
            restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("添加索引文档失败异常。索引名称【{}】,索引文档ID【{}】", indexName, id, e);
            optFlag = Boolean.FALSE;
        }
        return optFlag;
    }
    
    /**
      *  根据ID获取索引文档
     * @param indexName 索引名称
     * @param id 文档id
     * @return
     * @throws IOException 
     */
    public Map<String, Object> getDocument(String indexName, String id) {
        Map<String, Object> docMap = null;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(id, "索引文档ID不能为空");
            GetRequest request = new GetRequest(indexName, id);
            GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
            docMap = response.getSourceAsMap();
        } catch (Exception e) {
            log.error("根据ID获取索引文档异常。索引名称【{}】,索引文档ID【{}】", indexName, id, e);
        }
        return docMap;
    }
    
    /**
      *  根据ID判断索引文档是否存在
     * @param indexName 索引名称
     * @param id 文档id
     * @return
     * @throws IOException 
     */
    public boolean existsDocument(String indexName, String id) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(id, "索引文档ID不能为空");
            GetRequest request = new GetRequest(indexName, id);    
            request.fetchSourceContext(new FetchSourceContext(false)); 
            request.storedFields("_none_");
            optFlag = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("根据ID判断索引文档是否存在异常。索引名称【{}】,索引文档ID【{}】", indexName, id, e);
        }
        return optFlag;
    }
    
    /**
      *  根据ID删除索引文档
     * @param indexName 索引名称
     * @param id 文档id
     * @return
     * @throws IOException 
     */
    public boolean deleteDocument(String indexName, String id) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(id, "索引文档ID不能为空");
            GetRequest request = new GetRequest(indexName, id);    
            request.fetchSourceContext(new FetchSourceContext(false)); 
            request.storedFields("_none_");
            optFlag = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("根据ID删除索引文档异常。索引名称【{}】,索引文档ID【{}】", indexName, id, e);
        }
        return optFlag;
    }
    
    /**
      *  根据ID修改索引文档
     * @param indexName 索引名称
     * @param id 文档id
     * @param docMap 文档map
     * @return
     * @throws IOException 
     */
    public boolean updateDocument(String indexName, String id, Map<String, Object> docMap) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(id, "索引文档ID不能为空");
            Assert.notNull(docMap, "索引文档docMap不能为空");
            UpdateRequest request = new UpdateRequest(indexName, id).doc(docMap);
            restHighLevelClient.update(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("根据ID修改索引文档异常。索引名称【{}】,索引文档ID【{}】", indexName, id, e);
        }
        return optFlag;
    }
    
    /**
      *  批量添加索引文档
     * @param indexName 索引名称
     * @param docMaps 文档maps
     * @return
     * @throws IOException 
     */
    public boolean bulkAddDocument(String indexName, List<Map<String, Object>> docMaps) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(docMaps, "索引文档docMaps不能为空");
            
            BulkRequest bulkRequest = new BulkRequest();
            for (int i = 0; i < docMaps.size(); i++) {
                Map<String, Object> docMap = docMaps.get(i);
                bulkRequest.add(new IndexRequest(indexName).id(docMap.get("id")+"").source(docMaps).opType(DocWriteRequest.OpType.CREATE));
            }
            restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("批量添加索引文档异常。索引名称【{}】", indexName, e);
        }
        return optFlag;
    }
    
    /**
      *  批量修改索引文档
     * @param indexName 索引名称
     * @param docMaps 文档maps
     * @return
     * @throws IOException 
     */
    public boolean bulkUpdateDocument(String indexName, List<Map<String, Object>> docMaps) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(docMaps, "索引文档docMaps不能为空");
            
            BulkRequest bulkRequest = new BulkRequest();
            for (int i = 0; i < docMaps.size(); i++) {
                Map<String, Object> docMap = docMaps.get(i);
                bulkRequest.add(new UpdateRequest(indexName, docMap.get("id")+"").doc(docMap));
            }
            restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("批量修改索引文档异常。索引名称【{}】", indexName, e);
        }
        return optFlag;
    }
    
    /**
      *  批量修改索引文档
     * @param indexName 索引名称
     * @param docMaps 文档maps
     * @return
     * @throws IOException 
     */
    public boolean bulkDeleteDocument(String indexName, List<Map<String, Object>> docMaps) {
        boolean optFlag = Boolean.TRUE;
        try {
            Assert.notNull(indexName, "索引名称不能为空");
            Assert.notNull(docMaps, "索引文档docMaps不能为空");
            
            BulkRequest bulkRequest = new BulkRequest();
            for (int i = 0; i < docMaps.size(); i++) {
                Map<String, Object> docMap = docMaps.get(i);
                bulkRequest.add(new DeleteRequest(indexName, docMap.get("id")+""));
            }
            restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("批量修改索引文档异常。索引名称【{}】", indexName, e);
        }
        return optFlag;
    }
    
    /**######################################## QUERY DSL  #################################################
     * https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-all-query.html
     * 
     */
    
    /**
      *  精准匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName in(term1,term2)
     * GET /_search
     *    {
     *        "query": {
     *            "terms" : { "user" : ["kimchy", "elasticsearch"]}
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForTermsQuery(String indexName, String fieldName, Object... terms ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "精准查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.termsQuery(fieldName, terms));
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("精准匹配检索异常。索引名称【{}】, terms查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**
      *  精准匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName in(term1,term2)
     * GET /_search
     *    {
     *        "query": {
     *            "terms" : { "user" : ["kimchy", "elasticsearch"]}
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForTermsQuery(String indexName, String fieldName, int offset, int pageSize, Object... terms ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "精准查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.termsQuery(fieldName, terms));
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("精准匹配分页检索异常。索引名称【{}】, terms查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**
      *  范围匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param range 支持 gte(>=)、gt(>)、lte(<=)、lt(<)
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20
     * GET _search
     *    {
     *        "query": {
     *            "range" : {
     *                "age" : {
     *                    "gte" : 10,
     *                    "lte" : 20,
     *                    "boost" : 2.0
     *                }
     *            }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForRangeQuery(String indexName, String fieldName, Map<String, Object> terms, String format) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置range查询条件
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(fieldName);
            for (String key: terms.keySet()) {
                switch (key) {
                    case "gte":
                        rangeQueryBuilder.gte(terms.get(key));
                        continue;
                    case "gt":
                        rangeQueryBuilder.gt(terms.get(key));
                        continue;
                    case "lte":
                        rangeQueryBuilder.lte(terms.get(key));
                        continue;
                    case "lt":
                        rangeQueryBuilder.lt(terms.get(key));
                        continue;
                    default:
                        break;
                    }
            }
            // 设置转换规则 一般针对时间属性 dd/MM/yyyy||yyyy
            if(StringUtils.isNotEmpty(format)) {
                rangeQueryBuilder.format(format);
            }
            sourceBuilder.query(rangeQueryBuilder);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("单属性范围匹配检索异常。索引名称【{}】, range查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**
      *  范围匹配-分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param range 支持 gte(>=)、gt(>)、lte(<=)、lt(<)
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20
     * GET _search
     *    {
     *        "query": {
     *            "range" : {
     *                "age" : {
     *                    "gte" : 10,
     *                    "lte" : 20,
     *                    "boost" : 2.0
     *                }
     *            }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForRangeQuery(String indexName, String fieldName, int offset, int pageSize, Map<String, Object> terms, String format) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置range查询条件
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(fieldName);
            for (String key: terms.keySet()) {
                switch (key) {
                    case "gte":
                        rangeQueryBuilder.gte(terms.get(key));
                        continue;
                    case "gt":
                        rangeQueryBuilder.gt(terms.get(key));
                        continue;
                    case "lte":
                        rangeQueryBuilder.lte(terms.get(key));
                        continue;
                    case "lt":
                        rangeQueryBuilder.lt(terms.get(key));
                        continue;
                    default:
                        break;
                    }
            }
            // 设置转换规则 一般针对时间属性 dd/MM/yyyy||yyyy
            if(StringUtils.isNotEmpty(format)) {
                rangeQueryBuilder.format(format);
            }
            sourceBuilder.query(rangeQueryBuilder);
            
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("单属性范围匹配分页检索异常。索引名称【{}】, range查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**
      *  前缀模糊匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param term 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like 'prefix%'
     * GET /_search
     *    { "query": {
     *        "prefix" : { "user" : "ki" }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForPrefixQuery(String indexName, String fieldName, String term) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "前缀模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.prefixQuery(fieldName, term));
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error(" 前缀模糊匹配检索异常。索引名称【{}】, terms查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  前缀模糊匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like 'prefix%'
     * GET /_search
     *    { "query": {
     *        "prefix" : { "user" : "ki" }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForPrefixQuery(String indexName, String fieldName, int offset, int pageSize, String term ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "前缀模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.prefixQuery(fieldName, term));
            //设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error(" 前缀模糊匹配分页检索异常。索引名称【{}】, terms查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  通配符模糊匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param term 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like 'xxx%xxx'
     * GET /_search
     *    {
     *        "query": {
     *            "wildcard" : { "user" : "ki*y" }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForWildcardQuery(String indexName, String fieldName, String term) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "通配符模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.wildcardQuery(fieldName, term));
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error(" 通配符模糊匹配检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  通配符模糊匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like 'xxx%xxx'
     * GET /_search
     *    {
     *        "query": {
     *            "wildcard" : { "user" : "ki*y" }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForWildcardQuery(String indexName, String fieldName, int offset, int pageSize, String term ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "通配符模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.wildcardQuery(fieldName, term));
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error(" 通配符模糊匹配检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  模糊匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like '%term%'
     * GET /_search
     *    {
     *        "query": {
     *           "fuzzy" : { "user" : "ki" }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForFuzzyQuery(String indexName, String fieldName, Object term) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.fuzzyQuery(fieldName, term));
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error(" 模糊匹配检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  模糊匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where fieldName like '%term%'
     * GET /_search
     *    {
     *        "query": {
     *           "fuzzy" : { "user" : "ki" }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForFuzzyQuery(String indexName, String fieldName, int offset, int pageSize, Object term ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(term, "模糊检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.fuzzyQuery(fieldName, term));
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("模糊匹配检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, term, e);
        }
        return qr;
    }
    
    /**
      *  根据ids匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where id in(id1,id2...)
     * GET /_search
     *    {
     *        "query": {
     *            "ids" : {
     *                "values" : ["1", "2", "3"]
     *            }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForIdsQuery(String indexName, String fieldName, String... terms ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "ids检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.idsQuery(terms));
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("根据ids匹配检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**
      *  根据ids匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param terms 匹配条件
     * @return SQL翻译 select * from xxx_table where id in(id1,id2...)
     * GET /_search
     *    {
     *        "query": {
     *            "ids" : {
     *                "values" : ["1", "2", "3"]
     *            }
     *        }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForIdsQuery(String indexName, String fieldName, int offset, int pageSize, String... terms ) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            Assert.notNull(terms, "ids检索条件term不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.idsQuery(terms));
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("根据ids匹配分页检索异常。索引名称【{}】, term查询字段【{}】,查询条件【{}】", indexName, fieldName, JSONObject.toJSONString(terms), e);
        }
        return qr;
    }
    
    /**######################################## QUERY DSL【复合查询】  #################################################
     * https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-all-query.html
     * 
     */
    
    /**
      *  复合匹配--检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param bool 支持 
     * must 必须出现在匹配的文档中,并有助于得分
     * filter 必须出现在匹配的文档中。但是不同于 must查询的分数将被忽略。过滤器子句在过滤器上下文中执行,这意味着忽略评分并考虑使用子句进行高速缓存
     * should 应出现在匹配的文档中
     * must_not不得出现在匹配的文档中
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20 and name not in(name1,name2)
     * POST _search
     *    {
     *      "query": {
     *        "bool" : {
     *          "must" : {
     *            "term" : { "user" : "kimchy" }
     *          },
     *          "filter": {
     *            "term" : { "tag" : "tech" }
     *          },
     *          "must_not" : {
     *            "range" : {
     *              "age" : { "gte" : 10, "lte" : 20 }
     *            }
     *          },
     *          "should" : [
     *            { "term" : { "tag" : "wow" } },
     *            { "term" : { "tag" : "elasticsearch" } }
     *          ],
     *          "minimum_should_match" : 1,
     *          "boost" : 1.0
     *        }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForBoolQuery(String indexName, Map<String, List<QueryBuilder>> terms) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            //Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            //Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置复合查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String key: terms.keySet()) {
                switch (key) {
                    case "must":
                        List<QueryBuilder> mustQbs = terms.get(key);
                        for(QueryBuilder qb: mustQbs) {
                            boolQueryBuilder.must(qb);
                        }
                        continue;
                    case "filter":
                        List<QueryBuilder> filterQbs = terms.get(key);
                        for(QueryBuilder qb: filterQbs) {
                            boolQueryBuilder.filter(qb);
                        }
                        continue;
                    case "mustNot":
                        List<QueryBuilder> mustNotQbs = terms.get(key);
                        for(QueryBuilder qb: mustNotQbs) {
                            boolQueryBuilder.mustNot(qb);
                        }
                        continue;
                    case "should":
                        List<QueryBuilder> shouldQbs = terms.get(key);
                        for(QueryBuilder qb: shouldQbs) {
                            boolQueryBuilder.should(qb);
                        }
                        continue;
                    default:
                        break;
                    }
            }
            sourceBuilder.query(boolQueryBuilder);
            
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
        } catch (Exception e) {
            log.error("复合匹配检索异常。索引名称【{}】", indexName, e);
        }
        return qr;
    }
    
    /**
      *  复合匹配--分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param bool 支持 
     * must 必须出现在匹配的文档中,并有助于得分
     * filter 必须出现在匹配的文档中。但是不同于 must查询的分数将被忽略。过滤器子句在过滤器上下文中执行,这意味着忽略评分并考虑使用子句进行高速缓存
     * should 应出现在匹配的文档中
     * must_not不得出现在匹配的文档中
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20 and name not in(name1,name2)
     * POST _search
     *    {
     *      "query": {
     *        "bool" : {
     *          "must" : {
     *            "term" : { "user" : "kimchy" }
     *          },
     *          "filter": {
     *            "term" : { "tag" : "tech" }
     *          },
     *          "must_not" : {
     *            "range" : {
     *              "age" : { "gte" : 10, "lte" : 20 }
     *            }
     *          },
     *          "should" : [
     *            { "term" : { "tag" : "wow" } },
     *            { "term" : { "tag" : "elasticsearch" } }
     *          ],
     *          "minimum_should_match" : 1,
     *          "boost" : 1.0
     *        }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForBoolQuery(String indexName, int offset, int pageSize, Map<String, List<QueryBuilder>> terms) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            //Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            //Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置复合查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String key: terms.keySet()) {
                switch (key) {
                    case "must":
                        List<QueryBuilder> mustQbs = terms.get(key);
                        for(QueryBuilder qb: mustQbs) {
                            boolQueryBuilder.must(qb);
                        }
                        continue;
                    case "filter":
                        List<QueryBuilder> filterQbs = terms.get(key);
                        for(QueryBuilder qb: filterQbs) {
                            boolQueryBuilder.filter(qb);
                        }
                        continue;
                    case "mustNot":
                        List<QueryBuilder> mustNotQbs = terms.get(key);
                        for(QueryBuilder qb: mustNotQbs) {
                            boolQueryBuilder.mustNot(qb);
                        }
                        continue;
                    case "should":
                        List<QueryBuilder> shouldQbs = terms.get(key);
                        for(QueryBuilder qb: shouldQbs) {
                            boolQueryBuilder.should(qb);
                        }
                        continue;
                    default:
                        break;
                    }
            }
            sourceBuilder.query(boolQueryBuilder);
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
            
        } catch (Exception e) {
            log.error("复合匹配分页检索异常。索引名称【{}】", indexName, e);
        }
        return qr;
    }
    
    /**######################################## QUERY DSL【高亮查询】  #################################################
     * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
     * 
     */
    
    /**
      *  复合匹配--高亮检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param bool 支持 
     * must 必须出现在匹配的文档中,并有助于得分
     * filter 必须出现在匹配的文档中。但是不同于 must查询的分数将被忽略。过滤器子句在过滤器上下文中执行,这意味着忽略评分并考虑使用子句进行高速缓存
     * should 应出现在匹配的文档中
     * must_not不得出现在匹配的文档中
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20 and name not in(name1,name2)
     * POST _search
     *    {
     *      "query": {
     *        "bool" : {
     *          "must" : {
     *            "term" : { "user" : "kimchy" }
     *          },
     *          "filter": {
     *            "term" : { "tag" : "tech" }
     *          },
     *          "must_not" : {
     *            "range" : {
     *              "age" : { "gte" : 10, "lte" : 20 }
     *            }
     *          },
     *          "should" : [
     *            { "term" : { "tag" : "wow" } },
     *            { "term" : { "tag" : "elasticsearch" } }
     *          ],
     *          "minimum_should_match" : 1,
     *          "boost" : 1.0
     *        }
     *      },
     *    "highlight": {
     *        "pre_tags": "<em>",
     *        "post_tags": "</em>",
     *        "fields": {
     *          "fullText":{
     *            "type": "unified",
     *            "fragment_size" : 300,
     *            "number_of_fragments" : 0,
     *            "no_match_size": 2      
     *          }
     *        }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForHighlightBoolQuery(String indexName, Map<String, List<QueryBuilder>> terms) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            //Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            //Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置复合查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String key: terms.keySet()) {
                switch (key) {
                    case "must":
                        List<QueryBuilder> mustQbs = terms.get(key);
                        for(QueryBuilder qb: mustQbs) {
                            boolQueryBuilder.must(qb);
                        }
                        continue;
                    case "filter":
                        List<QueryBuilder> filterQbs = terms.get(key);
                        for(QueryBuilder qb: filterQbs) {
                            boolQueryBuilder.filter(qb);
                        }
                        continue;
                    case "mustNot":
                        List<QueryBuilder> mustNotQbs = terms.get(key);
                        for(QueryBuilder qb: mustNotQbs) {
                            boolQueryBuilder.mustNot(qb);
                        }
                        continue;
                    case "should":
                        List<QueryBuilder> shouldQbs = terms.get(key);
                        for(QueryBuilder qb: shouldQbs) {
                            boolQueryBuilder.should(qb);
                        }
                        continue;
                    default:
                        break;
                    }
            }
            sourceBuilder.query(boolQueryBuilder);
            
            // 高亮构造器
            HighlightBuilder highlightBuilder = new HighlightBuilder(); 
            
            // 全局设置
            highlightBuilder.numOfFragments(1);
            highlightBuilder.fragmentSize(900);
            
            // 自定义高亮标签
            highlightBuilder.preTags("<em class='searcHighlight'>");
            highlightBuilder.postTags("</em>");
            
            HighlightBuilder.Field highlightFullText =new HighlightBuilder.Field("fullText");
            // 设置显示器。支持unified、plain、fvh。默认unified
            highlightFullText.highlighterType("fvh");
            // 设置片段长度,默认100
            highlightFullText.fragmentOffset(300);
            // 设置返回的片段数, 默认5
            highlightFullText.numOfFragments(10);
            highlightBuilder.field(highlightFullText);  
            
            // 添加更多高亮字段
            //HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("ip_addr");
            //highlightBuilder.field(highlightUser);
            
            // 设置高亮构造器
            sourceBuilder.highlighter(highlightBuilder);
            
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
            
        } catch (Exception e) {
            log.error("复合匹配高亮检索异常。索引名称【{}】", indexName, e);
        }
        return qr;
    }
    
    /**
      *  复合匹配--高亮分页检索
     * @param indexName 索引名称
     * @param fieldName 查询目标字段
     * @param bool 支持 
     * must 必须出现在匹配的文档中,并有助于得分
     * filter 必须出现在匹配的文档中。但是不同于 must查询的分数将被忽略。过滤器子句在过滤器上下文中执行,这意味着忽略评分并考虑使用子句进行高速缓存
     * should 应出现在匹配的文档中
     * must_not不得出现在匹配的文档中
     * @return SQL翻译 select * from xxx_table where age >=18 and age<20 and name not in(name1,name2)
     * POST _search
     *    {
     *      "query": {
     *        "bool" : {
     *          "must" : {
     *            "term" : { "user" : "kimchy" }
     *          },
     *          "filter": {
     *            "term" : { "tag" : "tech" }
     *          },
     *          "must_not" : {
     *            "range" : {
     *              "age" : { "gte" : 10, "lte" : 20 }
     *            }
     *          },
     *          "should" : [
     *            { "term" : { "tag" : "wow" } },
     *            { "term" : { "tag" : "elasticsearch" } }
     *          ],
     *          "minimum_should_match" : 1,
     *          "boost" : 1.0
     *        }
     *      },
     *    "highlight": {
     *        "pre_tags": "<em>",
     *        "post_tags": "</em>",
     *        "fields": {
     *          "fullText":{
     *            "type": "unified",
     *            "fragment_size" : 300,
     *            "number_of_fragments" : 0,
     *            "no_match_size": 2      
     *          }
     *        }
     *      }
     *    }
     * @throws IOException 
     */
    public QueryResult searchForHighlightBoolQuery(String indexName, int offset, int pageSize, Map<String, List<QueryBuilder>> terms) {
        QueryResult qr = new QueryResult(Boolean.TRUE);
        try {
            Assert.notNull(indexName, "索引名称indexName不能为空");
            //Assert.notNull(fieldName, "查询目标属性fieldName不能为空");
            //Assert.notNull(terms, "范围查询条件terms不能为空");
            SearchRequest searchRequest = new SearchRequest();
            // 设置要查询的索引名称
            searchRequest.indices(indexName);
            
            // 构造查询器
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            
            // 设置复合查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (String key: terms.keySet()) {
                switch (key) {
                    case "must":
                        List<QueryBuilder> mustQbs = terms.get(key);
                        for(QueryBuilder qb: mustQbs) {
                            boolQueryBuilder.must(qb);
                        }
                        continue;
                    case "filter":
                        List<QueryBuilder> filterQbs = terms.get(key);
                        for(QueryBuilder qb: filterQbs) {
                            boolQueryBuilder.filter(qb);
                        }
                        continue;
                    case "mustNot":
                        List<QueryBuilder> mustNotQbs = terms.get(key);
                        for(QueryBuilder qb: mustNotQbs) {
                            boolQueryBuilder.mustNot(qb);
                        }
                        continue;
                    case "should":
                        List<QueryBuilder> shouldQbs = terms.get(key);
                        for(QueryBuilder qb: shouldQbs) {
                            boolQueryBuilder.should(qb);
                        }
                        continue;
                    default:
                        break;
                    }
            }
            sourceBuilder.query(boolQueryBuilder);
            
            // 高亮构造器
            HighlightBuilder highlightBuilder = new HighlightBuilder(); 
            
            // 全局设置
            highlightBuilder.numOfFragments(5);
            highlightBuilder.fragmentSize(100);
            
            // 自定义高亮标签
            highlightBuilder.preTags("<em class='searcHighlight'>");
            highlightBuilder.postTags("</em>");
            
            HighlightBuilder.Field highlightFullText =new HighlightBuilder.Field("fullText");
            // 设置显示器。支持unified、plain、fvh。默认unified
            highlightFullText.highlighterType("fvh");
            // 设置片段长度,默认100
            highlightFullText.fragmentOffset(100);
            // 设置返回的片段数, 默认5
            highlightFullText.numOfFragments(5);
            highlightBuilder.field(highlightFullText);  
            
            // 添加更多高亮字段
            //HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("ip_addr");
            //highlightBuilder.field(highlightUser);
            
            // 设置高亮构造器
            sourceBuilder.highlighter(highlightBuilder);
            
            // 设置分页
            sourceBuilder.from(offset).size(pageSize);
            
            // 执行查询
            excuteQuery(searchRequest, sourceBuilder, qr);
            
        } catch (Exception e) {
            log.error("复合匹配高亮分页检索异常。索引名称【{}】", indexName, e);
        }
        return qr;
    }
    
    /**
      * 执行查询并设置相关参数
     * @throws IOException 
     */
    public void excuteQuery(SearchRequest searchRequest, SearchSourceBuilder sourceBuilder, QueryResult qr) throws IOException {
        // 设置超时
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        // 按查询评分降序 排序支持四种:Field-, Score-, GeoDistance-, ScriptSortBuilder
        sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        
        // 设置查询器
        searchRequest.source(sourceBuilder);
        
        // 执行查询
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 封装查询结果
        doQueryResult(searchResponse, qr);
    }
    
    /**
     * 查询结果封装
     */
    public void doQueryResult(SearchResponse searchResponse, QueryResult qr) {
        // 查询耗时
        TimeValue took = searchResponse.getTook();
        qr.setTook(took.getMillis());
        
        // 是否超时
        qr.setTimedout(searchResponse.isTimedOut());
        
        // 查询总数
        qr.setHitsTotal(searchResponse.getHits().getTotalHits().value);
        
        // 最高评分
        qr.setMaxScore(searchResponse.getHits().getMaxScore());
        
        // 分片信息
        Map<String, Integer> shardsInfo = new HashMap<String, Integer>();
        shardsInfo.put("total", searchResponse.getTotalShards());
        shardsInfo.put("successful", searchResponse.getSuccessfulShards());
        shardsInfo.put("skipped", searchResponse.getSkippedShards());
        shardsInfo.put("failed", searchResponse.getFailedShards());
        qr.setShardsInfo(shardsInfo);
        
        // 获取查询结果
        List<Map<String, Object>> hitsBody = new ArrayList<Map<String, Object>>();
        SearchHits termhts=searchResponse.getHits();
        for(SearchHit hit:termhts){
            Map<String, Object> hitMap = hit.getSourceAsMap();
            
            // 高亮内容封装
            if(hitMap!= null) {
                Map<String, HighlightField> highMap = hit.getHighlightFields();
                Map<String, String> highTextMap = new HashMap<String, String>();
                if(highMap != null) {
                    for (String highKey: highMap.keySet()) {
                        String fieldName = highMap.get(highKey).getName();
                        Text highText = (highMap.get(highKey).fragments())[0];
                        highTextMap.put(fieldName, highText.toString());
                    }
                    
                    hitMap.put("highlight", highTextMap);
                }
            }
            hitsBody.add(hitMap);
        }
         qr.setHitsBody(hitsBody);
    }
}
 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

档案小唐总

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值