ES检索结果高亮显示JAVA以及Kibana实现

java 复制代码
/**
 * 查询接口
 *
 * @param searchReqVO
 */
public EsSearchPageInfoResVO guessYouWantListForClient(EsSearchRequestVO searchReqVO) {
    BaseInfo baseInfo = getApp();
    List<Long> catalogues = getAccesses();
    EsSearchPageInfoResVO result = new EsSearchPageInfoResVO();
    SearchRequest request = new SearchRequest();
    CountRequest countRequest = new CountRequest();
    countRequest.indices(INDEX_NAME);
    request.indices(INDEX_NAME);
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<em style='color: red'>");
    highlightBuilder.postTags("</em>");
    highlightBuilder.field("question_info");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    boolQueryBuilder.must(QueryBuilders.matchQuery("base_info_id", baseInfo.getId()));
    int shouldCount = 0;
    if (!StringUtils.isEmpty(searchReqVO.getSearchText())) {
        shouldCount++;
        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("question_info", searchReqVO.getSearchText()));
        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("answer_info", searchReqVO.getSearchText()));
        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("keyword", searchReqVO.getSearchText()));
        boolQueryBuilder.should(QueryBuilders.matchQuery("question_info", searchReqVO.getSearchText()));
        boolQueryBuilder.should(QueryBuilders.matchQuery("answer_info", searchReqVO.getSearchText()));
        boolQueryBuilder.should(QueryBuilders.matchQuery("keyword", searchReqVO.getSearchText()));
    }
    boolQueryBuilder.minimumShouldMatch(shouldCount);
    countRequest.query(boolQueryBuilder);
    //设置分页 from:页码,(当前页-1)*每页条数
    searchSourceBuilder.from(searchReqVO.getRows() * (searchReqVO.getPage() - 1));
    searchSourceBuilder.size(searchReqVO.getRows());
    searchSourceBuilder.query(boolQueryBuilder);
    searchSourceBuilder.highlighter(highlightBuilder);


    //未输入模糊搜索内容时默认按更新时间排序、输入则默认按es相似度分值排序
    if (StringUtils.isEmpty(searchReqVO.getSearchText())) {
        searchSourceBuilder.sort("update_timestamp", SortOrder.DESC);
    }
    request.source(searchSourceBuilder);
    SearchResponse searchResponse = null;
    CountResponse countResponse = null;
    List<EsSearchResponseVO> resultList = new ArrayList<>();
    try {
        countResponse = highLevelClient.count(countRequest, RequestOptions.DEFAULT);
        Long totalCount = countResponse.getCount();
        result.setTotal(totalCount);
        searchResponse = highLevelClient.search(request, RequestOptions.DEFAULT);
        SearchHit[] searchHits = searchResponse.getHits().getHits();
        for (SearchHit searchHit : searchHits) {
            //原理就是用es自动查找出来的hightlight字段值替换正常检索出来的值
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightTitle = highlightFields.get("question_info");//注意是数组
            Map<String, Object> sourceMap = searchHit.getSourceAsMap();
            if(highlightTitle != null){
                Text[] fragments = highlightTitle.getFragments();
                if(fragments != null && fragments.length > 0){
                    //替换(fargment[0]是Text类型的)
                    sourceMap.replace("question_info", fragments[0].toString());
                }
            }
            ESQuestionAnswerVersionDTO esResult = JSON.parseObject(JSON.toJSONString(sourceMap), ESQuestionAnswerVersionDTO.class);
            EsSearchResponseVO vo = new EsSearchResponseVO();
            vo.setQuestionInfo(esResult.getQuestion_info());
            vo.setKnowledgeId(esResult.getKnowledge_id());
            vo.setId(esResult.getId());
            vo.setBaseInfoId(esResult.getBase_info_id());
            resultList.add(vo);
        }
    } catch (Exception e) {
        log.info("联想搜索知识失败,搜索条件: ", JSONUtil.toJsonStr(searchReqVO));
        Traces.recordException(e);
    }
    result.setRows(resultList);
    return result;
}

对比做了高亮前后的结果返回:

高亮前:

高亮后:

可以看到加入高亮的代码之后返回的json串命中的关键字被套了一层<em style='color: red'>xxx</em>标签,也就是我们前置设置的preTags与postTags;

当然hightlight本身支持多个字段高亮,java代码实现只要设置多个

