如何实现 Es 全文检索、高亮文本略缩处理
如何实现 Es 全文检索、高亮文本略缩处理
- 前言
- 技术选型
- JAVA 常用语法说明
- 全文检索开发
- 高亮开发
- Es Map 转对象使用
- 核心代码 Trans 接口(支持父类属性的复杂映射)
- Trans 接口的不足
- 真实项目落地效果
前言
最近手上在做 Es 全文检索的需求,类似于百度那种,根据关键字检索出对应的文章,然后高亮显示,特此记录一下,其实主要就是处理 Es 数据那块复杂,涉及到高亮文本替换以及高亮字段截取,还有要考虑到代码的复用性,是否可以将转换代码抽离出来,提供给不同结构的索引来使用。
技术选型
像市面上有的 Spring Data,码云上面的 GVP 项目 (EasyEs)等其他封装框架。使用起来确实很方便,但是考虑到由于开源项目的不稳定性且 Es 不同版本间语法差异比较大,决定使用原生的 Api。也就是使用 RestHighLevelClient。
JAVA 常用语法说明
查时间范围内的数据 BoolQuery 里面嵌套一个 RangeQuery 即可在RangeQuery 里面指定时间范围。BoolQuery.must() 各位理解为 Mybatis 中的 eq 方法即可,必须包含的意思。
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(articleRequest.getSortType());if (StringUtils.isNotEmpty(articleRequest.getBeginTime())) {rangeQuery.gte(articleRequest.getBeginTime());}if (StringUtils.isNotEmpty(articleRequest.getEndTime())) {rangeQuery.lte(articleRequest.getEndTime());}boolQuery.must(rangeQuery);
BoolQuery.should() 方法可以理解为 OR 可包含可不包含,多字段全文检索时应用 shoud。
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.should(QueryBuilders.matchPhraseQuery(articleRequest.getKeys()[i], articleRequest.getKeyword()));
termsQuery 字符精确匹配
QueryBuilders.termsQuery()
字符短句匹配,字符不会进行分词
QueryBuilders.matchPhraseQuery()
分词匹配
QueryBuilders.multiMatchQuery()
全文检索开发
核心代码如下
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();if (StringUtils.isNotEmpty(articleRequest.getKeyword())) {for (int i = 0; i < articleRequest.getKeys().length; i++) {//根据短句匹配boolQuery.should(QueryBuilders.matchPhraseQuery(articleRequest.getKeys()[i], articleRequest.getKeyword()));}}
高亮开发
里面可以指定高亮的字段,以及高亮前缀,尾缀,API的调用,直接 copy 就行
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().highlighter(new HighlightBuilder().requireFieldMatch(false).field("author").field("title").field("body").field("attachments.filename").preTags(EsConstant.HIGHT_PREFIX).postTags(EsConstant.HIGHT_END).fragmentSize(800000)//下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等;.numOfFragments(0)).query(boolQuery).from(articleRequest.getPage() - 1).size(articleRequest.getSize())
Es Map 转对象使用
由于索引结构是已 ArticleResponse 格式存储的,查询的时候也需将的得到 SourceAsMap 转换成 ArticleResponse 格式,核心逻辑我都封装到 Trans 接口了。利用反射实现的,当然也可以用其他技术实现,例如 MapStruct 在编译期间就自动生成对应的 get、set 方法,比反射效率高点,毕竟反射是运行期间的属性映射!!!!
SearchHits hits = restHighLevelClient.search(new SearchRequest().indices(indexname).source(searchSourceBuilder)).getHits();for (SearchHit hit : hits) {result.add(new ArticleResponse().trans(hit.getSourceAsMap(),hit.getHighlightFields(),Collections.singletonList("attachments.filename")));
使用的话只需让 ArticleResponse 类实现 Trans 接口,即可调用里面的 trans 方法。
核心代码 Trans 接口(支持父类属性的复杂映射)
主要逻辑就是挨个拿到本身、然后递归获取父类的所有字段名称、字段类型放到一个 Map(nameTypeMap) 中,然后遍历 SourceAsMap 挨个进行字段类型匹配校验,如果是 String 类型直接进行反射填充属性。
非 String 类型,进行类型转换然后再进行属性填充。
以及高亮字段文本略缩的处理,主要就是用了下 Jsoup 中去除 Html 标签的 Api,本来想着让前端自己去找插件看能不能处理下的,无奈说处理不了,想了个取巧的方法,高亮标签我用特殊字符,然后去除所有的 html 标签后,我的特殊字符还存在,之后将特殊字符再次替换回高亮 Html 标签,这样就得到了只存在我自定义高亮 Html 标签的一段文本了,同时高亮标签里面我塞了一个 id,之后根据高亮标签中的 id 截取字符即可,即可实现文本略缩的效果,同事直呼秒啊哈哈哈哈
/*** map 转对象* author:zzh*/
public interface Trans<T> {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Class getTargetClass();/*** 逻辑写的太多了,可以搞几个抽象类抽分功能* @param SourceAsMap 原始数据* @param highlightFieldsSource 高亮数据* @param highLightFields 高亮字段*/default Object trans(Map<String, Object> SourceAsMap, Map<String, HighlightField> highlightFieldsSource, List<String> highLightFields) throws IntrospectionException, InstantiationException, IllegalAccessException {Object o = getTargetClass().newInstance();Class tclass = getTargetClass();HashMap<String, Class> nameTypeMap = new HashMap<>();//找到父类的所有字段do {Arrays.stream(tclass.getDeclaredFields()).forEach(field -> {field.setAccessible(true);//key:字段名称,value:字段类型nameTypeMap.put(field.getName(), field.getType());});tclass = tclass.getSuperclass();} while (!tclass.equals(Object.class));PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();Arrays.stream(propertyDescriptors).forEach(propertyDescriptor -> {if (!"targetClass".equals(propertyDescriptor.getName()) && !Objects.isNull(SourceAsMap.get(propertyDescriptor.getName()))) {try {Method writeMethod = propertyDescriptor.getWriteMethod();if (null != writeMethod) {if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {writeMethod.setAccessible(true);}Object sourceValue = SourceAsMap.get(propertyDescriptor.getName());//父类以及自己所有字段类型Class aClass = nameTypeMap.get(propertyDescriptor.getName());//String 类型以及高亮直接赋值if (sourceValue.getClass().equals(aClass)) {HighlightField highlightObject = highlightFieldsSource.get(propertyDescriptor.getName());//如果高亮字段是 body,为了避免高亮文本处于文章末尾搜索页显示不到的问题,因此采用截取字符串将高亮字段偏移至前面if ("body".equals(propertyDescriptor.getName()) && null != highlightObject) {String highlightString = highlightObject.getFragments()[0].toString();//去除所有 html 标签,并将自定义高亮前缀替换 span 标签,这样就实现了只保留高亮标签的目的了highlightString = Jsoup.parse(highlightString).body().text().replaceAll(EsConstant.HIGHT_PREFIX, EsConstant.HIGHT_PREFIX_HTML).replaceAll(EsConstant.HIGHT_END, EsConstant.HIGHT_END_HTML);//高亮字段前 50 个字到文章末尾highlightString = highlightString.substring((highlightString.indexOf(EsConstant.HIGHT_HTML_ID) - EsConstant.HIGHT_SIZE) < 0? 0 : (highlightString.indexOf(EsConstant.HIGHT_HTML_ID) - EsConstant.HIGHT_SIZE));writeMethod.invoke(o, highlightObject != null ? highlightString : SourceAsMap.get(propertyDescriptor.getName()));} else {//非 body 的其他高亮字段正常替换高亮文本writeMethod.invoke(o, highlightObject != null ? highlightObject.getFragments()[0].toString().replaceAll(EsConstant.HIGHT_PREFIX, EsConstant.HIGHT_PREFIX_HTML).replaceAll(EsConstant.HIGHT_END, EsConstant.HIGHT_END_HTML) : SourceAsMap.get(propertyDescriptor.getName()));}}/*** 类型不一致强转,这里可以搞个策略模式优化优化*/else {if (aClass.equals(Date.class)) {Date parse = simpleDateFormat.parse(String.valueOf(SourceAsMap.get(propertyDescriptor.getName())));writeMethod.invoke(o, parse);}if (aClass.equals(Integer.class)) {writeMethod.invoke(o, Integer.valueOf(String.valueOf(SourceAsMap.get(propertyDescriptor.getName()))));}if (aClass.equals(Long.class)) {writeMethod.invoke(o, Long.valueOf(String.valueOf(SourceAsMap.get(propertyDescriptor.getName()))));}if (aClass.equals(List.class)) {ArrayList<Map<String, Object>> oraginSources = (ArrayList<Map<String, Object>>) SourceAsMap.get(propertyDescriptor.getName());//复杂对象高亮字段映射if (null != oraginSources && 0 != highlightFieldsSource.size()) {for (int i = 0; i < oraginSources.size(); i++) {for (int j = 0; j < highLightFields.size(); j++) {try {if (highlightFieldsSource.containsKey(highLightFields.get(j))) {oraginSources.get(i).put(highLightFields.get(j).split("\\.")[1],highlightFieldsSource.get(highLightFields.get(j)).getFragments()[j].toString().replaceAll(EsConstant.HIGHT_PREFIX, EsConstant.HIGHT_PREFIX_HTML).replaceAll(EsConstant.HIGHT_END, EsConstant.HIGHT_END_HTML));}} catch (Exception e) {e.printStackTrace();}}}}writeMethod.invoke(o, oraginSources);}if (aClass.equals(int.class)) {writeMethod.invoke(o, Integer.parseInt(String.valueOf(SourceAsMap.get(propertyDescriptor.getName()))));}}} else throw new RuntimeException(propertyDescriptor.getName() + "~ writeMethod is null!!!!!");} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}});return o;}}
Trans 接口的不足
追求极至代码解耦的人,里面的类型转换的代码可以搞个策略模式优化优化。Trans 接口定义一个就好,可以搞几个抽象类,譬如专门处理高亮文本的抽象类、不涉及到嵌套对象的抽象类转换、以及通用转换抽象类出来,写多了感觉自己再写源码了,那些搞开源项目的还有一些主流框架的源码不都是这么干的,但是需要花费一定的精力去写,笔者比较懒,下完班只想回家美美的打打游戏,追追剧,就点到为止了。
真实项目落地效果
复杂对象高亮字段替换效果
相关文章:

如何实现 Es 全文检索、高亮文本略缩处理
如何实现 Es 全文检索、高亮文本略缩处理 前言技术选型JAVA 常用语法说明全文检索开发高亮开发Es Map 转对象使用核心代码 Trans 接口(支持父类属性的复杂映射)Trans 接口的不足真实项目落地效果 前言 最近手上在做 Es 全文检索的需求,类似于…...

Netty(四)NIO-优化与源码
Netty优化与源码 1. 优化 1.1 扩展序列化算法 序列化,反序列化主要用于消息正文的转换。 序列化:将java对象转为要传输对象(byte[]或json,最终都是byte[]) 反序列化:将正文还原成java对象。 //java自带的序列化 // 反序列化 b…...

我的创业之路:我为什么选择 Angular 作为前端的开发框架?
我是一名后端开发人员,在上班时我的主要精力集中在搜索和推荐系统的开发和设计工作上,我比较熟悉的语言包括java、golang和python。对于前端技术中typescript、dom、webpack等流行的框架和工具也懂一些。目前,已成为一名自由职业者࿰…...

阿里云服务器ECS是什么?云服务器详细介绍
阿里云服务器ECS英文全程Elastic Compute Service,云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务,阿里云提供多种云服务器ECS实例规格,如经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等,阿里云服务器网分享阿…...

深入了解快速排序:原理、性能分析与 Java 实现
快速排序(Quick Sort)是一种经典的、高效的排序算法,被广泛应用于计算机科学和软件开发领域。本文将深入探讨快速排序的工作原理、步骤以及其在不同情况下的性能表现。 什么是快速排序? 快速排序是一种基于分治策略的排序算法&am…...
[晕事]今天做了件晕事22;寻找99-sysctl.conf; systemd
这个文件,使用ls命令看不出来是一个链接。 然后满世界的找这个文件怎么来的,后来发现是systemd里的一个文件。 从systemd的源文件里也没找到相关的文件信息。 最后把这个rpm安装包下载下来,才找到这个文件原来是一个链接 #ll /etc/sysctl.d/9…...
2578. 最小和分割
给你一个正整数 num ,请你将它分割成两个非负整数 num1 和 num2 ,满足: num1 和 num2 直接连起来,得到 num 各数位的一个排列。 换句话说,num1 和 num2 中所有数字出现的次数之和等于 num 中所有数字出现的次数。num1…...

Mybatis mapper报错:Class not found: org.jboss.vfs.VFS
报错 Logging initialized using class org.apache.ibatis.logging.stdout.StdOutImpl adapter. Class not found: org.jboss.vfs.VFS JBoss 6 VFS API is not available in this environment. Class not found: org.jboss.vfs.VirtualFile VFS implementation org.apache.iba…...

ARM作业1
三盏灯流水 代码 .text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0X50000A28 LDR R1,[R0] 从r0为起始地址的4字节数据取出放在R1 ORR R1,R1,#(0x3<<4) 第4位设置为1 STR R1,[R0] 写回2.设置PE10管…...
leetcode 502. IPO
假设 力扣(LeetCode)即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司,力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后…...
[软考中级]软件设计师-计算机网络
网络设备 物理层 物理层不能隔离广播域和冲突域 中继器,集线器 集线器可看成是特殊的多路中继器 数据链路层 可以隔离冲突域不能隔离广播域 网桥,交换机 交换机是多端口的网桥 网络层 可以隔离广播域和冲突域 路由器 应用层 网关 协议簇 …...

Linux搭建我的世界MC服务器 【Minecraft外网联机教程】
目录 前言 1. 安装JAVA 2. MCSManager安装 3.局域网访问MCSM 4.创建我的世界服务器 5.局域网联机测试 6.安装cpolar内网穿透 7. 配置公网访问地址 8.远程联机测试 9. 配置固定远程联机端口地址 9.1 保留一个固定tcp地址 9.2 配置固定公网TCP地址 9.3 使用固定公网…...
APISIX 中ETCD 的问题
1. 问题1 : Error: client: etcd cluster is unavailable or misconfigured; error #0: client: endpoint http://etcd:2379 exceeded header timeout error #0: client: endpoint http://etcd:2379 exceeded header timeout 修改APISIX config ETCD_ADVERTISE_CL…...

SSH版本信息可被获取
漏洞描述 Name SSH版本信息可被获取 Description SSH服务允许远程攻击者获得ssh的具体信息,如版本号等等。这可能为攻击者发动进一步攻击提供帮助。 CVE No. CVE-1999-0634 分析结果 该问题不属于漏洞,不存在安全风险。SSH协议是一种安全协议&am…...

android 修改输出apk的包名
一,打包方式使用IDE菜单选项 二、在app级别的build.gradle下配置: static def releaseTime() {return new Date().format("yyyyMMdd.kkmm", TimeZone.getTimeZone("GMT8")) }android.applicationVariants.all { variant ->print…...

uni-app:文本超出部分用省略号表示
效果 前 后 核心代码 white-space: nowrap; /* 强制不换行 */ text-overflow: ellipsis; /* 超过部分省略号代替 */ overflow: hidden; /* 必须同时设置overflow:hidden才能生效 */ 完整代码 <template><view><view class"all_style"><view c…...

轻松实现视频、音频、文案批量合并,享受批量剪辑的便捷
在日常生活中,我们经常会需要将多个视频、音频和文案进行合并剪辑,以制作出符合我们需求的短视频。然而,这个过程通常需要花费大量的时间和精力。幸运的是,现在有一款名为“固乔智剪软件”的工具可以帮助我们轻松完成这个任务。 首…...
Spring Boot、Nacos配置文件的优先级
在标准的 SpringBoot 应用中,本地配置加载顺序如下: 本地 bootstrap 配置,先于 application 配置加载。不带 profile 的配置,先于带 profile 的配置加载。xxx.yaml 先于 xxx.properties 加载。本地配置先于 nacos 配置中心加载。…...
GO脚本-模拟鼠标键盘
01GetCoordinate 获取坐标 package mainimport ("github.com/go-vgo/robotgo" )func main() {// 获取当前鼠标所在的位置x, y : robotgo.GetMousePos()println(x:, x, y:, y)}02GetColor 获取坐标颜色 package mainimport ("fmt&quo…...
Ubuntu设置SSH
在Ubuntu上通过SSH服务远程连接其他机器 首先通过以下命令判断是否安装SSH服务: ssh localhost如果出现 ssh: connect to host localhost port 22: Connection refused 则表示还未安装SSH。 通过以下命令安装SSH: sudo apt update sudo apt install…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...