如何实现 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 接口定义一个就好,可以搞几个抽象类,譬如专门处理高亮文本的抽象类、不涉及到嵌套对象的抽象类转换、以及通用转换抽象类出来,写多了感觉自己再写源码了,那些搞开源项目的还有一些主流框架的源码不都是这么干的,但是需要花费一定的精力去写,笔者比较懒,下完班只想回家美美的打打游戏,追追剧,就点到为止了。

真实项目落地效果

在这里插入图片描述
复杂对象高亮字段替换效果在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/128516.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pycharm配置python3.8版本专门用于undecteded_chromedriver测试

pycharm配置python3.8版本专门用于undecteded_chromedriver测试 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://pay.xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、Pycharm及python环境的配置 1.安装python-3.8.7rc1-amd64.e…

c++视觉---使用轨迹条设置图片的对比度,亮度

轨迹条&#xff1a;cv::createTrackbar cv::createTrackbar 是OpenCV库中的一个函数&#xff0c;用于创建一个图形用户界面 (GUI) 中的滑动条控件&#xff0c;允许用户在应用程序运行时调整特定参数的值。这个函数的调用方式如下&#xff1a; int cv::createTrackbar(const s…

GitHub基础

1、仓库是什么意思&#xff1f;仓库拥有者是谁&#xff1f; 在软件开发或版本控制系统中&#xff0c;"仓库"&#xff08;Repository&#xff09;是指存储项目代码、配置文件、文档等相关文件的地方。它可以看作是一个中央存储库&#xff0c;用于管理和跟踪项目的各个…

【MyBatis】MyBatis 详解

MyBatis 详解 一. MyBatis 是什么二. MyBatis 在整个框架中的定位三. MyBatis 的操作1. 先创建好数据库和表2. 添加MyBatis框架⽀持3. 修改配置文件4. 添加业务代码5. 增、删、改操作① 增加⽤户② 修改用户操作③ 删除操作 6. 查询操作① 单表查询② 多表查询 一. MyBatis 是什…

Swift SwiftUI CoreData 过滤数据 2

预览 Code import SwiftUI import CoreDatastruct HomeSearchView: View {Environment(\.dismiss) var dismissState private var search_value ""FetchRequest(entity: Bill.entity(),sortDescriptors: [NSSortDescriptor(keyPath: \Bill.c_at, ascending: false)…

提升企业形象与效率,申请办理400电话客服中心

引言&#xff1a; 随着企业竞争的日益激烈&#xff0c;提供优质的客户服务成为企业赢得市场份额的关键。而400电话客服中心的建立&#xff0c;不仅可以提升企业形象&#xff0c;还能提高客户满意度和工作效率。本文将介绍400电话客服中心的申请办理过程&#xff0c;以及其对企业…

2023年中国隆鼻行业发展历程及趋势分析:隆鼻手术市场将实现进一步增长[图]

隆鼻术就是以各种植入材料置入为主要方法&#xff0c;隆起或抬高鼻部形态为主要目的的鼻整形术式。隆鼻术可能是开展最多的整形美容手术之一。隆鼻术也是一种很成熟的美容手术&#xff0c;操作较为简单、安全、风险较小&#xff0c;也易于接受。 隆鼻行业分类 资料来源&#x…

万界星空科技低代码平台+协同制造MES产品

在生产数字化建设方面&#xff0c;MES产品针对不同的制造行业&#xff0c;开发的工作量较大。传统交付方式开发周期长、过程不可控、质量把控难&#xff0c;同时&#xff0c;开发实施周期长带来了需求变化的可能性增加&#xff0c;周期可能还会延长。 随着数字技术的不断迭代成…

用delphi7将excel导入access并查询及其分析(一)

开发环境&#xff1a;win7 64&#xff08;win10 64&#xff09;两个系统环境&#xff0c;delphi7。 一、安装delphi7&#xff08;已经放在阿里云盘的soft中&#xff09; 解压安装&#xff0c;文件夹里自带SN.txt。直接默认路径安装&#xff08;关系到后续的控件安装时方便&…

【Zookeeper专题】Zookeeper经典应用场景实战(二)

目录 前置知识课程内容一、Zookeeper分布式锁实战1.1 什么是分布式锁1.2 基于数据库设计思路1.3 基于Zookeeper设计思路一1.4 基于Zookeeper设计思路二1.5 Curator 可重入分布式锁工作流程1.6 总结 二、基于Zookeeper实现服务的注册与发现2.1 设计思路2.2 Zookeeper实现注册中心…

计算机基础

分值&#xff1a;3-7 1. 计算机系统概述 2. 计算机组成结构 3. 存储结构 3.1. 层次化存储结构 一般用什么调什么&#xff0c;局部性原理 内存和外存可以统称为虚拟存储器 我们可以操作哪些&#xff1a;操作外存、内存、CPU寄存器。Cache具有透明性。 3.2. Cache Cache的功…

《C和指针》笔记31:多维数组的数组名、指向多维数组的指针、作为函数参数的多维数组

文章目录 1. 指向多维数组的数组名2. 指向多维数组的指针3. 作为函数参数的多维数组 1. 指向多维数组的数组名 我们知道一维数组名的值是一个指针常量&#xff0c;它的类型是“指向元素类型的指针”&#xff0c;它指向数组的第1个元素。那么多维数组的数组名代表什么呢&#x…