当前位置: 首页 > news >正文

如何实现 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 接口&#xff08;支持父类属性的复杂映射&#xff09;Trans 接口的不足真实项目落地效果 前言 最近手上在做 Es 全文检索的需求&#xff0c;类似于…...

Netty(四)NIO-优化与源码

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

我的创业之路:我为什么选择 Angular 作为前端的开发框架?

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

阿里云服务器ECS是什么?云服务器详细介绍

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

深入了解快速排序:原理、性能分析与 Java 实现

快速排序&#xff08;Quick Sort&#xff09;是一种经典的、高效的排序算法&#xff0c;被广泛应用于计算机科学和软件开发领域。本文将深入探讨快速排序的工作原理、步骤以及其在不同情况下的性能表现。 什么是快速排序&#xff1f; 快速排序是一种基于分治策略的排序算法&am…...

[晕事]今天做了件晕事22;寻找99-sysctl.conf; systemd

这个文件&#xff0c;使用ls命令看不出来是一个链接。 然后满世界的找这个文件怎么来的&#xff0c;后来发现是systemd里的一个文件。 从systemd的源文件里也没找到相关的文件信息。 最后把这个rpm安装包下载下来&#xff0c;才找到这个文件原来是一个链接 #ll /etc/sysctl.d/9…...

2578. 最小和分割

给你一个正整数 num &#xff0c;请你将它分割成两个非负整数 num1 和 num2 &#xff0c;满足&#xff1a; num1 和 num2 直接连起来&#xff0c;得到 num 各数位的一个排列。 换句话说&#xff0c;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

假设 力扣&#xff08;LeetCode&#xff09;即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司&#xff0c;力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限&#xff0c;它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后…...

[软考中级]软件设计师-计算机网络

网络设备 物理层 物理层不能隔离广播域和冲突域 中继器&#xff0c;集线器 集线器可看成是特殊的多路中继器 数据链路层 可以隔离冲突域不能隔离广播域 网桥&#xff0c;交换机 交换机是多端口的网桥 网络层 可以隔离广播域和冲突域 路由器 应用层 网关 协议簇 …...

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 &#xff1a; 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的具体信息&#xff0c;如版本号等等。这可能为攻击者发动进一步攻击提供帮助。 CVE No. CVE-1999-0634 分析结果 该问题不属于漏洞&#xff0c;不存在安全风险。SSH协议是一种安全协议&am…...

android 修改输出apk的包名

一&#xff0c;打包方式使用IDE菜单选项 二、在app级别的build.gradle下配置&#xff1a; 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…...

轻松实现视频、音频、文案批量合并,享受批量剪辑的便捷

在日常生活中&#xff0c;我们经常会需要将多个视频、音频和文案进行合并剪辑&#xff0c;以制作出符合我们需求的短视频。然而&#xff0c;这个过程通常需要花费大量的时间和精力。幸运的是&#xff0c;现在有一款名为“固乔智剪软件”的工具可以帮助我们轻松完成这个任务。 首…...

Spring Boot、Nacos配置文件的优先级

在标准的 SpringBoot 应用中&#xff0c;本地配置加载顺序如下&#xff1a; 本地 bootstrap 配置&#xff0c;先于 application 配置加载。不带 profile 的配置&#xff0c;先于带 profile 的配置加载。xxx.yaml 先于 xxx.properties 加载。本地配置先于 nacos 配置中心加载。…...

GO脚本-模拟鼠标键盘

01GetCoordinate 获取坐标 package mainimport ("github.com/go-vgo/robotgo" )func main() {// 获取当前鼠标所在的位置x, y : robotgo.GetMousePos()println(x&#xff1a;, x, y&#xff1a;, y)}02GetColor 获取坐标颜色 package mainimport ("fmt&quo…...

Ubuntu设置SSH

在Ubuntu上通过SSH服务远程连接其他机器 首先通过以下命令判断是否安装SSH服务&#xff1a; ssh localhost如果出现 ssh: connect to host localhost port 22: Connection refused 则表示还未安装SSH。 通过以下命令安装SSH&#xff1a; sudo apt update sudo apt install…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...