ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理

ElasticSearch nested 字段多关键字搜索,高亮全部匹配关键字的处理

环境介绍

ElasticSearch 版本号: 6.7.0

需求说明

用户会传入多个关键字去ES查询ElasticSearch nested 字段 的多个字段,要求在返回的结果中被搜索的字段需要高亮所有匹配的关键字。例如同时通过上海策划关键字,再 工作经历的列表中的工作内容公司名称中搜索。如果有人员的工作经历中这两个关键字上海策划都可以匹配到,那么返回的结果中同时高亮这上海策划关键字

分析调研

基础的ElasticSearch nested 字段的高亮实现搜可以参考https://blog.csdn.net/weixin_48990070/article/details/120342597 这篇笔记。

问题点1

对于同一个nested 字段支持在一个nested Query 用不同的关键字来搜索,但对于should 查询只会高亮其中匹配的一个关键字,而不是全部。引入如果多关键字直接是任意满足的关系,则之后高亮匹配的其中的一个关键字,这个与不满足需求。

问题点2

那就把关键字拆分为多个nested Query ,一个关键字对应一个nested Query 。但这个方法一样可以搜索,但对于同一个nested字段的nested Query 默认的inner_hits 属性只能出现在一个nested Query中,不允许同一个nested字段的不同nested Query 都指定inner_hits ,如果一定要这么做,那么就会得到一个查询错误的提示,提示如下:

json 复制代码
        "reason": {
          "type": "illegal_argument_exception",
          "reason": "[inner_hits] already contains an entry for key [trackRecordList]"
        }

如果只在一个关键字的nested Query指定inner_hits,那么最终的高亮结果只会有该nested Query的高亮,还是不满足要求。

问题点3

通过AI询问得知inner_hits 有个name 属性可以解决问题点2的情况,可以通过设置inner_hits不同的name 属性值来达到对同一个nested字段用不同nested Query 来做多关键字的高亮效果,但是这样里又出现了两个新的问题。

1、inner_hits 有个name 属性值不能重复,否则一样出问题点2的错误提示。

2、高亮 结果是按照inner_hits 有个name 属性值分组展示的,不像非nested会给一个最终多个关键字都高亮的结果。

转换下问题就是:

1、要根据关键字自动生成不重复inner_hits 有个name 属性值

2、对于同字段的高亮结果,要做高亮内容的合并。

因此只要解决了上面两个问题,就可以完成业务的需求了。

最终解决方案

问题1解决方案

在将查询参数转换为ES Query语句的处理中,用Map来缓存每个nested字段的当前有几个nested Query,通过累计数量,来自动生成每个nested Query中的inner_hits 有个name 属性名,例如名称为 nested字段名+"-"+自增序号

因此就不能再使用静态方法来构建查询语句了,得用构建器了,下面就是构建器的部分实现

java 复制代码
public class EsQueryBuilder {

    // 存储嵌套字段及其累计值的映射
    private Map<String, IntAccumulator> accNestedFieldMap = new HashMap<>();
    
    // 无需嵌套高亮的字段集合
    private Set<String> noNestedHighlightFields = new HashSet<>();

    // 关键词分组列表
    private List<PageSearchKeywordGroupParameter> keywordGroupList ;
    
    // 是否开启高亮显示
    private boolean isHighlight = false;

    // 主查询构建器
    private BoolQueryBuilder mainQueryBuilder;

    //存储嵌套字段及其高亮构建器
    private Map<NestedQueryBuilder,InnerHitBuilder> nestedQueryBuilderHighlightMap = new HashMap<>();
    
    /**
     * 构造方法
     * @param keywordGroupList 搜索关键字组
     * @param isHighlight 是否高亮
     */
    public EsQueryBuilder(List<PageSearchKeywordGroupParameter> keywordGroupList,boolean isHighlight) {
        this.keywordGroupList = keywordGroupList;
        this.isHighlight = isHighlight;
        this.mainQueryBuilder = new BoolQueryBuilder();
        //补充嵌套字段初始累加器
        EsQueryFieldEnum.getNestedFieldList().forEach(item->{
            accNestedFieldMap.put(item.getFieldConfig().getMainField(),new IntAccumulator(0));
        });
    }

