整合全文检索引擎 Lucene 添加站内搜索子模块

整合全文检索引擎 Lucene: 添加站内搜索子模块

1. 什么是 Lucene ? 有啥优势?

Lucene 是一个开源的全文检索引擎库,由 Apache 基金会维护,官网地址:https://lucene.apache.org/ 。它提供了丰富的文本处理和搜索功能,允许开发者在应用程序中集成强大的全文检索能力。

以下是 Lucene 的一些主要特点和优势:

  1. 全文检索: Lucene 支持全文检索,可以在大量文本数据中快速而准确地查找关键字。
  2. 开源: Lucene 是开源的,可以免费使用,并且具有灵活的许可证,适用于各种项目。
  3. 高性能: Lucene 的搜索性能非常高效,它使用了许多优化算法和数据结构,能够在大型数据集上快速执行搜索。
  4. 跨平台: Lucene 是用 Java 编写的,因此可以在几乎所有的平台上运行。
  5. 可扩展: Lucene 提供了丰富的API和插件机制,可以轻松扩展其功能,以满足不同应用的需求。
  6. 丰富的查询语法: Lucene 支持复杂的查询语法,包括通配符、模糊查询、范围查询等。

2.添加依赖

    <!-- 版本号统一管理 --><properties>// 省略...<lucene.version>8.11.1</lucene.version></properties><!-- 统一依赖管理 --><dependencyManagement><dependencies>// 省略...<!-- lucene 全文检索引擎 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>${lucene.version}</version></dependency><!-- 中文分词 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-smartcn</artifactId><version>${lucene.version}</version></dependency><!-- 关键词高亮 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-highlighter</artifactId><version>${lucene.version}</version></dependency><!-- 查询解析器 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-queryparser</artifactId><version>${lucene.version}</version></dependency></dependencies></dependencyManagement>

添加 CommandLineRunner 项目启动任务:初始化 Lucene 文章索引

如何在 Spring Boot 工程启动时,执行一些任务呢? 其实实现方式有多种,这里使用的是 CommandLineRunner

CommandLineRunner 是什么?

CommandLineRunner 是 Spring Boot 提供的一个接口,用于在 Spring Boot 应用启动后执行一些初始化逻辑。它是一个功能接口,只包含一个 run 方法,该方法会在 Spring Boot 应用启动后被调用。

我们在 weblog-module-search 模块中,创建一个 /runner 包,并添加一个 InitLuceneIndexRunner 初始化索引的任务

package com.quanxiaoha.weblog.search.runner;import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class InitLuceneIndexRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {log.info("==> 开始初始化 Lucene 索引...");}
}

2. 扫描 CommandLineRunner

3.Lucene 相关概念

在这里插入图片描述

  • 索引(Index): 索引是 Lucene 中的核心概念,它类似于数据库中的表。在 Lucene 中,索引是由一系列词项(terms)构成的数据结构,每个词项都关联到一个或多个文档。这允许非常快速的搜索,类似于数据库中使用索引进行快速检索的方式;
  • 文档(Document):文档是 Lucene 中的基本信息单元,可以看作数据库表中的一行。每个文档由一组字段(Field)组成,每个字段包含一个值。文档在索引中存储,并且可以根据这些字段进行搜索;
  • 字段(Field):字段是文档中的一个属性,它有一个名称和一个值。在搜索和检索中,我们可以使用字段来过滤和排序文档;
  • 分析器(Analyzer):分析器负责将文本切分成单词,并对这些单词进行标准化处理,以便建立索引和进行搜索。Lucene 提供了各种分析器来处理不同类型的文本数据;
  • 查询(Query):查询是用于在索引中搜索文档的表达式。Lucene 提供了强大的查询语言,允许我们构建复杂的搜索条件。

4.配置索引存储目录

为了能够自定义 Lucene 索引的存储目录,编辑 application-dev.yml 开发环境配置文件,自定义一个 lucene.indexDir 配置:

#=================================================================
# Lucene 全文检索
#=================================================================
lucene:indexDir: E:\\java_workspace\\lucene-index # lucene 索引存放的位置

另外,再编辑 applicaiton-prod.yml 生产环境配置文件,自定义一个 linux 环境的索引存储目录,这里放置到了项目部署的目录下,方便后续维护:

#=================================================================
# Lucene 全文检索
#=================================================================
lucene:indexDir: /app/weblog/lucene-index # lucene 索引存放的位置

读取 Lucene 配置

添加一个 /config 包,并新建一个 LuceneProperties 配置类,用于读取 .yml 文件中的 lucene 配置:

@ConfigurationProperties(prefix = "lucene")
@Component
@Data
public class LuceneProperties {/*** 索引存放的文件夹*/private String indexDir;
}

定义索引

接着,再添加一个 /index 包,在里面创建一个 ArticleIndex 索引接口,用于定义文章索引的名称,以及文档字段。添加哪些字段,就看你页面中需要展示哪些数据,代码如下:

public interface ArticleIndex {/*** 索引名称*/String NAME = "article";// --------------------- 文档字段 ---------------------String COLUMN_ID = "id";String COLUMN_TITLE = "title";String COLUMN_COVER = "cover";String COLUMN_SUMMARY = "summary";String COLUMN_CONTENT = "content";String COLUMN_CREATE_TIME = "createTime";
}

引入 commons-io 工具包

准备工作完成后,正式进入到创建索引阶段了。因为涉及到操作文件,这里在父 pom.xml 中声明一下 commons-io 工具包,该库中封装了一些文件相关的常用操作,如创建文件,创建文件夹,删除文件夹等等,还是非常好用的:

    <!-- 版本号统一管理 --><properties>// 省略...<commons-io.version>2.11.0</commons-io.version></properties><dependencies>// 省略..<!-- 工具包 --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>${commons-io.version}</version></dependency></dependencies>    

封装 Lucene 工具类

@Component
@Slf4j
public class LuceneHelper {/*** 创建索引* @param indexDir 索引存放的目录* @param documents 文档*/public void createIndex(String indexDir, List<Document> documents) {try {File dir = new File(indexDir);// 判断索引目录是否存在if (dir.exists()) {// 删除目录中的内容FileUtils.cleanDirectory(dir);} else {// 若不存在,则创建目录FileUtils.forceMkdir(dir);}// 读取索引目录Directory directory = FSDirectory.open(Paths.get(indexDir));// 中文分析器Analyzer analyzer = new SmartChineseAnalyzer();IndexWriterConfig config = new IndexWriterConfig(analyzer);// 创建索引IndexWriter writer = new IndexWriter(directory, config);// 添加文档documents.forEach(document -> {try {writer.addDocument(document);} catch (IOException e) {log.error("添加 Lucene 文档错误: ", e);}});// 提交writer.commit();writer.close();} catch (Exception e) {log.error("创建 Lucene 索引失败: ", e);}}
}

初始化 Lucene 索引

工具类封装完成后,回到 InitLuceneIndexRunner 类中,编辑代码如下:

package com.quanxiaoha.weblog.search;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.stereotype.Component;import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;@Component
@Slf4j
public class InitLuceneIndexRunner implements CommandLineRunner {@Autowiredprivate LuceneProperties luceneProperties;@Autowiredprivate LuceneHelper luceneHelper;@Autowiredprivate ArticleMapper articleMapper;@Autowiredprivate ArticleContentMapper articleContentMapper;@Overridepublic void run(String... args) throws Exception {log.info("==> 开始初始化 Lucene 索引...");// 查询所有文章List<ArticleDO> articleDOS = articleMapper.selectList(Wrappers.emptyWrapper());// 未发布文章,则不创建 lucene 索引if (articleDOS.isEmpty()) {log.info("==> 未发布任何文章,暂不创建 Lucene 索引...");return;}// 若配置文件中未配置索引存放目录,日志提示错误信息if (StringUtils.isBlank(luceneProperties.getIndexDir())) {log.error("==> 未指定 Lucene 索引存放位置,需在 application.yml 文件中添加路径配置...");return;}// 文章索引存放目录, 如 /app/weblog/lucene-index/articleString articleIndexDir = luceneProperties.getIndexDir() + File.separator + ArticleIndex.NAME;List<Document> documents = Lists.newArrayList();articleDOS.forEach(articleDO -> {Long articleId = articleDO.getId();// 查询文章正文ArticleContentDO articleContentDO = articleContentMapper.selectByArticleId(articleId);// 构建文档Document document = new Document();// 设置文档字段 Fielddocument.add(new TextField(ArticleIndex.COLUMN_ID, String.valueOf(articleId), Field.Store.YES));document.add(new TextField(ArticleIndex.COLUMN_TITLE, articleDO.getTitle(), Field.Store.YES));document.add(new TextField(ArticleIndex.COLUMN_COVER, articleDO.getCover(), Field.Store.YES));document.add(new TextField(ArticleIndex.COLUMN_SUMMARY, articleDO.getSummary(), Field.Store.YES));document.add(new TextField(ArticleIndex.COLUMN_CONTENT, articleContentDO.getContent(), Field.Store.YES));document.add(new TextField(ArticleIndex.COLUMN_CREATE_TIME, Constants.DATE_TIME_FORMATTER.format(articleDO.getCreateTime()), Field.Store.YES));documents.add(document);});// 创建索引luceneHelper.createIndex(articleIndexDir, documents);log.info("==> 结束初始化 Lucene 索引...");}
}

上述代码中,首先查询了文章表,校验了一下是否有文章,以及 .yml 文件中是否配置了索引存储目录。若通过,则对文章数据进行遍历,拿着文章 ID 查询正文,并构建 Document 文档,以及设置文档中需要存储的 Field 字段,最终调用 LuceneHelper 工具类中的 createIndex()方法,完成对文章索引的创建工作。

封装查询总文档数方法

在动手写接口之前,先来给 LuceneHelper 工具类封装两个分页相关的方法:

  • 1、中文分词分页搜索,查询总文档数;
  • 2、中文分词分页搜索;

编辑 LuceneHelper 工具类,添加查询总文档数方法,代码如下:

package com.quanxiaoha.weblog.search;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.stereotype.Component;import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;@Component
@Slf4j
public class LuceneHelper {// 省略.../*** 关键词搜索, 查询总数据量* @param indexDir 索引目录* @param word 查询关键词* @param columns 需要搜索的字段* @return*/public long searchTotal(String indexDir, String word, String[] columns) {try {// 打开索引目录Directory directory = FSDirectory.open(Paths.get(indexDir));IndexReader reader = DirectoryReader.open(directory);IndexSearcher searcher = new IndexSearcher(reader);// 中文分析器Analyzer analyzer = new SmartChineseAnalyzer();// 查询解析器QueryParser parser = new MultiFieldQueryParser(columns, analyzer);// 解析查询关键字Query query = parser.parse(word);// 搜索文档TopDocs totalDocs  = searcher.search(query, Integer.MAX_VALUE);// 返回文档数return totalDocs.totalHits.value;} catch (Exception e) {log.error("查询 Lucene 错误: ", e);return 0;}}
}

上面封装的方法,主要是用于在 Lucene 中执行搜索,然后获取匹配的文档总数。入参需要传入索引目录,被查询的关键词,以及想要搜索的文档字段。

接下来,我来逐行解释一下方法内的代码含义:

  1. 打开索引目录并创建 IndexReaderIndexSearcher

    Directory directory = FSDirectory.open(Paths.get(indexDir));
    IndexReader reader = DirectoryReader.open(directory);
    IndexSearcher searcher = new IndexSearcher(reader);
    
    • FSDirectory.open(Paths.get(indexDir)) 打开存储索引的目录。
    • DirectoryReader.open(directory) 创建一个 IndexReader,用于读取索引。
    • IndexSearcher(reader) 创建一个 IndexSearcher,用于执行搜索操作。
  2. 设置中文分析器和查询解析器:

    Analyzer analyzer = new SmartChineseAnalyzer();
    QueryParser parser = new MultiFieldQueryParser(columns, analyzer);
    
    • 创建中文分析器 SmartChineseAnalyzer
    • 创建 MultiFieldQueryParser,用于解析查询字符串。columns 参数指定了在哪些字段上执行查询。
  3. 解析查询字符串:

    Query query = parser.parse(word);
    
    • 使用 parser.parse(word) 将查询字符串 word 解析为 Lucene 的查询对象 Query
  4. 执行搜索并获取总文档数:

    TopDocs totalDocs  = searcher.search(query, Integer.MAX_VALUE);
    
