一、前言

在实际使用中搜索结果中的关键词前端通常会以特殊形式展示,比如标记为红色使人一目了然。我们可以通过 ES 提供的高亮功能实现此效果。


二、代码实现

前文查询是通过一个继承 ElasticsearchRepository 的接口实现的,但是如果要实现高亮,这种方式就满足不了了,这里我们需要通过 ElasticsearchTemplate 来完成。

注入 ElasticsearchTemplate

① ElasticsearchTemplate 类简介

public class ElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
...省略其余部分...
}

从上述源码中可以看到 ElasticsearchTemplate 实现了 ApplicationContextAware 接口,表明这个类是被 Spring 管理的,可以直接注入使用。

② 业务实现类注入 ElasticsearchTemplate

@Autowired
private ElasticsearchTemplate elasticsearchTemplate;

查询对象指定高亮字段

在构建查询对象时需要指定高亮字段,通过 withHighlightFields 方法设置。

private SearchQuery getKnowledgeSearchQuery(KnowledgeSearchParam param) {
Pageable pageable = PageRequest.of(param.getStart() / param.getSize(), param.getSize());
String knowledgeTitleFieldName = "knowledgeTitle";
String knowledgeContentFieldName = "knowledgeContent";
String preTags = "<span style=\"color:#F56C6C\">";
String postTags = "</span>";
HighlightBuilder.Field knowledgeTitleField = new HighlightBuilder.Field(knowledgeTitleFieldName).preTags(preTags).postTags(postTags);
HighlightBuilder.Field knowledgeContentField = new HighlightBuilder.Field(knowledgeContentFieldName).preTags(preTags).postTags(postTags);
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.termQuery("isDeleted", IsDeletedEnum.NO.getKey()));
queryBuilder.should(QueryBuilders.matchQuery(knowledgeTitleFieldName, param.getKeyword()));
queryBuilder.should(QueryBuilders.matchQuery(knowledgeContentFieldName, param.getKeyword()));
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(queryBuilder)
.withHighlightFields(knowledgeTitleField, knowledgeContentField)
.build();
}

自定义 ResultMapper

ResultMapper 是用于将 ES 文档转换成 Java 对象的映射类,因为 Spring Data Elasticsearch 默认的的映射类 DefaultResultMapper 不支持高亮,因此,我们需要自定义一个 ResultMapper 。

完整代码如下:

@Slf4j
@Component
public class HighlightResultHelper implements SearchResultMapper { private static ObjectMapper objectMapper = new ObjectMapper(); static {
objectMapper.setVisibility(JsonMethod.FIELD, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
} private static final Pattern SUB_FIELD_PATTERN = Pattern.compile("\\..*"); private static final String HIGHLIGHT_FIELD_SUFFIX = "Highlight"; @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
long totalHits = response.getHits().getTotalHits();
List<T> list = Lists.newArrayList();
// 获取搜索结果
SearchHits hits = response.getHits();
for (SearchHit searchHit : hits) {
if (hits.getHits().length <= 0) {
continue;
}
// 获取高亮字段Map
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
// 通过jackson将json字符串转化为对象
T item = jsonStrToObject(searchHit.getSourceAsString(), clazz);
if (Objects.isNull(item)) {
continue;
}
// 遍历高亮字段Map,将高亮字段key转化为原始字段名(title.pinyin -> title),拼接高亮文本并与原始字段名组装为一个Map
Map<String, String> highlightFieldMap = Maps.newHashMap();
for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
String key = SUB_FIELD_PATTERN.matcher(highlightField.getKey()).replaceAll(Constants.BLANK) + HIGHLIGHT_FIELD_SUFFIX;
HighlightField value = highlightField.getValue();
Text[] fragments = value.getFragments();
StringBuilder sb = new StringBuilder();
for (Text text : fragments) {
sb.append(text);
}
highlightFieldMap.put(key, sb.toString());
}
// 通过反射将高亮文本赋值到原始字段对应的高亮字段中
try {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!field.getName().contains(HIGHLIGHT_FIELD_SUFFIX)) {
continue;
}
field.setAccessible(true);
if (highlightFieldMap.containsKey(field.getName())) {
field.set(item, highlightFieldMap.get(field.getName()));
} else {
field.set(item, searchHit.getSource().get(field.getName().replace(HIGHLIGHT_FIELD_SUFFIX, Constants.BLANK)));
}
}
} catch (Exception e) {
e.printStackTrace();
}
list.add(item);
}
return new AggregatedPageImpl<>(list, pageable, totalHits);
} private <T> T jsonStrToObject(String json, Class<T> cls) {
try {
return objectMapper.readValue(json, cls);
} catch (IOException e) {
log.error("json cant be objectTranslate to object,{}", json);
return null;
}
}
}