	/**
     * 向当前的查询构建器中添加条件。这个方法会遍历关键字组列表(keywordGroupList)中的每一个项目,
     * 并根据是否标记为排除条件,将关键字添加到查询的必须条件(must)或者必须不条件(mustNot)中。
     * @return EsQueryBuilder 返回当前的查询构建器实例,允许链式调用。
     */
    public EsQueryBuilder addCondition() {
        keywordGroupList.forEach(item->{
            // 只处理非空关键字的项目
            if(StringUtils.isNotBlank(item.getKeyword())){
                // 根据是否为排除条件,选择添加到must或mustNot中
                if(BooleanUtils.isTrue(item.getIsExclude())){
                    mainQueryBuilder.mustNot(buildQueryBuilder(item));
                }else{
                    mainQueryBuilder.must(buildQueryBuilder(item));
                }
            }
        });
        return this;
    }

    /**
     * 为所有内容添加高亮显示条件的查询构建器。
     * 该方法遍历关键字组列表,对非排除条件的关键字进行全文搜索设置,并根据关键字是否为排除条件,添加相应的查询条件。
     * @return EsQueryBuilder 当前查询构建器实例,支持链式调用。
     */
    public EsQueryBuilder addConditionForAllContentHighlight() {
        // 遍历关键字组列表,过滤掉设置为排除条件的关键字,对剩余的关键字进行全文搜索设置
        keywordGroupList.stream()
            // 过滤掉设置为排除条件的关键字
            .filter(item->BooleanUtils.isNotTrue(item.getIsExclude()))
            .peek(item->{
                // 设置搜索类型为全文搜索,清空子类型设置
                item.setSearchType(EsQueryTypeEnum.ALL.value());
                item.setSearchSubType(null);
            })
            .forEach(item->{
                // 根据关键字是否为排除条件,添加相应的查询条件
                if(StringUtils.isNotBlank(item.getKeyword())){
                    if(BooleanUtils.isTrue(item.getIsExclude())){
                        // 如果是排除条件,则添加到must not查询条件中
                        mainQueryBuilder.mustNot(buildQueryBuilder(item));
                    }else{
                        // 如果不是排除条件,则添加到must查询条件中
                        mainQueryBuilder.must(buildQueryBuilder(item));
                    }
                }
            });
        return this;
    }


    /**
     * 嵌套字段高亮处理
     **/
    private void highlightNestedQuery() {
        if(!nestedQueryBuilderHighlightMap.isEmpty()){
            nestedQueryBuilderHighlightMap.forEach(NestedQueryBuilder::innerHit);
        }
    }



    /**
     * 为查询添加过滤条件。
     * 这个方法允许用户指定一个过滤条件,并将其应用到当前的查询构建器中。
     * @param queryBuilder 过滤条件的查询构建器。这是一个已经构建好的查询条件,将作为过滤条件添加到主查询中。
     * @return 返回当前的EsQueryBuilder实例,允许链式调用。
     */
    public EsQueryBuilder filterCondition(QueryBuilder queryBuilder) {
        // 为主查询添加过滤条件
        mainQueryBuilder.filter(queryBuilder);
        return this;
    }

    /**
     * 构建查询条件
     * @return org.elasticsearch.search.builder.SearchSourceBuilder
     */
    public SearchSourceBuilder build() {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //查询条件
        searchSourceBuilder.query(mainQueryBuilder);

        return searchSourceBuilder;
    }

    /**
     * 构建查询条件(支持高亮)
     * @return org.elasticsearch.search.builder.SearchSourceBuilder
     */
    public SearchSourceBuilder buildWithHighlight() {
        SearchSourceBuilder searchSourceBuilder = build();
        if(isHighlight){
            //补充非嵌套字段的高亮
            highlightNestedQuery();
            //非嵌套高亮
            searchSourceBuilder.highlighter(EsHighlightUtils.buildNotNestHighlightBuilder(noNestedHighlightFields));
        }
        return searchSourceBuilder;
    }