    • searcher.search(query, Integer.MAX_VALUE) 执行搜索,获取匹配查询的所有文档。
    • totalDocs.totalHits.value 获取匹配的文档总数。
  5. 返回匹配文档的总数:

    return totalDocs.totalHits.value;
    

ery query = parser.parse(word);


- 使用 `parser.parse(word)` 将查询字符串 `word` 解析为 Lucene 的查询对象 `Query`。4. **执行搜索并获取总文档数:**```java
TopDocs totalDocs  = searcher.search(query, Integer.MAX_VALUE);
  • searcher.search(query, Integer.MAX_VALUE) 执行搜索,获取匹配查询的所有文档。
  • totalDocs.totalHits.value 获取匹配的文档总数。
  1. 返回匹配文档的总数:

    return totalDocs.totalHits.value;
    
    • 返回搜索结果中匹配的文档总数。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

OceanBase详解及如何通过MySQL的lib库进行连接

OceanBase详解及如何通过MySQL的lib库进行连接 一、引言二、OceanBase概述1. 起源与发展2. 核心技术特点3. 应用场景三、OceanBase架构解析1. 系统架构2. 存储引擎3. 分布式架构四、如何使用MySQL的lib库连接OceanBase1. 前提条件2. 安装MySQL Connector/C3. 编写连接代码4. 编…

104. UE5 GAS RPG 实现技能火焰爆炸

这一篇文章我们再实现一个技能火焰爆炸&#xff0c;由于我们之前已经实现了三个玩家技能&#xff0c;这一个技能有一些总结的味道&#xff0c;对于创建技能相同的部分&#xff0c;长话短说&#xff0c;我们过一遍。 准备工作 我们需要一个技能类&#xff0c;继承于伤害技能基…

【力扣打卡系列】验证二叉搜索树

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day17 验证二叉搜索树 题目描述 解题思路 前序遍历&#xff1a;先访问节点值&#xff0c;再访问左右子树有效二叉搜索树的定义 节点的左子树只包含小于当前节点的数节点的右子树只包含大于当前节…

Swarm-LIO: Decentralized Swarm LiDAR-inertial Odometry论文翻译

文章目录 前言一、介绍二、相关工作三、方法A. 问题表述B. 框架概述C. 群体系统的初始化D. 去中心化激光雷达-惯性状态估计 四. 实验A. 室内飞行B. 退化环境飞行C. 去中心化部署 五. 结论和未来工作 前言 原文&#xff1a;原文 准确的自我状态和相对状态估计是完成群体任务的关…

京东毫秒级热key探测框架JD-hotkey

前言 对任意突发性的&#xff0c;无法预先感知的热点数据&#xff0c;包括热点数据&#xff08;如突发大量请求同一个商品&#xff09;、热用户&#xff08;如恶意爬虫刷子&#xff09;、热接口&#xff08;突发海量请求同一个接口&#xff09;等&#xff0c;一瞬间打到我们的服…

IntelliJ IDEA 中上传项目到 Gitee 的完整指南

博主主页:【南鸢1.0】 本文专栏&#xff1a;git 目录 简介 1.插入intellij-gitee 2.导入下载插件 3.选择导航栏中的VCS->Share Project on Gitee 4.登录gitee 6.验证gitee仓库是否创建成功 7.上传分享项目 8.验证仓库代码是否上传成功 总结 简介 Gitee 是一个代码…

低代码可视化-按钮open-type开放能力自定义-代码生成器

微信小程序原本确实不直接支持通过点击按钮将内容分享到朋友圈的功能&#xff0c;但微信在后续更新中逐步放开了部分限制&#xff0c;允许特定内容以小程序卡片的形式分享到朋友圈。然而&#xff0c;这一功能仍然需要满足一定的条件&#xff0c;并且需要开发者进行特定的配置。…

基于springboot乐器视频学习网站设计与实现(源码齐全可用)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题&#xff0c;今天给大家介绍…

TypeScript:never 类型的神奇妙用

在 TypeScript 中&#xff0c;never 是一个特殊类型&#xff0c;表示「永不存在的值类型」&#xff0c;通常用于表示不可能发生的情况。它适用于抛出异常、不返回值的函数或处理逻辑上永远不会出现的分支。 以下是它的简单用法和注意事项&#xff1a; 1. never 的用法 1、抛…

Redis-结构化value对象的类型

文章目录 一、Redis的结构化value对象类型的介绍二、Redis的这些结构化value对象类型的通用操作查看指定key的数据类型查看所有的key判断指定key是否存在为已存在的key进行重命名为指定key设置存活时间pexpire与expire 查看指定Key的存活时间为指定key设置成永久存活 三、Redis…

解密美国 VPS 主机的核心优势与未来发展

在全球网络需求不断增长的今天&#xff0c;许多企业和开发者都在寻找更灵活、安全、性能优异的主机解决方案。美国 VPS 主机凭借其强大的技术支持和广泛的网络连接&#xff0c;成为国际用户的热门选择。本文将深入探讨美国 VPS 主机的核心优势、其在网络应用上的独特表现&#…

XSS小游戏【1-13关】

第一关 Payload&#xff1a;<script>alert(1)</script> 第二关 Payload&#xff1a;keyword<script>alert(1)</script> 发现没有成功&#xff0c;F12发现需要闭合input 标签 再次输入payload&#xff1a;aaa"><svg οnlοadalert(1)> …

Spring Boot框架:校园社团信息管理的现代化解决方案

3系统分析 3.1可行性分析 通过对本校园社团信息管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本校园社团信息管理系统采用SSM框架&#xff0c;JAVA作…

uniapp推送配置流程

Dcloud Dcloud注册账号 个推 了解即可 注册个推账号 ios配置流程 需配置含有推送的描述文件以及p8证书 配置推送证书 ios证书配置报技术错误&#xff08;参数错误&#xff09; TeamID-苹果开发者账号唯一的ID 安卓需配置多厂商 小米手机需要配置小米厂商 华为手机则需…

JavaEE初阶---网络原理之TCP篇(二)

文章目录 1.断开连接--四次挥手1.1 TCP状态1.2四次挥手的过程1.3time_wait等待1.4三次四次的总结 2.前段时间总结3.滑动窗口---传输效率机制3.1原理分析3.2丢包的处理3.3快速重传 4.流量控制---接收方安全机制4.1流量控制思路4.2剩余空间大小4.3探测包的机制 5.拥塞控制---考虑…

单细胞数据分析(一):10X数据生成seurat数据对象

文章目录 介绍加载R包数据链接导入数据过滤细胞:移除双重细胞合并所有seurat数据对象输出结果系统信息介绍 在单细胞基因组学研究中,Seurat是一个流行的R包,用于单细胞基因表达数据的分析和探索。以下是如何从10X基因注释数据生成Seurat数据对象,并对该数据进行过滤的步骤…

了解SQLExpress数据库

SQLExpress&#xff08;Microsoft SQL Server Express&#xff09;是由微软公司开发的一款免费且轻量级的数据库管理系统。以下是关于SQLExpress的详细解释&#xff1a; 一、定义与特点 定义&#xff1a; SQLExpress是Microsoft SQL Server的一个缩减版或基础版&#xff0c;旨在…

C++ 魔法三钥:解锁高效编程的封装、继承与多态

快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 &#x1f4af;前言 &#x1f4af;封装 1.封装概念 2.封装格式 3.封装的原理 4.封装的作用 &#x1f4af;继承 1.继承的概念 2.继承格式 3.继承的…

开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序中积分使用价值的拓展策略

摘要&#xff1a;本文围绕开源 AI 智能名片 2 1 链动模式 S2B2C 商城小程序&#xff0c;深入探讨其积分使用价值的丰富策略。详细分析积分兑换礼品、会员升级、积分抵现等方式在该特定商城小程序环境下的应用特点、存在问题及对用户和商城的影响&#xff0c;旨在为商城的优化运…

UE4安卓Gradle工程中的libUE4.so的生成原理

流程图 流程图放在最前面&#xff0c;下面是讲解。 libUE4.so 问&#xff1a;在UE4安卓开发中&#xff0c;libUE4.so即是符号表&#xff0c;又是引擎代码native&#xff0c;是吗&#xff1f; 答&#xff1a;是的&#xff0c;libUE4.so在UE4安卓开发中既包含符号表&#xff0c;…