elasticsearch-rest-high-level-client操作elasticsearch

摘要

elasticsearch-rest-high-level-client操作elasticsearch
闲的无聊,于是写了这一篇爽文,米娜桑可直接用,除非几乎不可能有bug,有bug当我没说(doge)
QA:无想的一刀欧为啥不用springboot封装的操作依赖涅?
欧认为springboot对操作类过度封装,实现普通简单操作还行,但是涉及到较为复杂的操作时,难以使用,尤其是不同版本的springboot推出的api变化频繁,更加难以使用,es官方推出的api更新不会让操作类变化太频繁,个人感觉spboot操作不如es官方推出的api灵活强大,之前在工作中遇到的需求使用springboot提供的报错难以琢磨,且难以满足需求,所以使用了官方api
elasticsearch版本:7.4
安装操作文档:https://blog.csdn.net/UnicornRe/article/details/121747039?spm=1001.2014.3001.5501

依赖

依赖最好保持与es版本一致,如果以下依赖报错,在maven < parent > 同级标签旁加上

<properties>
        <java.version>1.8</java.version>
        <!-- <spring-cloud.version>2020.0.2</spring-cloud.version> -->
        <!--解决版本问题-->
        <elasticsearch.version>7.4.0</elasticsearch.version>
</properties>
<!--elasticsearch-->
<dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.0</version>
</dependency>
<dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.4.0</version>
</dependency>

yml配置

可自行修改配置和代码增加多台es机器,address逗号隔开

elasticsearch:
  schema: http
  address: 192.168.52.43:9200
  connectTimeout: 5000
  socketTimeout: 5000
  connectionRequestTimeout: 5000
  maxConnectNum: 100
  maxConnectPerRoute: 100

连接配置

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class EsHighLevalConfigure {
    //协议 
    @Value("${elasticsearch.schema:http}")
    private String schema="http";
    // 集群地址,如果有多个用“,”隔开 
    @Value("${elasticsearch.address}")
    private String address;
    // 连接超时时间 
    @Value("${elasticsearch.connectTimeout:5000}")
    private int connectTimeout;
    // Socket 连接超时时间 
    @Value("${elasticsearch.socketTimeout:10000}")
    private int socketTimeout;
    // 获取连接的超时时间 
    @Value("${elasticsearch.connectionRequestTimeout:5000}")
    private int connectionRequestTimeout;
    // 最大连接数 
    @Value("${elasticsearch.maxConnectNum:100}")
    private int maxConnectNum;
    // 最大路由连接数 
    @Value("${elasticsearch.maxConnectPerRoute:100}")
    private int maxConnectPerRoute;

    @Bean
    public static RestHighLevelClient restHighLevelClient() {
        List<HttpHost> hostLists = new ArrayList<>();
        String[] hostList = address.split(",");
        for (String addr : hostList) {
            String host = addr.split(":")[0];
            String port = addr.split(":")[1];
            hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
        }
        HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);
        // 异步连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeout);
            requestConfigBuilder.setSocketTimeout(socketTimeout);
            requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
            return requestConfigBuilder;
        });
        // 异步连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnectNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
            httpClientBuilder.setKeepAliveStrategy((response, context) -> Duration.ofMinutes(5).toMillis());
            return httpClientBuilder;
        });
        return new RestHighLevelClient(builder);
    }
}

索引结构

虽然索引结构肯定不是和你们一样的,但是代码结构不需要伤经动骨,
我来简单说说这个结构吧,一条知识产权信息內包含n个文档annex,包含n个(申请人发明人)applicant,
所以使用了 “type”: “nested"嵌套类型,不晓得与"type”: "object"区别的小伙伴自行学习吧,这里就不多说了。
想要学习部分优化的,安装,数据迁移冷备份的可以看看我的文章:(东西太多,部分就没写)https://blog.csdn.net/UnicornRe/article/details/121747039?spm=1001.2014.3001.5501