	/**
     * 构建查询构建器。
     * 该方法根据传入的参数生成一个对应的查询条件构建器,主要用于处理专家页面的搜索关键词分组参数。
     * @param parameter 搜索参数,包含需要搜索的关键词和其他搜索条件。
     * @return 返回构建好的查询条件构建器对象。
     */
    private QueryBuilder buildQueryBuilder(PageSearchKeywordGroupParameter parameter){
        // 初始化一个布尔类型的查询条件构建器,用于后续添加各种查询条件
        BoolQueryBuilder keywordQueryBuilder = new BoolQueryBuilder();
        // 根据参数生成对应的查询类型枚举,用于确定如何构建查询条件
        EsQueryTypeEnum queryTypeEnum = generateQueryTypeEnum(parameter);
        // 调用查询类型枚举中定义的添加条件处理器,处理当前搜索参数,并将其添加到查询条件构建器中
        queryTypeEnum.getAddConditionHandler().handle(this,keywordQueryBuilder,parameter.getKeyword());
        return keywordQueryBuilder;
    }

	/**
     * 根据关键词和字段枚举生成查询条件。
     * @param keyword 关键词,用于构建查询条件。
     * @param fieldEnum 字段枚举,包含字段配置信息,用于指定要查询的字段。
     * @param highlight 是否高亮处理。
     * @return org.elasticsearch.index.query.QueryBuilder 查询构建器,用于构建Elasticsearch的查询语句。
     */
    private QueryBuilder generateCondition(String keyword, EsQueryFieldEnum fieldEnum, boolean highlight){
        EsQueryFieldConfigDTO fieldConfigDTO = fieldEnum.getFieldConfig();
        // 构建基于关键词的基本查询条件
        BoolQueryBuilder boolQueryBuilder = EsQueryBuilderUtils.generateFieldQueryBuilder(keyword,
            true, fieldConfigDTO.getSearchFieldList());

        if(BooleanUtils.isTrue(fieldConfigDTO.getIsNested())){
            // 如果是嵌套类型字段,则使用NestedQueryBuilder来处理
            NestedQueryBuilder
                nestedQueryBuilder = new NestedQueryBuilder(fieldConfigDTO.getMainField(), boolQueryBuilder, ScoreMode.Avg);
            if(highlight && isHighlight){
                // 如果需要高亮显示,则为嵌套类型字段设置高亮处理
                String innerHitName = generateInnerHitName(fieldEnum);
                InnerHitBuilder innerHitBuilder = EsHighlightUtils.buildNestHighlightBuilder(innerHitName,
                    fieldConfigDTO.getSearchFieldList());
                nestedQueryBuilderHighlightMap.put(nestedQueryBuilder,innerHitBuilder);
            }
            return nestedQueryBuilder;
        }else{
            // 对于非嵌套类型字段,处理高亮显示的逻辑
            if(highlight && isHighlight){
                // 收集非嵌套类型的高亮字段
                noNestedHighlightFields.addAll(fieldConfigDTO.getSearchFieldList());
            }
            return boolQueryBuilder;
        }

    }

    /**
     * 生成嵌套查询的innerHit名称
     * @param fieldEnum
     * @return java.lang.String
     **/
     private String generateInnerHitName(EsQueryFieldEnum fieldEnum){
         IntAccumulator accumulator = accNestedFieldMap.get(fieldEnum.getFieldConfig().getMainField());
         accumulator.accumulate(1);
         return fieldEnum.getFieldConfig().getMainField()+"-"+accumulator.getValue();
     }

