从零搭建 ES 搜索服务(五)搜索结果高亮
一、前言
在实际使用中搜索结果中的关键词前端通常会以特殊形式展示,比如标记为红色使人一目了然。我们可以通过 ES 提供的高亮功能实现此效果。
二、代码实现
前文查询是通过一个继承 ElasticsearchRepository 的接口实现的,但是如果要实现高亮,这种方式就满足不了了,这里我们需要通过 ElasticsearchTemplate 来完成。
2.1 注入 ElasticsearchTemplate
① ElasticsearchTemplate 类简介
public class ElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
...省略其余部分...
}
从上述源码中可以看到 ElasticsearchTemplate 实现了 ApplicationContextAware 接口,表明这个类是被 Spring 管理的,可以直接注入使用。
② 业务实现类注入 ElasticsearchTemplate
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
2.2 查询对象指定高亮字段
在构建查询对象时需要指定高亮字段,通过 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();
}
2.3 自定义 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;
}
}
}
2.4 获取返回结果
① 返回对象增加高亮字段
@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 搜索服务(五)搜索结果高亮的更多相关文章
- 高德地图-搜索服务-POI搜索
高德地图-搜索服务-POI搜索 之前公司项目收货地址仿饿了么的收货地址,结果发现自己实现的关键字搜索和周边搜索,搜索到的poi列表跟饿了么的并不完全一样,后来考虑了下,应该是搜索的范围.类型之类的设置 ...
- 【高德地图API】从零开始学高德JS API(四)搜索服务——POI搜索|自动完成|输入提示|行政区域|交叉路口|自有数据检索
原文:[高德地图API]从零开始学高德JS API(四)搜索服务——POI搜索|自动完成|输入提示|行政区域|交叉路口|自有数据检索 摘要:地图服务,大家能想到哪些?POI搜素,输入提示,地址解析,公 ...
- 从零搭建 ES 搜索服务(二)基础搜索
一.前言 上篇介绍了 ES 的基本概念及环境搭建,本篇将结合实际需求介绍整个实现过程及核心代码. 二.安装 ES ik 分析器插件 2.1 ik 分析器简介 GitHub 地址:https://git ...
- 从零搭建ES搜索服务(一)基本概念及环境搭建
一.前言 本系列文章最终目标是为了快速搭建一个简易可用的搜索服务.方案并不一定是最优,但实现难度较低. 二.背景 近期公司在重构老系统,需求是要求知识库支持全文检索. 我们知道普通的数据库 like ...
- 【高德地图API】从零開始学高德JS API(四)搜索服务——POI搜索|自己主动完毕|输入提示|行政区域|交叉路口|自有数据检索
地图服务.大家能想到哪些?POI搜素,输入提示,地址解析,公交导航,驾车导航,步行导航,道路查询(交叉口),行政区划等等.假设说覆盖物Marker是地图的骨骼,那么服务,就是地图的气血. 有个各种各样 ...
- 从零搭建 ES 搜索服务(六)相关性排序优化
一.前言 上篇介绍了搜索结果高亮的实现方法,本篇主要介绍搜索结果相关性排序优化. 二.相关概念 2.1 排序 默认情况下,返回结果是按照「相关性」进行排序的--最相关的文档排在最前. 2.1.1 相关 ...
- 从零搭建 ES 搜索服务(三)同义词搜索
一.前言 上篇介绍了 ES 的基础搜索,能满足我们基本的需求,然而在实际使用中还可能希望搜索「番茄」能将包含「西红柿」的结果也罗列出来,本篇将介绍如何实现同义词之间的搜索. 二.安装 ES 同义词插件 ...
- 从零搭建 ES 搜索服务(四)拼音搜索
一.前言 上篇介绍了 ES 的同义词搜索,使我们的搜索更强大了,然而这还远远不够,在实际使用中还可能希望搜索「fanqie」能将包含「番茄」的结果也罗列出来,这就涉及到拼音搜索了,本篇将介绍如何具体实 ...
- 从零搭建一个Redis服务
前言 自己在搭建redis服务的时候碰到一些问题,好多人只告诉你怎么成功搭建,但是并没有整理过程中遇到的问题,所有楼主就花了点时间来整理下. linux环境安装redis 安装中的碰到的问题和解决办法 ...
随机推荐
- ES6 必须要用的数组Filter() 方法,不要再自己循环遍历了!!!
1,来一个最简单最常用的栗子: 获得年龄为9岁的孩子 1 let arr = [ 2 { 3 name:'小明', 4 sex:0, 5 age:9 6 }, 7 { 8 name:'小红', 9 s ...
- LeetCode(95): 不同的二叉搜索树 II
Medium! 题目描述: 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树. 示例: 输入: 3 输出: [ [1,null,3,2], [3,2,null,1], ...
- 【python】给正则匹配部分命名
可以用?P<name>的方法给正则匹配的部分命名. 例:要将<字母,数字>的部分命名为test x = "abc <haha,123> test @@&q ...
- 《剑指offer》从上往下打印二叉树
本题来自<剑指offer> 从上往下打印二叉树 题目: 从上往下打印出二叉树的每个节点,同层节点从左至右打印. 思路: 队列的思想. 先将根节点加入,当取该节点时候,依次将左右子树加入,直 ...
- Sequence Number
1570: Sequence Number 时间限制: 1 Sec 内存限制: 1280 MB 题目描述 In Linear algebra, we have learned the definit ...
- git使用diff----git-pull之后如何查看拉下来的文件有那些修改
git pull拉取 git pull对于拉下来的修改文件自动对其进行git add /rm 及git commit 操作.所以拉下来的文件有那些修改,查看的方式可把它们归结于上一次提交的比较. gi ...
- 通过iostat来查看linux硬盘IO性能|实例分析
iostat查看linux硬盘IO性能 rrqm/s: 每秒进行 merge 的读操作数目.即 delta(rmerge)/s wrqm/s: 每秒进行 merge 的写操作数目.即 delta(wm ...
- OrCAD Capture CIS 16.6 在原理图页面内放置图片
OrCAD Capture CIS 16.6 菜单:Place > Picture... 在Place Picture窗口中,文件类型选择All Files (*.*),接着选择需要插入的图片, ...
- C++ Primer 笔记——顺序容器
1.标准库中定义了一些顺序容器,所有顺序容器都提供了快速顺序访问元素的能力. 2.如果容器的元素类型没有默认构造函数,那么在构造这个容器的时候不能只指定这个容器的数目,因为没有办法默认构造这些元素. ...
- VMware 虚拟机 Ubuntu 系统执行 ifconfig 命令 eth0没有IP地址(intet addr、Bcast、Mask) 解决:UP BROADCAST MULTICAST 问题
VMware 虚拟机 ifconfig没有net_addr地址.Bcast.Mask的解决方法 使用时间长的虚拟机,会莫名其妙的连接不上网 在终端中,使用ifconfig命令查看Ubuntu系统的IP ...