PUT /intellectual
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}
 PUT /intellectual/_mapping
{
        "properties": {
            "id": {
            "type": "long"
            },
            "name": {
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_smart"
            },
            "type": {
            "type": "keyword"
            },
            "keycode": {
            "type": "text",
             "analyzer": "ik_max_word",
             "search_analyzer": "ik_smart"
            },
            "officeId": {
            "type": "keyword"
            },
            "officeName": {
            "type": "keyword"
            },
            "titular": {
            "type": "keyword"
            },
            "applyTime": {
            "type": "long"
            },
            "endTime": {
            "type": "long"
            },
            "status": {
            "type": "keyword"
            },
            "agentName": {
             "type": "text",
             "analyzer": "ik_smart",
             "search_analyzer": "ik_smart"
            },
            "annex": {
                "type": "nested",
                "properties": {
                    "id": {
                    "type": "long"
                    },
                    "name": {
                     "type": "text",
                     "analyzer": "ik_max_word",
                     "search_analyzer": "ik_smart"
                    },
                    "content": {
                     "type": "text",
                      "analyzer": "ik_max_word",
                      "search_analyzer": "ik_max_word"
                       },
                    "createTime": {
                        "type": "long"
                    }
                }
            },
            "applicant": {
                    "type": "nested",
                    "properties": {
                                "id": {
                                "type": "long"
                                },
                                "applicantId": {
                                 "type": "long"
                                },
                                "isOffice": {
                                  "type": "integer"
                                },
                                "userName": {
                                 "type": "text",
                                 "analyzer": "ik_max_word",
                                 "search_analyzer": "ik_smart"
                                },
                                "outUsername": {
                                     "type": "text",
                                     "analyzer": "ik_max_word",
                                     "search_analyzer": "ik_smart"
                                }
                    }
             }
        }
}

普通常见非嵌套结构的CRUD

先不管"type": "nested"嵌套的对象,只对普通字段操作
我先定义一个实体类IntellectualEntity字段和上面的mapping一致
所有操作都注入了RestHighLevelClient restHighLevelClient

新增

public void insertIntel(IntellectualEntity intellectualEntity) throws IOException {
        //intellectual为索引名
        IndexRequest indexRequest = new IndexRequest("intellectual")
        .source(JSON.toJSONString(intellectualEntity), XContentType.JSON)
        .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
        .id(intellectualEntity.getId()+"");//手动指定es文档的id
        IndexResponse out = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        log.info("状态:{}", out.status());
    }

更新(根据id更新)

只会更新entity不为空的字段,如同mybatisplus默认自带的update
因为es文档的id一定唯一,所以方法最多只能更新一条

public void updateIntel(IntellectualEntity entity) throws IOException {
        //根据IntellectualEntity的id更新文档
        UpdateRequest updateRequest = new UpdateRequest("intellectual", entity.getId()+"");
        byte[] json = JSON.toJSONBytes(entity);
        updateRequest.doc(json, XContentType.JSON);
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        log.info("状态:{}", response.status());
    }

更新(高级,根据搜索条件更新,采用无痛painless脚本)

painless脚本适用很多业务复杂的场合,比如如下更新值字段为map里的字段

private void updateByQuery(IntellectualEntity entity) throws IOException {
        UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest();
        updateByQueryRequest.indices("intellectual");
        //搜索条件为id(因为插入时指定doc的id和实体类id一致,这样就保证了搜索结果唯一)
        //如果搜索条件查出的结果很多,使用需谨慎
        updateByQueryRequest.setQuery(new TermQueryBuilder("id", entity.getId()));
        //map存储脚本实体参数值
        Map<String,Object> map=new HashMap<>();
        map.put("intelName", entity.getName());
        map.put("intelStatus", entity.getStatus());
        map.put("intelApplyTime", entity.getApplyTime());
        map.put("intelKeyCode", entity.getKeycode());
        map.put("intelEndTime", entity.getEndTime());
        map.put("intelType", entity.getType());
        map.put("intelTitular", entity.getTitular());
        //指定哪些字段需要更新,ctx._source.xxx为es的字段,使用map的值赋值更新
        updateByQueryRequest.setScript(new Script(ScriptType.INLINE,
                "painless",
                "ctx._source.intelName=params.intelName;" +
                        "ctx._source.intelStatus=params.intelStatus;"+
                        "ctx._source.intelApplyTime=params.intelApplyTime;"+
                        "ctx._source.intelKeyCode=params.intelKeyCode;"+
                        "ctx._source.intelType=params.intelType;"+
                        "ctx._source.intelTitular=params.intelTitular;"
                , map));
        BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);
        log.info("创建状态:{}", bulkByScrollResponse.getStatus());
    }

删除

public void deleteIntel(IntellectualEntity entity) throws IOException {
        DeleteRequest deleteRequest=new DeleteRequest("intellectual",entity.getId()+"");
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        log.info("状态:{}", deleteResponse.status());
    }