    /**
     * 向查询构建器中添加公司名称条件。
     * @param esQueryBuilder ES查询构建器,用于生成特定的ES查询条件。
     * @param keywordQueryBuilder 关键词查询构建器,用于组合不同的查询条件。
     * @param keyword 用户输入的关键词,用于匹配公司名称。
     */
    public static void addCompanyNameCondition(EsQueryBuilder esQueryBuilder,BoolQueryBuilder keywordQueryBuilder,
            String keyword) {
        // 根据关键词和字段类型(当前公司名称),生成查询条件,并添加到关键词查询构建器中
        keywordQueryBuilder.should(esQueryBuilder.generateCondition(keyword,EsQueryFieldEnum.CURRENT_COMPANY,true));
        // 根据关键词和字段类型(履历中的公司名称),生成查询条件,并添加到关键词查询构建器中
        keywordQueryBuilder.should(esQueryBuilder.generateCondition(keyword,EsQueryFieldEnum.TRACK_RECORD_COMPANY,true));
    }

}

其他相关代码:

定义一个适用Lambda表达式的接口

java 复制代码
/**
 * Es 搜索条件处理器
 */
@FunctionalInterface
public interface IEsQueryConditionHandler {

    /**
     * 处理Es搜索条件
     * @param esQueryBuilder
     * @param keywordQueryBuilder
     * @param keyword
     * @return void
     */
    void handle(EsQueryBuilder esQueryBuilder, BoolQueryBuilder keywordQueryBuilder,
        String keyword);
}

定义搜索字段的枚举

java 复制代码
/**
  * 专家库ES查询字段枚举
  */
public enum EsQueryFieldEnum {

    /**
     * 当前公司
     */
    CURRENT_COMPANY(10,"当前公司", EsQueryFieldConfigDTO.builder()
        .mainField("companyInfo")
        .isNested(false)
        .searchFieldList(List.of("companyInfo.companyName"))
        .build()),
        
    /**
     * 工作经历公司
     */
    TRACK_RECORD_COMPANY(20,"工作经历公司", EsQueryFieldConfigDTO.builder()
        .mainField("trackRecordList")
        .isNested(true)
        .searchFieldList(List.of("trackRecordList.companyName","trackRecordList.companyOtherName"))
        .build()),

     
     ;

     /**
      * 嵌套字段列表
      */
     private static final List<EsQueryFieldEnum> NESTED_FIELD_LIST = Stream.of(EsQueryFieldEnum.values())
         .filter(item->item.fieldConfig.getIsNested()).collect(Collectors.toList());

    EsQueryFieldEnum(Integer value, String description,
        EsQueryFieldConfigDTO fieldConfig){
        this.value =value;
        this.description = description;
        this.fieldConfig = fieldConfig;
    }
    private final Integer value;

    private final String description;

    private final EsQueryFieldConfigDTO fieldConfig;

    public Integer value() {
        return this.value;
    }

    public String getDescription() {
        return this.description;
    }

    public EsQueryFieldConfigDTO getFieldConfig() {
        return fieldConfig;
    }

    /**
     * 获取嵌套字段列表
     */
    public static List<EsQueryFieldEnum> getNestedFieldList() {
        return NESTED_FIELD_LIST;
    }


}

定义搜索类型的枚举

java 复制代码
/**
 * ES查询类型枚举
 **/
public enum EsQueryTypeEnum {
 
    /**
     * 公司
     */
    COMPANY(20,"公司", EsQueryBuilder::addCompanyNameCondition),
    
     ;

    EsQueryTypeEnum(Integer value, String description,
        IEsQueryConditionHandler addConditionHandler){
        this.value =value;
        this.description = description;
        this.addConditionHandler = addConditionHandler;
    }
    private final Integer value;

    private final String description;

    private final IEsQueryConditionHandler addConditionHandler;

    public Integer value() {
        return this.value;
    }

    public String getDescription() {
        return this.description;
    }

    public IEsQueryConditionHandler getAddConditionHandler() {
        return addConditionHandler;
    }


    public static EsQueryTypeEnum resolve(Integer statusCode) {
        for (EsQueryTypeEnum status : values()) {
            if (status.value.equals(statusCode)) {
                return status;
            }
        }
        return null;
    }

}

使用方法