获取返回结果

① 返回对象增加高亮字段

@Data
@Document(indexName = "knowledge", type = "knowledge")
public class KnowledgeDO { ...省略其余部分... private String knowledgeTitleHighlight; private String knowledgeContentHighlight;
}

② 业务实现类注入 HighlightResultHelper

@Autowired
private HighlightResultHelper highlightResultHelper;

③ 获取分页结果由前文的 knowledgeRepository.search 改为 elasticsearchTemplate.queryForPage 实现,查询时指定 highlightResultHelper

Page<KnowledgeDO> page = elasticsearchTemplate.queryForPage(searchQuery, KnowledgeDO.class, highlightResultHelper);

注:测试结果展示

[
{
"id": 850,
"knowledgeTitle": "小儿腺样体肥大的孩子宜多吃什么?",
"knowledgeTitleHighlight": "小儿腺样体肥大的孩子宜多吃什么?",
"knowledgeContent": "1、饮食中要停掉一切寒凉的食物,只吃性平、性温的食物,如猪肉、鸡肉、牛肉、鸽肉、鹌鹑、鳝鱼、泥鳅、青菜、白菜、包菜、黄豆芽、土豆、韭菜、胡萝卜(一周2次)等,夏天再增加四季豆、豇豆、黄瓜、西红柿、藕、芹菜、花菜、各种菌类(菌类也偏凉适合夏天吃),水果吃新鲜时令的水果,5月份以后,新鲜水果上市了。可以吃草莓、桃子、葡萄、樱桃,秋天可以吃苹果、梨子、桔子等。\n2、每周吃2-3次红烧鳝鱼或喝鳝鱼汤,鳝鱼与其它鱼类不同,补血、补肾、抗过敏的作用明显,但不易上火,补而不燥。每周吃2次海虾,一次10只左右,7岁左右的孩子可以一次半斤,海虾就是鸡尾虾或对虾,补肾阳的作用明显,可以用来治疗慢性扁桃体炎、慢性鼻炎、慢性咽炎,与河虾的功效完全不一样。",
"knowledgeContentHighlight": "1、饮食中要停掉一切寒凉的食物,只吃性平、性温的食物,如猪肉、鸡肉、牛肉、鸽肉、鹌鹑、鳝鱼、泥鳅、青菜、白菜、包菜、黄豆芽、土豆、韭菜、胡萝卜(一周2次)等,夏天再增加四季豆、豇豆、黄瓜、<span style=\"color:#F56C6C\">西红柿</span>、藕",
"referenceCount": 0
}
]