java 复制代码
highlightBuilder.field("aaaa");

highlightBuilder.field("bbb");

...

后续查询出结果之后挨个全部替换成hightlight的结果即可。


翻译成es的kibana语句如下:

bash 复制代码
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 22.00881,
    "hits" : [
      {
        "_index" : "knowledge_question_answer",
        "_type" : "_doc",
        "_id" : "12494",
        "_score" : 22.00881,
        "_source" : {
          "id" : 12494,
          "question_info" : "香肠/腊肠/金字火腿常见问题",
          "answer_info" : "万有全广式香肠蒸出来口感很粉,&nbsp; 是面粉放多了吗",
          "keyword" : "香肠发酸,香肠,腊肠,金字火腿,火腿,金字金华香肠,腊肠发酸,万有全广式香肠"
        },
        "highlight" : {
          "question_info" : [
            "<em style='color: red'>香肠</em>/腊肠/金字火腿常见问题"
          ]
        }
      }
    ]
  }
}

这里只设置了一个字段高亮,只要该字段有匹配到的关键字就会被放到结果集的高亮那一栏中。结果如下:

bash 复制代码
GET /knowledge_question_answer/_doc/_search
{
  "from": 0,
  "size": 20,
  "query": {
    "bool": {
      "should": [
        //查询条件忽略
        ...
      ],
      "adjust_pure_negative": true,
      "minimum_should_match": "1",
      "boost": 1
    }
  },
  "highlight": {
    "pre_tags": [
      "<em style='color: red'>"
    ],
    "post_tags": [
      "</em>"
    ],
    "fields": {
      "question_info": {},
      "answer_info": {},
      "keyword": {}
    }
  }
}

多个高亮查询结果如下:

bash 复制代码
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 22.00881,
    "hits" : [
      {
        "_index" : "knowledge_question_answer",
        "_type" : "_doc",
        "_id" : "12494",
        "_score" : 22.00881,
        "_source" : {
          "id" : 12494,
          "question_info" : "香肠/腊肠/金字火腿常见问题",
          "answer_info" : "万有全广式香肠蒸出来口感很粉,&nbsp; 是面粉放多了吗",
          "keyword" : "香肠发酸,香肠,腊肠,金字火腿,火腿,金字金华香肠,腊肠发酸,万有全广式香肠"
        },
        "highlight" : {
          "answer_info" : [
"万有全广式<em style='color: red'>香肠</em>蒸出来口感很粉,&nbsp; 是面粉放多了吗?"
          ],
          "question_info" : [
            "<em style='color: red'>香肠</em>/腊肠/金字火腿常见问题"
          ],
          "keyword" : [
            "<em style='color: red'>香肠</em>发酸,<em style='color: red'>香肠</em>,腊肠,金字火腿,火腿,金字金华<em style='color: red'>香肠</em>,腊肠发酸,万有全广式<em style='color: red'>香肠</em>"
          ]
        }
      }
    ]
  }
}

可以看到,多个field只要出现检索词"香肠"的地方 都被套上了前置后置的标签,展示在前端页面也就又了高亮显示的效果。

相关推荐
极小狐16 分钟前
从复制粘贴到可复用组件:极狐GitLab CI/CD 组件目录实战
java·ci/cd·gitlab·devops·组件化
麻瓜code21 分钟前
【Agent】Spring AI RAG 实战:本地向量库 + 云端知识库
java·人工智能·spring
小刘在重生~40 分钟前
Java 集合|Collection、List、ArrayList、LinkedList、泛型、Collections 工具类
java·数据结构·list
我不会起名字3221 小时前
一天一道算法题(29):单调栈
java·数据结构·python·算法·leetcode·golang·单调栈
XR1234567881 小时前
医院移动医护零漫游无线网络选型指南
java·后端·struts
干到60岁退休的码农1 小时前
13.构建登录接口响应数据
java·spring boot·mybatis
水巷石子1 小时前
学习langChain4j的第二天,体验springBoot中的starter
java·spring boot·学习·spring·langchain4j
ly76891 小时前
Spring Bean生命周期全流程:从BeanDefinition到销毁
java·后端·spring·bean生命周期·beandefinition·初始化回调
程序员梅雨2 小时前
微服务学习最终篇: Elasticsearch
学习·elasticsearch·微服务