java 复制代码
SearchSourceBuilder searchSourceBuilder = queryBuilder
    // 增加关键字查询条件条件
    .addCondition()
    // 组合条件过滤
    .filterCondition(EsQueryHandler.getAdvancedSearchQueryBuilder(searchParameter))
    //生成查询语句
    .build();
// 获取总条数
Integer total = EsService.countBySearch(searchSourceBuilder);

//重新生成高亮查询语句
searchSourceBuilder = queryBuilder.buildWithHighlight();
//补充排序规则
EsQueryHandler.setSearchSortRule(searchSourceBuilder,searchParameter.getSortType());
// 从第几页开始
searchSourceBuilder.from(searchParameter.getOffset());
// 每页显示多少条
searchSourceBuilder.size(searchParameter.getLimit());
//分页搜索
List<EsAllInfoDTO> allInfoList = EsService.listByPageSearch(searchSourceBuilder);

问题2解决方案

合并高亮的处理,这个问题实际就是:对于一个字符串a,存在多个字符串a1,a2,a3,并且a1,a2,a3再过滤掉<em></em> 字符后是相同的字符串。现在需要将字符串a,a1,a2,a3 合并为一个字符串fa。合并后的字符串需要满足:

1、fa过滤掉<em></em> 字符后同a相同

2、所有在a1,a2,a3<em></em>包围的子字符串,在fa同样被<em></em>包围

另外要保证一个点是原始的字符串a不能本身就有<em></em> 这些字符串,这个可以通过对数据源头进行过滤就可以了。比如使用Jsonp 过滤。

合并高亮字符串的具体的实现算法如下:

java 复制代码
/**
 * Es高亮工具类
 */
public class EsHighlightUtils {
    public static final String emBegin = "<em>";
    public static final String emEnd = "</em>";
    private static final String emRegex = "(?i)<em>|</em>";
    private static final int emBeginLen = emBegin.length();
    private static final int emEndLen = emEnd.length();


    /**
     * 将字符串数组中的字符串合并,并在特定位置添加增强标签(<em></em>)。
     * @param stringList 字符串数组,数组中所有字符串如果去除"<em>" 和"</em>"后必定是相同的字符串。
     * @return 合并后的字符串,增强了指定的字符串片段。
     */
    public static String mergeStrWithEmTags(List<String> stringList) {
        // 移除原始字符串中的所有em标签,获取干净的源字符串
        String sourceStr = stringList.get(0).replaceAll(emRegex, "");
        // 使用StringBuilder来操作源字符串,以便高效地添加em标签
        StringBuilder sourceBuilder = new StringBuilder(sourceStr);
        // 初始化一个布尔数组,用于标记哪些字符需要增强
        boolean[] emFlags = new boolean[sourceStr.length()];
        // 填充布尔数组,标记需要增强的字符位置
        fillEmFlags(stringList, emFlags);
        // 根据标记,在相应位置添加em标签
        addEmFlags(sourceBuilder, emFlags);
        return sourceBuilder.toString();
    }

    /**
     * 为给定的字符串数组中的每个字符串设置强调标志数组。
     * 该方法会查找每个字符串中所有"<em>"开头和"</em>"结尾的包围结构,
     * 并将这些包围结构在原字符串中的对应部分在标志数组中设置为true。
     *  @param stringList 字符串数组,包含需要处理的字符串。
     * @param emFlags 增强标志数组,与字符串数组对应,用于标记特定部分。
     */
    private static void fillEmFlags(List<String> stringList, boolean[] emFlags) {
        // 遍历字符串数组,为每个字符串设置强调标志
        for(int j = 0; j< stringList.size(); j++){
            String str = stringList.get(j);
            // 查找每个字符串中"<em>"的起始位置
            int beginIndex = str.indexOf(emBegin);
            int cumulativeOffset = 0;
            int noEmLen = 0;
            int endIndex = 0;
            while(beginIndex != -1){
                //计算没有增强的字符串长度
                noEmLen = endIndex>0?Math.max(beginIndex - (endIndex + emEndLen),0):beginIndex;
                // 查找"<em>"后的"</em>"位置
                endIndex = str.indexOf(emEnd,beginIndex+emBeginLen);
                if(endIndex==-1){
                    // 如果找不到结束标签,则跳出循环
                    break;
                }
                // 计算被包围的子字符串长度
                int emSubLength = endIndex - beginIndex - emBeginLen;
                // 更新累计偏移量,跳过未增强的字符串
                cumulativeOffset = cumulativeOffset+ noEmLen;
                // 将被包围的子字符串在标志数组中对应的元素设置为true
                for(int i=0;i<emSubLength;i++){
                    emFlags[cumulativeOffset + i] = true;
                }
                // 更新累计偏移量,为处理下一个"<em>"做准备
                cumulativeOffset = cumulativeOffset + emSubLength;
                // 计算下一个"<em>"标签的起始位置
                beginIndex = endIndex + emEndLen;
                // 继续查找下一个"<em>"
                beginIndex = str.indexOf(emBegin,beginIndex);

            }
        }
    }


