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);
    }
相关推荐
孤水寒月1 小时前
Git忽略文件.gitignore
git·elasticsearch
LKAI.8 小时前
搭建Elastic search群集
linux·运维·elasticsearch·搜索引擎
WTT001110 小时前
2024楚慧杯WP
大数据·运维·网络·安全·web安全·ctf
云云32115 小时前
怎么通过亚矩阵云手机实现营销?
大数据·服务器·安全·智能手机·矩阵
新加坡内哥谈技术15 小时前
苏黎世联邦理工学院与加州大学伯克利分校推出MaxInfoRL:平衡内在与外在探索的全新强化学习框架
大数据·人工智能·语言模型
Data-Miner15 小时前
经典案例PPT | 大型水果连锁集团新零售数字化建设方案
大数据·big data
lovelin+v1750304096615 小时前
安全性升级:API接口在零信任架构下的安全防护策略
大数据·数据库·人工智能·爬虫·数据分析
道一云黑板报16 小时前
Flink集群批作业实践:七析BI批作业执行
大数据·分布式·数据分析·flink·kubernetes
节点。csn16 小时前
flink集群搭建 详细教程
大数据·服务器·flink