删除(根据搜索条件删除)

和更新搜索条件操作类似,结合删除操作替换DeleteRequestDeleteByQueryRequest,相信机智的你已经会了

搜索高亮(普通高亮,空格多条件搜索)

这块代码暂时不涉及nested的字段的嵌套高亮
条件设置时,should=or,must=and
步骤:设置高亮构造器->搜索出结果->将高亮数据替换掉非高亮数据->返回结果
先写一个高亮构造器吧
高亮构造器:

private static void HighlightBuilder highlightBuilder;
    static {
        highlightBuilder = new HighlightBuilder();
        highlightBuilder.numOfFragments(0);//从第一个分片获取高亮片段
        highlightBuilder.preTags("<font color='#e75213'>");//自定义高亮标签
        highlightBuilder.postTags("</font>");
        highlightBuilder.highlighterType("unified");//高亮类型
        highlightBuilder
                .field("name")//需要高亮的属性值
                .field("keycode")
        ;
        highlightBuilder.requireFieldMatch(false);
    }

搜索步骤:

public List<Map<String,Object>>  queryByContent(String content,Integer pageCurrent, Date startTimeApply,Date endTimeApply,Date startTimeEnd,Date endTimeEnd ) throws IOException {
        //空格分割多条件,本搜索支持多搜索词条空格分开,多词条搜索关系用and
        String[] manyStr = content.split("\\s+");
        //定义一个list<map>作为返回结果
        List<Map<String,Object>> list = new LinkedList<>();
        //首先构造条件构造器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(manyStr.length>1){
                for (int i=0;i<manyStr.length;i++){
                	BoolQueryBuilder innerBoolQueryBuilder = QueryBuilders.boolQuery();
                	//nestedQuery,嵌套搜索条件
                    innerBoolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.content", manyStr[i]) , ScoreMode.Max).boost(2));
                    innerBoolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.simpleContent", manyStr[i]) , ScoreMode.Max).boost(2));
                    innerBoolQueryBuilder.should(QueryBuilders.nestedQuery("applicant",QueryBuilders.matchQuery("applicant.userName", manyStr[i]).prefixLength(2).maxExpansions(4).boost(5) , ScoreMode.Max));
                    innerBoolQueryBuilder.should(QueryBuilders.nestedQuery("applicant",QueryBuilders.matchQuery("applicant.outUsername", manyStr[i]).prefixLength(2).maxExpansions(4).boost(5) , ScoreMode.Max));
                    innerBoolQueryBuilder.should(QueryBuilders.matchQuery("name", manyStr[i]).boost(8));
                    innerBoolQueryBuilder.should(QueryBuilders.termsQuery("officeName", manyStr[i]).boost(100));
                    innerBoolQueryBuilder.should(QueryBuilders.fuzzyQuery("keycode", manyStr[i]).boost(5));
                    innerBoolQueryBuilder.should(QueryBuilders.matchQuery("agentName", manyStr[i]).boost(5));
                    innerBoolQueryBuilder.should(QueryBuilders.termsQuery("status", manyStr[i]).boost(30));
                    //and关系
                    boolQueryBuilder.must(innerBoolQueryBuilder);//
                }
            }
            else {
                //没有空格的
                boolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.content", content) , ScoreMode.Max).boost(2));
                boolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.simpleContent", content) , ScoreMode.Max).boost(2));
                //暂不用嵌套高亮.innerHit(new InnerHitBuilder().setHighlightBuilder(highlightBuilder)
                boolQueryBuilder.should(QueryBuilders.nestedQuery("applicant",QueryBuilders.matchQuery("applicant.userName", content).prefixLength(2).maxExpansions(4).boost(5) , ScoreMode.Max));
                boolQueryBuilder.should(QueryBuilders.nestedQuery("applicant",QueryBuilders.matchQuery("applicant.outUsername", content).prefixLength(2).maxExpansions(4).boost(5) , ScoreMode.Max));
                boolQueryBuilder.should(QueryBuilders.matchQuery("name", content).boost(8));
                boolQueryBuilder.should(QueryBuilders.termsQuery("officeName", content).boost(100));
                boolQueryBuilder.should(QueryBuilders.fuzzyQuery("keycode", content).boost(5));
                boolQueryBuilder.should(QueryBuilders.matchQuery("agentName", content).boost(5));
                boolQueryBuilder.should(QueryBuilders.termsQuery("status", content).boost(30));
            }
            if(startTimeApply!=null){
                //filter 不参与评分,不会由于搜索时间条件造成搜索评分较高导致排序不准确
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("applyTime").gte(startTimeApply.getTime()));
            }
            if(endTimeApply!=null){
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("applyTime").lte(endTimeApply.getTime()));
            }
            if(startTimeEnd!=null){
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("endTime").gte(startTimeEnd.getTime()));
            }
            if(endTimeEnd!=null){
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("endTime").lte(endTimeEnd.getTime()));
            }
            //新建请求
            SearchRequest searchRequest = new SearchRequest("intellectual");
            //新建搜索配置器
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            //搜索配置器 -> 高亮配置器
            searchSourceBuilder.highlighter(highlightBuilder);
            //搜索配置器 -> 配置组合条件
            //解释:minScore为搜索匹配项的最小评分,低于此分数的项不计入搜索结果,评分大小受到 搜索条件的.boost(权值)的影响,
            //boost值越大,导致计算评分越大,如果搜索结果不满意,可以测试调整boost的值达到比较满意的结果,还有一种方案就是自定义计算评分公式,属于专家级使用方案
            //分页解释:这种from-size分页当 页数过大的集群 可能导致搜索崩溃(因为搜索结果汇总数据条数过大,需要较大jvm内存,原理我就懒得讲太多,写不下,米娜桑有兴趣自行学习去吧),
            //解决方案是使用深度分页,当然了,单机单分片的es机器from-size不会导致搜索崩溃
            searchSourceBuilder
                    .minScore(9)//设置最小评分
                    .query(boolQueryBuilder)//装载搜索条件
                    .from((pageCurrent-1)*10)//起始条数,从0
                    .size(10)//每页展示记录
            ;
            //装载搜索配置器
            searchRequest.source(searchSourceBuilder);
            //搜索返回结果
            SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //可测试搜索的分数,结合调整boot值可以让搜索结果更加 “尽人意”
            log.info("总条数"+search.getHits().getTotalHits().value);
            log.info("符合条件的文档最大得分: "+search.getHits().getMaxScore());
            //遍历搜索结果列表
            for(SearchHit documentFields : search.getHits().getHits()){
                //sourceAsMap是不包含高亮的结果,如果搜索不要求高亮,就直接返回结果
                Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
                //highlightFieldsMap是高亮的结果
                Map<String, HighlightField> highlightFieldsMap = documentFields.getHighlightFields();
                //通过getHighLightMap方法将原不高亮的字段结果替换为高亮结果
                sourceAsMap = changeHighLightMap(sourceAsMap, highlightFieldsMap);
                //因为es存的时间设置为long时间戳类型,需要转化
                sourceAsMap.put("applyTime", new Date(Long.parseLong(sourceAsMap.get("applyTime")+"")));
                if(sourceAsMap.get("endTime")!=null){
                    sourceAsMap.put("endTime",new Date(Long.parseLong(sourceAsMap.get("endTime")+"")));
                }
                //打印分值
                log.info(documentFields.getScore());
                //存入list
                list.add(sourceAsMap);
            }
            return list;
    }

