文章目录
摘要
闲的无聊,于是写了这一篇爽文,米娜桑可直接用,除非几乎不可能有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());
}
删除(根据搜索条件删除)
和更新搜索条件操作类似,结合删除操作替换DeleteRequest为DeleteByQueryRequest,相信机智的你已经会了
搜索高亮(普通高亮,空格多条件搜索)
这块代码暂时不涉及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嵌套删除
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嵌套更新
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().setHighlightBuilder(highlightBuilder2))
boolQueryBuilder.should(QueryBuilders.nestedQuery("annex",QueryBuilders.matchQuery("annex.content", "文本") , scoreMode.Max).boost(2).innerHit(new InnerHitBuilder().setHighlightBuilder(highlightBuilder2)));
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);
}
private static void changenestedHighLightMap(Map<String, SearchHits> innerHits, Map<String, Object> sourceAsMap) {
SearchHit[] annexes = innerHits.get("annex").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);
}
}
}
图解结果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"
]
}
}
]
}
}
}
}
]
}
}