    /**
     * 向源字符串中插入增强标签。
     * 根据给定的增强标志数组(emFlags),在源字符串(sourceBuilder)中插入开始(emBegin)和结束(emEnd)标签。
     * 当emFlags中的元素为true时,表示字符串的这个位置需要被增强
     * @param sourceBuilder 被插入标签的源字符串的StringBuilder对象。
     * @param emFlags 增强标志数组,true表示字符串的这个位置需要被增强。
     */
    private static void addEmFlags(StringBuilder sourceBuilder, boolean[] emFlags) {
        // 初始化是否开始插入标签的标志和累计偏移量
        boolean startEm = false;
        int cumulativeOffset = 0 ;
        // 遍历增强标志数组,根据标志插入相应的标签
        for (boolean emFlag : emFlags) {
            if (emFlag) {
                // 当前位置需要插入开始标签
                if (!startEm) {
                    // 第一次需要插入开始标签,进行插入操作并更新累计偏移量
                    startEm = true;
                    sourceBuilder.insert(cumulativeOffset, emBegin);
                    cumulativeOffset += emBeginLen;
                }
                // 无论是否第一次,只要需要插入开始标签,累计偏移量就需要增加
                cumulativeOffset++;
            } else {
                // 当前位置需要插入结束标签
                if (startEm) {
                    // 已经开始插入标签,进行插入操作并更新累计偏移量
                    sourceBuilder.insert(cumulativeOffset, emEnd);
                    cumulativeOffset += emEndLen;
                }
                // 标记不再插入开始标签
                startEm = false;
                // 累计偏移量增加
                cumulativeOffset++;
            }
        }
        // 如果遍历结束时正在插入开始标签,插入结束标签
        if(startEm){
            sourceBuilder.insert(cumulativeOffset,emEnd);
        }
    }




    /**
     * 构建嵌套的高亮 InnerHitBuilder
     * @param name
     * @param fields
     * @return org.elasticsearch.index.query.InnerHitBuilder
     */
    public static InnerHitBuilder buildNestHighlightBuilder(String name, Collection<String> fields) {
        if(CollectionUtils.isEmpty(fields)){
            return null;
        }
        InnerHitBuilder innerHitBuilder = StringUtils.isBlank(name)?new InnerHitBuilder():new InnerHitBuilder(name);
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(emBegin).postTags(emEnd);
        //设置高亮的方法
        highlightBuilder.highlighterType("plain");
        //设置分段的数量不做限制
        highlightBuilder.numOfFragments(0);
        for(String field:fields){
            highlightBuilder.field(field);
        }
        innerHitBuilder.setHighlightBuilder(highlightBuilder);
        return innerHitBuilder;
    }

    /**
     * 构建非嵌套的高亮 HighlightBuilder
     * @param fields
     * @return org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
     */
    public static HighlightBuilder buildNotNestHighlightBuilder(Collection<String> fields) {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(emBegin).postTags(emEnd);
        //设置高亮的方法
        highlightBuilder.highlighterType("plain");
        //设置分段的数量不做限制
        highlightBuilder.numOfFragments(0);
        for(String field:fields){
            highlightBuilder.field(field);
        }
        return highlightBuilder;
    }
}