changeHighLightMap方法,这里暂时去除nested字段高亮显示

private Map<String, Object> changeHighLightMap(Map<String, Object> map, Map<String, HighlightField> highlightFields) {
        //从高亮结果获取高亮属性值,因为一条数据有多个属性值,高亮设器也可以设置多个属性值,
        //搜索的结果可能有的属性值搜索命中被存入highlightFields,有的属性值搜索没有命中则不会存入highlightFields,
        //所以判断!=null时则认为这条数据的这个属性值被命中
        HighlightField highlightName = highlightFields.get("name");
        HighlightField highlightKC = highlightFields.get("keycode");
        if (highlightName != null) {
            //可以看到fragments()本身是个数组,如果是nested类型数据fragments()数组长度可能较大,
            //但是这里选的高亮类型数据没有nested类型的,所有要么highlightName=null要么fragments长度=1
            //替换高亮的数据到不高亮的结果集
            map.put("name", highlightName.fragments()[0].string());
        }
        if (highlightKC != null) {
            map.put("keycode", highlightKC.fragments()[0].string());
        }
}

nested嵌套类型高级painless CRUD

nested嵌套插入

上面的一条知识产权信息內包含n个文档annex,包含n个(申请人发明人)applicant,这两个属性类型都是nested类型,属于列表
先写一个格式化工具:

    private static com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();

    //格式化参数
    private static Map<String, Object> convertValueToMap(Object data) {
        return mapper.convertValue(data, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
    }

假如:这条知识产权数据有了,用户上传一个pdf文档上来,我们读取完pdf文档内容后,需要将这个pdf信息存入到对应知识产权的annex列表里

public void addAnnex(IntellectualEntity entity,AnnexEntity annexEntity) throws IOException {
        UpdateRequest updateRequest=new UpdateRequest("intellectual",entity.getId()+"");
        Map<String, Object> param = new HashMap<>();
        //格式化参数
        param.put("data",convertValueToMap(annexEntity));
        //ctx._source为固定写法
        StringBuffer sc = new StringBuffer("ctx._source.annex.add(params.data);");
        Script script = new Script(INLINE, Script.DEFAULT_SCRIPT_LANG, sc.toString(), param);
        updateRequest.script(script);
        //按脚本更新,第一次插入之后,也会执行脚本,没有数据的话,插入upsert的内容
        //updateRequest.scriptedUpsert(true);
        //这个必须有不然没有数据的话,会报错
        //updateRequest.upsert(param);
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
    }

nested嵌套删除

这时用户要删除归属某条知识产权的某个annex文档

public void deleteAnnex(String intelId,Integer annexId) throws IOException {
        UpdateRequest updateRequest=new UpdateRequest("intellectual",intelId);
        Map<String, Object> param = new HashMap<>();
        param.put("id",annexId);//字符串无效
        StringBuffer sc = new StringBuffer("ctx._source.annex.removeIf(item -> item.id == params.id)");
        Script script = new Script(INLINE, Script.DEFAULT_SCRIPT_LANG, sc.toString(), param);
        updateRequest.script(script);
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
}

nested嵌套更新

用户修改归属某条知识产权的某个annex文档

public void addAnnex(IntellectualEntity entity,AnnexEntity annexEntity) throws IOException {
        UpdateRequest updateRequest=new UpdateRequest("intellectual",entity.getId()+"");
        Map<String, Object> param = new LinkedHashMap<>();
        //格式化参数
        param.put("data",convertValueToMap(annexEntity));
        //ctx._source为固定写法
        StringBuffer sc = new StringBuffer(
        "int i = 0;for(LinkedHashMap item:ctx._source.annex){if(item.id == params.data.id){ctx._source.annex[i] = params.data;}i++;}"
        );
        Script script = new Script(INLINE, Script.DEFAULT_SCRIPT_LANG, sc.toString(), param);
        updateRequest.script(script);
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
    }

nested嵌套搜索结果高亮

同理先写个高亮配置器
对nested类型数据annex的多个属性加高亮,(最好不要写在普通高亮配置器里)

private static HighlightBuilder highlightBuilder2;
    static {
        highlightBuilder2 = new HighlightBuilder();
        highlightBuilder2.numOfFragments(0);
        highlightBuilder2.preTags("<font color='#e75213'>");
        highlightBuilder2.postTags("</font>");
        highlightBuilder2.highlighterType("unified");
        highlightBuilder2
                .field("annex.content")
                .field("annex.simpleContent")
                .field("applicant.userName")
                .field("applicant.outUsername")
        ;
        highlightBuilder2.requireFieldMatch(false);
    }

搜索代码nestedQuery加上
.innerHit(new InnerHitBuilder(“特别重要的名字,取名唯一,最好设置为当前搜索属性的名字,如annex.simpleContent”).setHighlightBuilder(highlightBuilder2))
如果取名重复将会报异常:如果不取名字,默认为path的名字annex,将会导致重复
这个取名还要用在高亮里面
在这里插入图片描述
代码和取名如下

boolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.simpleContent", "文本") , ScoreMode.Max).boost(2).innerHit(new InnerHitBuilder("annex.simpleContent").setHighlightBuilder(highlightBuilder)));
boolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.content", "文本") , ScoreMode.Max).boost(2).innerHit(new InnerHitBuilder("annex.content").setHighlightBuilder(highlightBuilder)));

依然是普通高亮代码,做修改

 for(SearchHit documentFields : search.getHits().getHits()){
            //sourceAsMap是不包含高亮的结果
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
            //获取嵌套命中数据
            Map<String, SearchHits> innerHits = documentFields.getInnerHits();
            //引用传参,替换nested高亮
            changeNestedHighLightMap(innerHits,sourceAsMap);
            //highlightFieldsMap是高亮的结果,获取普通高亮
            Map<String, HighlightField> highlightFieldsMap = documentFields.getHighlightFields();
            //通过getHighLightMap方法将替换普通高亮
            sourceAsMap = changeHighLightMap(sourceAsMap,highlightFieldsMap);
            //因为es存的时间设置为long时间戳类型,需要转化
            if(sourceAsMap.get("applyTime")!=null){
                sourceAsMap.put("applyTime",new Date(Long.parseLong(sourceAsMap.get("applyTime")+"")));
            }
            if(sourceAsMap.get("endTime")!=null){
                sourceAsMap.put("endTime",new Date(Long.parseLong(sourceAsMap.get("endTime")+"")));
            }
            //打印分值
            //存入list
            list.add(sourceAsMap);
        }

changeNestedHighLightMap方法

private static void changeNestedHighLightMap(Map<String, SearchHits> innerHits, Map<String, Object> sourceAsMap) {
        //这里innerHits.get("annex.content")就是条件构造时取的对应的名字
        SearchHit[] annexes = innerHits.get("annex.content").getHits();
        if(annexes!=null){
            for(SearchHit searchHit:annexes){
                int offset = searchHit.getNestedIdentity().getOffset();//高亮命中的数组的下标
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                List<Map<String,Object>> lm=(List<Map<String,Object>>)sourceAsMap.get("annex");
                Map<String, Object> map = lm.get(offset);
                HighlightField content = highlightFields.get("annex.content");
                if(content!=null){
                    map.put("content", content.fragments()[0].string());
                }
                lm.set(offset,map);
            }
        }
        这里innerHits.get("annex.simpleContent")就是条件构造时取的对应的名字
        SearchHit[] annexesSimpleContent = innerHits.get("annex.simpleContent").getHits();
        if(annexesSimpleContent!=null){
            for(SearchHit searchHit:annexesSimpleContent){
                int offset = searchHit.getNestedIdentity().getOffset();//高亮命中的数组的下标
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                List<Map<String,Object>> lm=(List<Map<String,Object>>)sourceAsMap.get("annex");
                Map<String, Object> map = lm.get(offset);
                HighlightField simpleContent = highlightFields.get("annex.simpleContent");
                if(simpleContent!=null){
                    map.put("simpleContent", simpleContent.fragments()[0].string());
                }
                lm.set(offset,map);
            }
        }
    }

图解结果json解释nested高亮

{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.1507283,
    "hits": [
      {
        "_index": "intellectual",
        "_type": "_doc",
        "_id": "1",
        "_score": 1.1507283,
        "_source": {
          "keycode": "keycode-1",
          "name": "无痛更新2",
          "id": 1,
          "applyTime": 1645442231033,
          "annex": [
            {
              "size": null,
              "createTime": null,
              "filePath": null,
              "apId": null,
              "name": null,
              "simpleContent": null,
              "annexs": null,
              "id": 1,
              "type": null,
              "isLogimg": null,
              "content": "文本=====dddd"
            }
          ]
        },
        ==========以上是原数据
        ==========以下是nested数据并携带高亮数据
        "inner_hits": {
          "annex": {
            "hits": {
              "total": {
                "value": 1,
                "relation": "eq"
              },
              "max_score": 0.5753642,
              "hits": [
                {
                  "_index": "intellectual",
                  "_type": "_doc",
                  "_id": "1",
                  "_nested": {
                    "field": "annex",
                    "offset": 0 ==========nested数组命中下标
                  },
                  "_score": 0.5753642,
                  "_source": {
                    "size": null,
                    "createTime": null,
                    "filePath": null,
                    "apId": null,
                    "name": null,
                    "simpleContent": null,
                    "annexs": null,
                    "id": 1,
                    "type": null,
                    "isLogimg": null,
                    "content": "文本=====dddd"
                  },
                  "highlight": {
                    "annex.content": [
                    ==========这里需要取出来替换原数据下标=offset的原数据
                      "<font color='#e75213'>文</font><font color='#e75213'>本</font>=====dddd"
                    ]
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

可——叹——落叶飘零

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

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

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

打赏作者

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

抵扣说明:

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

余额充值