ES检索服务搜索结果高亮的更多相关文章

  1. 从零搭建 ES 搜索服务(五)搜索结果高亮

    一.前言 在实际使用中搜索结果中的关键词前端通常会以特殊形式展示,比如标记为红色使人一目了然.我们可以通过 ES 提供的高亮功能实现此效果. 二.代码实现 前文查询是通过一个继承 Elasticsea ...

  2. 在 Angular 中实现搜索关键字高亮

    在 Angular 中,我们不应该试图直接修改 DOM 的内容,当需要更新 DOM 内容的时候,应该修改的其实是我们的数据模型,也就是 $scope 中的数据,Angular 会帮助我们将修改之后的数 ...

  3. 学习笔记CB011:lucene搜索引擎库、IKAnalyzer中文切词工具、检索服务、查询索引、导流、word2vec

    影视剧字幕聊天语料库特点,把影视剧说话内容一句一句以回车换行罗列三千多万条中国话,相邻第二句很可能是第一句最好回答.一个问句有很多种回答,可以根据相关程度以及历史聊天记录所有回答排序,找到最优,是一个 ...

  4. ElasticSearch核心知识总结(一)es的六种搜索方式和数据分析

    es的六种搜索方式 query string search GET /ecommerce/product/_search //查询所有数据 { "took": 4,//耗费几毫秒 ...

  5. TKE用户故事 | 作业帮检索服务基于Fluid的计算存储分离实践

    作者 吕亚霖,2019年加入作业帮,作业帮基础架构-架构研发团队负责人,在作业帮期间主导了云原生架构演进.推动实施容器化改造.服务治理.GO微服务框架.DevOps的落地实践. 张浩然,2019年加入 ...

  6. TKE 用户故事 - 作业帮 PB 级低成本日志检索服务

    作者 吕亚霖,2019年加入作业帮,作业帮架构研发负责人,在作业帮期间主导了云原生架构演进.推动实施容器化改造.服务治理.GO微服务框架.DevOps的落地实践. 莫仁鹏,2020年加入作业帮,作业帮 ...

  7. 微信小程序搜索并高亮关键字

    更多解读可使用博客: https://www.jianshu.com/p/86d73745e01c 实现流程:1.在文本框中输入关键字key,如"比赛",检索出比赛相关的列表key ...

  8. 【Android Developers Training】 77. 使用Wi-Fi P2P进行服务搜索

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  9. SDP服务搜索流程源码分析

    BREDR的设备 在进行配对完成之后,进行;连接之前都要进行服务的搜索,服务搜索走的流程是SDP,这篇文章就分析一下,bluedroid中SDP的代码流程,我们从配对完成的回调函数开始分析: /*** ...

  10. GATT服务搜索流程(二)

    关于bta_dm_cb.p_sec_cback,这里我们之前已经分析过,他就是bte_dm_evt ,最终调用的函数btif_dm_upstreams_evt : static void btif_d ...

随机推荐

  1. 证明: 设n阶方阵A相似于对角阵Λ, λ是A的k重特征值, 则r(λE-A)=n-k.

    命题: 设n阶方阵A相似于对角阵Λ, λ是A的k重特征值, 则r(λE-A)=n-k.证明: 由定理3.9: A~Λ <=> A有n个线性无关的特征向量, 知k重特征值λ存在k个线性无关的 ...

  2. 9组-Alpha冲刺-6/4

    一.基本情况 队名:不行就摆了吧 组长博客:https://www.cnblogs.com/Microsoft-hc/p/15546712.html 小组人数: 8 二.冲刺概况汇报 卢浩玮 过去两天 ...

  3. 【全】CSS动画大全之按钮【c】

    效果预览 代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...

  4. Linux/Go环境搭建, HelloWorld运行

    package main import "fmt" func main() { fmt.Printf("Hello,World!!!\n") } 以上是Go语言 ...

  5. Linux中级——“驱动” 控制硬件必须学会的底层知识

    驱动认知 1. 什么是驱动 驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口. 设备分类: linux系统将设备分为3类:字符设备.块设备.网络设备. 字符设备:指只能一个字节一个字节读写的 ...

  6. Win32 自绘控件按钮类

    今天学了控件的自绘,初步偿试了下,蹂躏的不行不行的,查了好多的资料,头都弄大了, 有好多还是没弄明白,只是初步实现一个按钮的基本功能,好难呀, 先看下效果: 按下状态 弹起状态 按钮2按下状态 按钮2 ...

  7. VMware Workstation Pro 开启虚拟化引擎

    摘要:想开启 VMware Workstation Pro 虚拟机上的 虚拟化 Intel VT-x/EPT 或 AMD-V/RVI 选项,却发现打不开.在网上一番搜集之后找到了解决办法. ️ 注意: ...

  8. Xmind 8思维导图(含补丁)

    Xmind 8思维导图(含补丁) 什么是思维导图? 如何下载Xmind8 Xmind 8软件简单使用 获取Xmind 8 补丁 什么是思维导图? 数据结构.电路模拟等学习路线,老师都有叫画思维导图,那 ...

  9. Mac m1 安装 scrcpy

    前提:已经安装 brew 1. 设定 HOMEBREW_BOTTLE_DOMAIN(不设定的时候 ,会遇到报错  Bottle missing, falling back to the default ...

  10. Go 必知必会:探索 Go 语言中的数组和切片深入理解顺序集合

    文末有面经共享群 在 Go 语言的丰富数据类型中,数组和切片是处理有序数据集合的强大工具,它们允许开发者以连续的内存块来存储和管理相同类型的多个元素.无论是在处理大量数据时的性能优化,还是在实现算法时 ...