修改https://blog.csdn.net/weixin_48990070/article/details/120342597 这篇笔记中的替换高亮处理的代码,思路为每次只合并找到的第一个高亮内容,将它和当前的原始内容合并,并将合并后的内容替换掉原始内容。重复这个动作知道所有高亮的内容都被合并到当前的原始内容中。

java 复制代码
	/**
     * 替换嵌套高亮的值
     * @param sourceObj
     * @param nestedEle
     * @param highlightEle
     * @return void
     */
    private void replaceInnerHighlightValue(JsonObject sourceObj, JsonElement nestedEle, JsonElement highlightEle){
        if(nestedEle==null || highlightEle==null){
            return ;
        }
        //获取源对象中的嵌套字段名称
        JsonObject nestedObj= nestedEle.getAsJsonObject();
        String innerFieldName = nestedObj.get("field").getAsString();
        //获取当前对象匹配的源对象中的偏移位置
        int innerFieldOffset = nestedObj.get("offset").getAsInt();

        //获取源对象
        JsonObject findSourceObj = GsonUtils.getJsonObjectForArray(sourceObj,innerFieldName,innerFieldOffset);
        if(findSourceObj==null){
            return ;
        }
        //替换高亮的部分
        log.debug("高亮的部分:{}",highlightEle);
        JsonObject highlightObj = highlightEle.getAsJsonObject();
        highlightObj.entrySet().forEach((h)->{
            //合并高亮字段对应的原值
            String highlightValue = h.getValue().getAsString();
            JsonObject currentSourceObj = findSourceObj;
            String[] keyNames = StringUtils.split(h.getKey(),".");
            //循环到倒数第二层,获取待替换字段值对象
            for(int i=0;i<keyNames.length-2;i++){
                String keyName = keyNames[i+1];
                currentSourceObj = currentSourceObj.get(keyName).getAsJsonObject();
            }
            //获取最后一层的字段名称
            String lastFieldName = keyNames[keyNames.length-1];
            //获取高亮字段对应的原值
            String sourceValue = currentSourceObj.get(lastFieldName).getAsString();
            //合并原值和高亮增强的值
            String mergedValue = EsHighlightUtils.mergeStrWithEmTags(List.of(sourceValue, highlightValue));
            //替换最后一层对象的指定字段的值
            GsonUtils.replaceFieldValue(currentSourceObj,
                lastFieldName, mergedValue);
        });
        log.debug("替换后的高亮的部分{}",findSourceObj);
    }
相关推荐
jianghx10248 小时前
Docker部署ES,开启安全认证并且设置账号密码(已运行中)
安全·elasticsearch·docker·es账号密码设置
IT小哥哥呀9 小时前
电池制造行业数字化实施
大数据·制造·智能制造·数字化·mom·电池·信息化
Xi xi xi9 小时前
苏州唯理科技近期也正式发布了国内首款神经腕带产品
大数据·人工智能·经验分享·科技
yumgpkpm9 小时前
华为鲲鹏 Aarch64 环境下多 Oracle 、mysql数据库汇聚到Cloudera CDP7.3操作指南
大数据·数据库·mysql·华为·oracle·kafka·cloudera
UMI赋能企业10 小时前
制造业流程自动化提升生产力的全面分析
大数据·人工智能
TDengine (老段)11 小时前
TDengine 数学函数 FLOOR 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
派可数据BI可视化13 小时前
商业智能BI 浅谈数据孤岛和数据分析的发展
大数据·数据库·数据仓库·信息可视化·数据挖掘·数据分析
jiedaodezhuti13 小时前
Flink性能调优基石:资源配置与内存优化实践
大数据·flink
Lx35214 小时前
Flink窗口机制详解:如何处理无界数据流
大数据
Lx35215 小时前
深入理解Flink的流处理模型
大数据