es简单实现文章检索功能
使用的api是:Elasticsearch Java API client 8.0
官网:Package structure and namespace clients | Elasticsearch Java API Client [8.15] | Elastic
1.建立索引库
实现搜索功能字段:
- title:文章标题
- content:文章内容
- category:文章分类
PUT /search_test
{"mappings": {"properties": {"title":{"type": "text","analyzer": "ik_max_word","index": true},"content":{"type": "text","analyzer": "ik_max_word","index": true},"category":{"type": "keyword","index": true}}}
}
2.代码实现
返回实体:
@Data
public class ContentSearchVO {@Schema(description = "标题")private String title;@Schema(description = "高亮内容部分,用于首页展示")private String highLightContent;@Schema(description = "内容")private String content;}
请求实体:
@Data
public class SearchDTO {@Schema(description="搜索词")@NotBlank(message = "搜索词不能是空")private String text;@Schema(description="分类")private String category;private Integer pageNo;private Integer pageSize;}
2.1基于模板形式
@Overridepublic List<ContentSearchVO> search(SearchDTO dto) {String tags = dto.getCategory();if (tags != null){tags = String.format(SearchConstants.CATEGORY, tags) ;}else {tags = "";}//es内容搜索String query = String.format(SearchConstants.SEARCH_MAPPING, dto.getText(), dto.getText(), tags, dto.getPageNo(), dto.getPageSize());StringReader stringReader = new StringReader(query);SearchRequest searchRequest = SearchRequest.of(builder -> builder.index(SearchConstants.INDEX_NAME).withJson(stringReader));SearchResponse<Object> response = EsSearchUtil.search(searchRequest);List<ContentSearchVO> searchResult = EsSearchUtil.buildSearchResult(response);return searchResult;}
模板常量
public class SearchConstants {//索引库public static final String INDEX_NAME = "search_test";//类别模板public static final String CATEGORY = " ,\"term\": {\n" +" \"category\": \"%s\"\n" +" },\n";//搜索模板public static final String SEARCH_MAPPING = "{" +" \"query\": {\n" +" \"bool\": {\n" +" \"must\":[ " +" {\n" +" \"match\": {\n" +" \"title\": \"%s\"\n" +" }\n" +" },\n" +" {\n" +" \"match\": {\n" +" \"content\": \"%s\"\n" +" }\n" +" }\n" +"%s"+" ]}\n" +" },\n" +" \"from\": %s,\n" +" \"size\": %s,\n" +" \"highlight\": {\n" +" \"pre_tags\": \"<em>\",\n" +" \"post_tags\": \"</em>\",\n" +" \"fields\": {\n"+" \"title\": {},\n" +" \"content\": {}\n" +" }\n" +" }\n" +"}";}
EsSearchUtil
@Component
@Slf4j
public class EsSearchUtil {private static ElasticsearchClient client;@Autowiredpublic EsSearchUtil(ElasticsearchClient client) {EsSearchUtil.client = client;}/*** 数据检索*/public static SearchResponse<Object> search(SearchRequest searchRequest) {SearchResponse<Object> response;try {response = client.search(searchRequest, Object.class);log.debug("搜索返回结果:" + response.toString());} catch (IOException e) {log.error(e.toString());throw new RuntimeException("搜索服务出了点小差,请稍后再试", e);}return response;}/*** 构建搜索结果*/public static <T> List<T> buildSearchResult(SearchResponse<Object> resp, Class<T> clazz) {if (resp.hits() == null || resp.hits().hits() == null) {return Collections.emptyList();}List<Hit<Object>> hits = resp.hits().hits();List<T> list = new ArrayList<>();for(Hit<Object> hit:hits) {//格式转换T t = ConvertUtil.objToObj(hit.source(), clazz);list.add(t);}return list;}public static List<ContentSearchVO> buildSearchResult(SearchResponse<Object> resp){List<Hit<Object>> hits = resp.hits().hits();List<ContentSearchVO> list = new ArrayList<>();for(Hit<Object> hit:hits){ContentSearchVO contentSearchVO = ConvertUtil.objToObj(hit.source(), ContentSearchVO.class);Map<String, List<String>> highlight = hit.highlight();if (!CollectionUtils.isEmpty(highlight)) {//标题List<String> highLightTitle = highlight.get("title");//内容List<String> highLightContent = highlight.get("content");//无高亮,就用内容替代if (!CollectionUtils.isEmpty(highLightTitle)){StringBuilder highLightTitleStringBuilder = new StringBuilder();for (String titleSegment : highLightTitle) {highLightTitleStringBuilder.append(titleSegment);}contentSearchVO .setTitle(highLightTitleStringBuilder.toString());}if (!CollectionUtils.isEmpty(highLightContent)){StringBuilder highLightContentStringBuilder = new StringBuilder();for (String contentSegment : highLightContent) {highLightContentStringBuilder.append(contentSegment);}contentSearchVO .setHighLightContent(highLightContentStringBuilder.toString());}else {contentSearchVO .setHighLightContent(contentSearchVO.getContent());}}list.add(contentSearchVO);}return list;}/*** 分词解析* @param text 搜索词* @param index 索引库* @return 分词结果*/public static List<String> analyze (String text,String index) {AnalyzeRequest analyzeRequest = new AnalyzeRequest.Builder().index(index).text(text).analyzer("ik_max_word") // ik_smart.build();AnalyzeResponse analyzeResponse;try {analyzeResponse = client.indices().analyze(analyzeRequest);} catch (IOException e) {throw new RuntimeException(e);}List<AnalyzeToken> tokens = analyzeResponse.tokens();List<String> result = new ArrayList<>();// 分词结果for (AnalyzeToken token : tokens) {result.add(token.token());}return result;}
}
2.2 基于搜索api实现
@Service
@Slf4j
@RequiredArgsConstructor
public class ContentSearchServiceImpl implements ContentSearchService {private final SearchHotWordsService searchHotWordsService;@Value("${search.highlightWords:500}")private Integer highlightWords;@Value("${search.words:500}")private Integer words;@Value("${search.searchWords:200}")private Integer searchWords;@Overridepublic SearchVO search(SearchDTO dto) {SearchVO result = new SearchVO();//构建boolBoolQuery boolQuery = this.buildBoolQuery(dto);//构建高亮字段Highlight highlight = this.buildHighlight();//构建查询SearchRequest searchRequest = SearchRequest.of(s -> s.index(SearchConstants.INDEX_NAME).query(q -> q.bool(boolQuery)).from((dto.getPageNo() - 1) * dto.getPageSize()).size(dto.getPageSize()).highlight(highlight));SearchResponse<SearchDocument> response = EsSearchUtil.search(searchRequest,SearchDocument.class);List<ContentSearchVO> searchResult = this.buildSearchResult(response);if (!CollectionUtils.isEmpty(searchResult)){searchResult = this.sortSearchResult(searchResult);}result.setList(searchResult);return result;}/*** 构建bool* @param dto 查询参数* @return BoolQuery*/private BoolQuery buildBoolQuery(SearchDTO dto) {String tags = dto.getTags();//要查询的字段List<String> queryFields = List.of(SearchConstants.FILE_NAME, SearchConstants.CONVERTED_TEXT);//构建bool查询BoolQuery.Builder boolBuilder = new BoolQuery.Builder().should(s -> s.multiMatch(mu -> mu.fields(queryFields).query(dto.getText())));if (tags != null){List<FieldValue> v = new ArrayList<>();String[] split = tags.split(",");for (String s : split) {v.add(FieldValue.of(s));}TermsQuery termsQuery = TermsQuery.of(t -> t.field(SearchConstants.TAGS).terms(tm -> tm.value(v)));boolBuilder.must(m -> m.terms(termsQuery));}return boolBuilder.build();}/*** 构建高亮字段* @return Highlight*/private Highlight buildHighlight() {Map<String, HighlightField> map = new HashMap<>();map.put(SearchConstants.FILE_NAME, HighlightField.of(hf -> hf.preTags(SearchConstants.PRE_TAGS).postTags(SearchConstants.POST_TAGS)));map.put(SearchConstants.CONVERTED_TEXT, HighlightField.of(hf -> hf.preTags(SearchConstants.PRE_TAGS).postTags(SearchConstants.POST_TAGS).numberOfFragments(1).fragmentSize(highlightWords) //numberOfFragments(1),表示将字段分割为最多1个片段,并设置 fragmentSize(300),表示每个片段的大小为300个字符。));return Highlight.of(h -> h.type(HighlighterType.Unified).order(HighlighterOrder.Score).fields(map));}private List<ContentSearchVO> buildSearchResult(SearchResponse<SearchDocument> resp) {List<Hit<SearchDocument>> hits = resp.hits().hits();List<ContentSearchVO> list = new ArrayList<>();for(Hit<SearchDocument> hit:hits){SearchDocument searchDocument = JsonUtil.objToObj(hit.source(), SearchDocument.class);ContentSearchVO contentSearchVO = this.searchAdapter(searchDocument);String content = contentSearchVO.getContent();Map<String, List<String>> highlight = hit.highlight();if (!CollectionUtils.isEmpty(highlight)) {//高亮标题List<String> highLightTitle = highlight.get(SearchConstants.FILE_NAME);//高亮内容List<String> highLightContent = highlight.get(SearchConstants.CONVERTED_TEXT);//标题if (!CollectionUtils.isEmpty(highLightTitle)){StringBuilder highLightTitleStringBuilder = new StringBuilder();for (String titleSegment : highLightTitle) {highLightTitleStringBuilder.append(titleSegment);}contentSearchVO .setTitle(highLightTitleStringBuilder.toString());}//内容if (!CollectionUtils.isEmpty(highLightContent)){StringBuilder highLightContentStringBuilder = new StringBuilder();for (String titleSegment : highLightContent) {highLightContentStringBuilder.append(titleSegment);}contentSearchVO .setContent(highlightContent);}else {//无高亮字段---从头开始取一定数量的字String s = this.replaceSymbol(content).replace(SearchConstants.LINE, "");if (s.length() >= words){s = s.substring(0,words);}contentSearchVO .setContent(s);}}else {//无高亮字段---从头开始取一定数量的字String s = this.replaceSymbol(content).replace(SearchConstants.LINE, "");if (s.length() >= words){s = s.substring(0,words);}contentSearchVO .setContent(s);}list.add(contentSearchVO );}return list;}private ContentSearchVO searchAdapter(SearchDocument searchDocument) {ContentSearchVO contentSearchVO = new ContentSearchVO();contentSearchVO .setTitle(searchDocument.getFileName());contentSearchVO .setContent(searchDocument.getDocText());contentSearchVO .setFileId(searchDocument.getFileId());contentSearchVO .setTags(searchDocument.getTags());contentSearchVO .setTimestamp(searchDocument.getTimestamp());return contentSearchVO ;}private String replaceSymbol(String replaceStr) {return replaceStr.replaceAll("\t","").replaceAll(" "," ").replaceAll(" "," ").replaceAll("(\\n\\s*)+", " ") //多重换行只保留一个.replaceAll("\\s+"," ").replaceAll("-\\d+-", "") //去掉 页码;}}
相关文章:
es简单实现文章检索功能
使用的api是:Elasticsearch Java API client 8.0 官网:Package structure and namespace clients | Elasticsearch Java API Client [8.15] | Elastic 1.建立索引库 实现搜索功能字段: title:文章标题content:文章内…...
太速科技-607-基于FMC的12收和12发的光纤子卡
基于FMC的12收和12发的光纤子卡 一、板卡概述 本卡是一个FPGA夹层卡(FMC)模块,可提供高达2个CXP模块接口,提供12路收,12路发的光纤通道。每个通道支持10Gbps,通过Aurora协议,可以组成X4࿰…...
UEFI学习笔记(十):系统表与ACPI表的遍历
一、概述 在 UEFI 系统表中,有几个关键的表用于提供系统信息、服务和硬件抽象。这些表可以通过 EFI_SYSTEM_TABLE 访问,常见的 UEFI 系统表如下: 1、EFI_SYSTEM_TABLE (系统表) EFI_SYSTEM_TABLE 是一个指针,包含多个服务和系统…...
【深度学习基础模型】液态状态机(Liquid State Machines, LSM)详细理解并附实现代码。
【深度学习基础模型】液态状态机(Liquid State Machines, LSM)详细理解并附实现代码。 【深度学习基础模型】液态状态机(Liquid State Machines, LSM)详细理解并附实现代码。 文章目录 【深度学习基础模型】液态状态机࿰…...
深入理解链表(SList)操作
目录: 一、 链表介绍1.1、 为什么引入链表1.2、 链表的概念及结构1.3、 链表的分类 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q循环链表&spm1001.2101.3001.7020)的实现2.1、 [单链表](https://so.csdn.net/so/search?q单链表&spm1001.2…...
03. prometheus 监控 Linux 主机
文章目录 一、prometheus 监控 Linux 主机二、防火墙打开端口1. 方式一:使用 iptables 添加白名单(推荐使用):2. 方式二:重载防火墙 一、prometheus 监控 Linux 主机 1. 官网下载 node_exporter 官网:htt…...
AI占据2024诺贝尔两大奖项,是否预示着未来AI即一切?
本次诺贝尔物理学和学奖的获得者都与AI息息相关,可谓是“AI领域的大丰收”。 2024年诺贝尔物理学奖揭晓:瑞典皇家科学院公布了2024年诺贝尔物理学奖的获得者。他们是美国的约翰霍普菲尔德(John J. Hopfield),以及加拿…...
[已解决] Install PyTorch 报错 —— OpenOccupancy 配环境
目录 关于 常见的初始化报错 环境推荐 torch, torchvision & torchaudio cudatoolkit 本地pip安装方法 关于 OpenOccupancy: 语义占用感知对于自动驾驶至关重要,因为自动驾驶汽车需要对3D城市结构进行细粒度感知。然而,现有的相关基准在城市场…...
6. PH47 代码框架硬件开发环境搭建
概述 PH47代码框架的硬件开发环境搭建同样简单, 建立基本的 PH47 框架学习或二次开发的硬件开发环境所需设备如下: BBP 飞控板及相关软硬件: BBP飞控板,或者至少一块Stm32F411核心板(WeAct Studio)Stm32程序烧录工具…...
package.json配置
package.json配置 描述配置文件配置脚本配置依赖配置发布配置系统配置第三方配置 描述配置 name : 项目名称,第三方包可以通过npm install 包名安装 "name":"react"version : 项目版本,项目版本号 "version" : "18.2…...
视频怎么转gif动图?5个简单转换方法快来学(详细教程)
相信大家在社交平台上会经常看到一些有趣的gif动图表情包,有些小伙伴就会问:这些GIF动图是如何制作的呢?一般GIF动图表情包可以用视频来制作,今天小编就来给大家分享几个视频转成GIF动图的方法,相信通过以下的几个方法…...
10月更新:优维EasyOps®需求解决更彻底,功能体验再升级
升 级 不 止 步 欢迎来到 需求至上,功能完善 的 \ EasyOps 7.5版本 / 👇 >> 联动架构视图:深度融合监控与资源拓扑 传统上,依赖监控态势感知系统固有的分层拓扑结构虽有其优势,但在处理复杂系统尤其是核心数…...
黑马javaWeb笔记重点备份1:三层架构、IOC、DI
来自:【黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖SpringMyBatisSpringMVCSpringBoot等)】 https://www.bilibili.com/video/BV1m84y1w7Tb/?p75&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c398…...
大坝渗流监测设备——渗压计
渗压计是一种用于监测大坝等水工建筑物渗流压力的重要设备,其准确性和可靠性对于保障大坝安全运行至关重要。南京峟思将为大家详细介绍渗压计的工作原理、安装方法及其在大坝渗流监测中的应用。 渗压计主要利用振弦频率的变化来测量渗透水压力。设备由透水部件、感应…...
Pikachu-Sql Inject-宽字节注入
基本概念 宽字节是相对于ascII这样单字节而言的;像 GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是常说的宽字节,实际上只有两字节 GBK 是一种多字符的编码,通常来说,一个 gbk 编码汉字,占用2个字节。一个…...
如何制作低代码开发的视频教程?
如何制作低代码开发的视频教程? 随着数字化转型的加速,越来越多的企业和组织开始采用低代码开发平台来加速应用程序的构建。对于许多开发者和业务人员来说,学习如何使用这些平台可以显著提高工作效率。因此,创建一份清晰、实用且…...
Flink学习地址
--基础概念 概览 | Apache Flink --应用系列 如何学习Flink:糙快猛的大数据之路(图文并茂)_flink 学习-CSDN博客 --Python系列 pyflink实时接收kafka数据至hive_pyflink下kafka数据经过处理后插入hive-CSDN博客 Pyflink教程(一)&#…...
05_23 种设计模式之《建造者模式》
文章目录 一、建造者模式基础知识建造者模式的结构建造者模式的应用场景 一、建造者模式基础知识 建造者模式(Builder Pattern)是一种创建型设计模式,它提供了一种优雅的方式来创建复杂对象,同时隐藏其构建过程。这种模式允许你通…...
详细分析Spring Security OAuth2中的JwtAccessTokenConverter基本知识(附Demo)
目录 前言1. 基本知识2. Demo3. 实战 前言 java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)【Java项目】实战CRUD的功能整理(持续更新) 1. 基本知识 JwtAccessTokenConverter 是 Spring Security OAuth2 中的一…...
python习题2
1、输出一个年份,判断其是不是闰年 #输入一个年份,判断其是否是闰年 y eval(input()) if y%4 0 and y%100 ! 0:print("是") elif y%4000:print("是") else:print("不是") 2、模拟智能客服: 按1查询账户余额…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
