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

news2024/10/24 0:14:04

整合全文检索引擎 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 {

    @Override
    public 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 {

    @Autowired
    private LuceneProperties luceneProperties;
    @Autowired
    private LuceneHelper luceneHelper;
    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private ArticleContentMapper articleContentMapper;

    @Override
    public 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/article
        String 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();
            // 设置文档字段 Field
            document.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.coloradmin.cn/o/2221978.html

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

相关文章

IO、存储、硬盘:解析文件系统和File类

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! 在计算机科学中&#xff0c;IO&#xff08;输入/输出&#xff09;、存储、硬盘和文件系统是构成计算机数据处理和存储的基础。本文将探讨这些概念&#xff0c;特别是文件系统的工作原理和相关知识。 输入/输出…

【C++篇】探索STL之美:熟悉使用String类

CSDN 文章目录 前言 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&…

吴恩达深度学习笔记(8)

计算机视觉 包括&#xff1a;图像分类也叫做图像识别、目标检测等 一个小的图像可能1M&#xff0c;但是他的像素是一个超级大向量&#xff0c;如果直接深度学习那么运算量会很大&#xff0c;因此需要运用卷积运算。 卷积运算是卷积神经网络的基础单元之一。下面用边缘检测理…

【Qt】控件——Qt多元素控件、常见的多元素控件、多元素控件的使用、List Widget、Table Widget、Tree Widget

文章目录 QtQt多元素控件List WidgetTable WidgetTree Widget Qt Qt多元素控件 List Widget 使用 QListWidget 能够显示一个纵向的列表。 属性说明currentRow当前被选中的是第几行。count一共有多少行。sortingEnabled是否允许排序。isWrapping是否允许换行。itemAlignment元素…

软件架构的 “4+1” 视图(附视图案例)

简介 Philippe Kruchten 在《IEEE Software》上发表的《The 41 View Model of Architecture》论文中提出了“41”视图方法&#xff0c;该方法被RUP采纳。“41”视图模型通过逻辑视图、进程视图&#xff08;也称为处理视图&#xff09;、物理视图、开发视图 和 场景视图 五个视…

YOLOv9改进,YOLOv9引入FLAttention注意力机制(ICCV2023),并二次创新RepNCSPELAN4结构

摘要 自我注意的二次计算复杂度在将 Transformer 模型应用于视觉任务时,这是一个长期存在的问题。除了减少注意力区域外,线性注意力也被认为是避免过多计算成本的有效解决方案。通过使用精心设计的映射函数来逼近 Softmax,线性注意力可以在自注意力操作中切换计算顺序并实现…

HTTP Proxy环境下部署Microsoft Entra Connect和Health Agents

在企业环境中&#xff0c;时常需要通过使用HTTP Proxy访问Internet&#xff0c;在使用HTTP Proxy访问Internet的环境中部署Microsoft Entra Connect和Microsoft Entra Connect Health Agents可能会遇到一些额外的配置步骤&#xff0c;以便这些服务能够正常连接到Internet。 一…

再Android10上实现检测AHD摄像头是否接入

项目有个需要&#xff0c;需要知道tp9951是否接入AHD摄像头 1&#xff0c;驱动层可以通过读取寄存器的值来检测是否接入AHD摄像头 tp9951_write_reg(0x40, 0x00); //select decoder page tp9951_write_reg(0x41, ch); val tp9951_read_reg(TP_INPUT_STATUS_REG);…

vscode默认添加python项目的源目录路径到执行环境(解决ModuleNotFoundError: No module named问题)

0. 问题描述 vscode中编写python脚本&#xff0c;导入工程目录下的其他模块&#xff0c;出现ModuleNotFoundError: No module named 错误 在test2的ccc.py文件中执行print(sys.path) 查看路径 返回结果发现并无’/home/xxx/first_demo’的路径&#xff0c;所以test2下面的文…

Vscode + EIDE +CortexDebug 调试Stm32(记录)

{// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0","configurations": [{"cwd": "${workspaceRoot…

w~自动驾驶合集9

我自己的原文哦~ https://blog.51cto.com/whaosoft/12320882 #自动驾驶数据集全面调研 自动驾驶技术在硬件和深度学习方法的最新进展中迅速发展&#xff0c;并展现出令人期待的性能。高质量的数据集对于开发可靠的自动驾驶算法至关重要。先前的数据集调研试图回顾这些数据集&…

Djang学习- URL反转

代码中url书写规范&#xff1a; 、 url反向解析 urls: path(test/url, views.test_url),path(test_result/<int:age>, views.test_result, name"rl") views: def test_url(request):return render(request, test_url.html)def test_result(request,age):re…

Vue3学习:vite项目中图片不能显示,报错 require is not defined

今天做了一个案例“给你喜欢的人送花”&#xff0c;如果喜欢谁&#xff0c;就给谁送花&#xff0c;最多可以送5朵。运行效果如下。 这个项目是使用 npm create vitelatest 命令创建的。 包括2个组件&#xff1a; 根组件App.vue子组件HelloVote.vue。 目录结构如图所示&#x…

资讯 | 财富通科技政务协同办公管理软件通过麒麟软件适配认证

2024年9月25日&#xff0c;财富通科技研发的政务协同办公管理软件成功通过中国国产操作系统麒麟软件的适配认证。本次认证是继公司区块链产品“基于区块链的企业及人员资质数字证书服务平台”认证以后得第二次认证。这一成就标志着财富通科技在推动国产软件生态建设方面迈出了坚…

基于SSM+微信小程序的家庭记账本管理系统(家庭1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 1、管理员端功能有首页、个人中心、用户管理&#xff0c;消费详情管理、收入详情管理、系统管理等。 2、用户端功能有首页、消费详情、收入详情、论坛信息、我的等功能。 2、项目技术 …

数据结构作业day2

作业一&#xff1a;结构体数组存储学生信息&#xff08;姓名&#xff0c;年龄&#xff0c;分数&#xff09;&#xff0c;完成输入学生信息&#xff0c;输出学生信息&#xff0c;求学生成绩之和&#xff0c;求最低学生成绩。 main.c #include "test.h"int main(int …

GitLab 老旧版本如何升级?

极狐GitLab 正式对外推出 GitLab 专业升级服务 https://dl.gitlab.cn/cm33bsfv&#xff01; 专业的技术人员为您的 GitLab 老旧版本实例进行专业升级&#xff01;服务详情可以在官网查看详细解读&#xff01; 那些因为老旧版本而被攻击的例子 话不多说&#xff0c;直接上图&a…

视频转GIF,5个简单好用的转换工具任你选!

GIF&#xff08;Graphics Interchange Format&#xff09;&#xff0c;作为一种轻量级的图像文件格式&#xff0c;因其能够存储多帧图像形成简单动画而广受欢迎。无论是动画影视片段&#xff0c;还是日常生活的小确幸&#xff0c;GIF都能将其定格为生动的视觉语言。当我们想要将…

FileLink跨网文件摆渡系统:重构跨网文件传输新时代

在数字化浪潮的推动下&#xff0c;企业对于数据的高效利用和安全管理提出了前所未有的要求。面对不同网络环境间的文件传输难题&#xff0c;传统方法往往显得力不从心&#xff0c;不仅效率低下&#xff0c;还存在极大的安全隐患。而FileLink跨网文件摆渡系统的出现&#xff0c;…

深信服超融合HCI6.8.0R2滚动热升级至HCI6.9.1

PS&#xff1a;滚动热升级没有业务影响&#xff0c;集群内主机逐台升级&#xff0c;会自动迁移运行中的虚拟机至其他主机&#xff1b; 整体巡检加上升级完成大概要三个小时的时间。如果在升级过程中&#xff0c;有跨集群迁移的任务&#xff0c;需要先停掉&#xff0c;不然无法…