简单介绍一下需求
可以支持文件上传和下载
为了能够根据关键字搜索文件,要求能够搜索文件中的文本,并且文件类型必须支持word、pdf、txt。
文件上传和下载都比较简单。 为了能够检索文件中的文本并尽可能准确,在这种情况下需要考虑很多事情。 在这种情况下,我决定使用 Elasticsearch。
因为准备找工作刷Nioke,发现很多面试官都问Elasticsearch,而当时我根本不知道Elasticsearch是什么,所以我决定尝试一些新的东西。 不得不说Elasticsearch版本更新得真快。 前几天刚用过7.9.1,不过25号就发布了7.9.2版本。
Elasticsearch 简介
Elasticsearch 是一个开源文献搜索引擎。 大概意思是你通过Rest请求告诉它关键字,它就会返回给你相应的内容。 就这么简单。
Elasticsearch 封装了 Lucene,这是 Apache 软件基金会的一个开源全文搜索引擎工具包。 对Lucene的调用比较复杂,因此Elasticsearch对其进行了再次封装,并提供了一些分布式存储等中间功能。
基于Elasticsearch的插件有很多。 我这次主要用的有两个,一个是kibana,一个是Elasticsearch-head。
开发环境
首先安装 Elasticsearch、Elasticsearch-head 和 kibana。 这三个工具都是开箱即用的,并且可以通过双击运行。 需要注意的是,kibana的版本必须与Elasticsearch的版本相对应。
Elasticsearch-head是Elasticsearch的可视化界面。 Elasticsearch基于Rest风格的API进行操作。 采用可视化界面,不需要每次都使用Get操作来查询,可以提高开发效率。
Elasticsearch-head是使用node.js开发的。 安装过程中可能会遇到跨域问题:Elasticsearch默认端口是9200,Elasticsearch-head默认端口是9100,需要更改配置文件。 怎么改我就不详细说了,毕竟通用搜索引擎都有。
Elasticsearch安装完成后,访问该端口,会出现如下界面。
Elasticsearch主页核心问题
需要解决两个核心问题,文件上传和输入关键词查询。
上传文件
首先,对于txt等纯文本方式,比较简单,直接传入上面的内容即可。 但对于pdf和word这两种特殊格式来说,文件中除了文字之外还有很多不相关的信息,比如图片、pdf中的标签等等。 这需要对文件进行预处理。
Elasticsearch 5.x以后提供了一个名为ingest node的功能,可以对输入文档进行预处理。 如图,进入PUT请求后,首先会判断是否有管道。 如果有,就会进入Ingest Node进行处理,然后进行处理。
引自Elastic中国社区官方博客
Ingest Attachment Processor Plugin是一个文本提取插件,本质上是利用Elasticsearch的ingest节点功能来提供关键的预处理器附件。 在安装目录下执行以下命令进行安装。
./bin/elasticsearch-plugin install ingest-attachment
定义文本提取管道
PUT /_ingest/pipeline/attachment
{
"description": "Extract attachment information",
"processors": [
{
"attachment": {
"field": "content",
"ignore_missing": true
}
},
{
"remove": {
"field": "content"
}
}
]
}
指定要过滤的数组为附件中的内容,因此写入Elasticsearch时,需要将文档内容放入到content数组中。
运行结果如图:
定义文本提取管道以构建文档结构映射
我们需要构建一个文档结构映射来定义文本文件通过预处理器上传后如何存储。 该索引是在 PUT 定义文档结构映射时手动创建的,因此我们首先创建一个 docwrite 索引进行测试。
PUT /docwrite
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"type":{
"type": "keyword"
},
"attachment": {
"properties": {
"content":{
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
}
}
ElasticSearch 中的附件数组被减少。 该数组是附件命名管道提取文档附件中的文本后手动附加的数组。 这是一个嵌套数组,其中包含多个子数组,包括提取的文本内容和一些文档信息元数据。
还将文件名指定为 ik_max_word 分析器,以便 ElasticSearch 在构建全文索引时可以对其执行英文动词。
创建文档结构测试
前两步完成后,我们进行一个简单的测试。 由于 ElasticSearch 是基于 JSON 的文档数据库,因此附加文档在插入 ElasticSearch 之前必须进行 Base64 编码。 首先通过下面的网站将pdf文件转换为base64文本。 PDF 转 Base64
测试文档如图:
测试文档
然后通过下面的请求上传,发现了一个很大的pdf文件。 需要指定的是我们刚刚创建的管道,结果如图所示。
文件上传测试
原来的索引有一个type类型,新版本之前会弃用,默认版本是_doc
然后我们使用GET操作来查看我们的文档是否上传成功。 可以看到,已经分析成功了。
查看文件上传结果
如果不指定pipline,解析起来会比较困难。
当没有指定管道时
根据结果我们可以看到我们的PDF文件已经通过了我们自定义的管道,接下来即将进入索引数据库docwrite。
关键词查询
关键词查询是指输入的文本可以用某些动词进行处理。 例如,对于“数据库计算机网络我的笔记本”这串单词,应该分为“数据库”、“计算机网络”和“我的笔记本”三个关键字,然后根据关键字进行查询。
Elasticsearch 附带了一个分词器,支持所有 Unicode 字符,但它只会做出最大的定义。 例如,进口啤酒这四个词,会分为“中”、“口”、“红”、“酒”。 有了这四个词,查询的结果就会包括“进口”、“口红”、“红酒”。
默认分词器
这不是我们想要的结果。 我们想要的结果是只将其分为“进口”和“红酒”两段,然后查询对应的结果。 这需要使用支持英语的分词器。
ik 分词器
ik tokenizer是开源社区流行的中文分词插件。 我们首先安装 ik 分词器。 注意以下代码不能直接使用。
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/...这里找你的版本
ik 分词器包括两种模式。
ik_max_word 会尽可能多地分割英文。
ik_smart会按照常见习惯进行定义,例如“进口啤酒”会被定义为“进口”、“红酒”。
ik_智能模式
我们在查询的时候使用指定的ik分词器来查询文档。 例如,对于插入的测试文档,我们使用ik_smart模式进行搜索typescript导入pdf文件typescript导入pdf文件,结果如图所示。
GET /docwrite/_search
{
"query": {
"match": {
"attachment.content": {
"query": "实验一",
"analyzer": "ik_smart"
}
}
}
}
搜索文章
我们可以在Elasticsearch中指定高亮,为过滤后的文本添加标签。 这样文本将在之前和之后被标记。 如图所示。
突出功效编码
编码使用Idea+maven的开发环境,首先导出依赖,依赖必须与Elasticsearch的版本对应。
导入依赖项
Elstacisearch 有两个针对 Java 的 API,我们使用封装的更健壮的中级 API。
org.elasticsearch.client
elasticsearch-rest-high-level-client
7.9.1
上传文件
首先构建一个与上面对应的fileObj对象
public class FileObj {
String id; //用于存储文件id
String name; //文件名
String type; //文件的type,pdf,word,or txt
String content; //文件转化成base64编码后所有的内容。
}
首先,如上所述,我们首先要以字节数组的形式读取文件,然后将其转换为Base64编码。
public FileObj readFile(String path) throws IOException {
//读文件
File file = new File(path);
FileObj fileObj = new FileObj();
fileObj.setName(file.getName());
fileObj.setType(file.getName().substring(file.getName().lastIndexOf(".") + 1));
byte[] bytes = getContent(file);
//将文件内容转化为base64编码
String base64 = Base64.getEncoder().encodeToString(bytes);
fileObj.setContent(base64);
return fileObj;
}
java.util.Base64已经提供了现成的函数Base64.getEncoder().encodeToString供我们使用。
接下来,您可以使用 Elasticsearch API 上传文件。
要上传,您需要使用 IndexRequest 对象。 使用FastJson将fileObj转换为Json然后上传。 您需要使用indexRequest.setPipeline函数来指定我们上面定义的管道。 这样,文件就会通过pipline进行预处理,然后进入到fileindex索引中。
public void upload(FileObj file) throws IOException {
IndexRequest indexRequest = new IndexRequest("fileindex");
//上传同时,使用attachment pipline进行提取文件
indexRequest.source(JSON.toJSONString(file), XContentType.JSON);
indexRequest.setPipeline("attatchment");
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse);
}
文件查询
文件查询需要使用SearchRequest对象。 首先,我需要指定 ik_smart 模式动词,以便对关键字使用 ik 分词器。
SearchSourceBuilder srb = new SearchSourceBuilder();
srb.query(QueryBuilders.matchQuery("attachment.content", keyword).analyzer("ik_smart"));
searchRequest.source(srb);
然后我们就可以通过返回的Response对象来获取每一次命中,进而得到返回的内容。
Iterator iterator = hits.iterator();
int count = 0;
while (iterator.hasNext()) {
SearchHit hit = iterator.next();
}
Elasticsearh一个非常强大的功能就是文件的高亮功能,所以我们可以设置一个高亮器来高亮查询的文本。
HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightContent = new HighlightBuilder.Field("attachment.content");
highlightContent.highlighterType();
highlightBuilder.field(highlightContent);
highlightBuilder.preTags("");
highlightBuilder.postTags("");
srb.highlighter(highlightBuilder);
我设置了一个尾随标签来包装查询结果。 这样,相应的结果就会包含在查询结果中。
多文件测试
一个简单的demo已经写好了,但是效果还需要使用多个文件来测试。 这是我的测试文件夹之一,下面放着各种类型的文件。
原来Elasticsearch也可以这么理解
上传该文件夹中的所有文件后,使用elestacisearch-head可视化界面查看导出的文件。
导入的文件
搜索码:
/**
* 这部分会根据输入的关键字去查询数据库中的信息,然后返回对应的结果
* @throws IOException
*/
@Test
public void fileSearchTest() throws IOException {
ElasticOperation elo = eloFactory.generate();
elo.search("数据库国务院计算机网络");
}
运行我们的demo,查询结果如图所示。
搜索结果中还存在的一些问题1.文件宽度问题
通过测试发现,对于文本内容超过10万字的文件,elasticsearch只保留10万字,并且后者被截断。 这就需要进一步了解Elasticsearch对10万字以上文本的支持。
2.一些编码问题
在我的代码中,将所有文件读入显存后,进行一系列的处理无疑会出现问题。 例如,如果是一个非常大的文件超出显存,或者是几个大文件,在实际生产环境中,文件上传会占用服务器很大一部分显存和带宽,需要根据具体情况进一步优化需要。
-结尾-
回顾过去的问题
◆Redis和Mysql如何保持数据一致性
◆软件架构可能不是你想象的那样
◆容器和Pod有什么区别和联系?
◆SRE的本质是懂运维的资深开发人员
◆Spring Cloud应用高贵离线灰度发布
◆一篇文章助你彻底掌握Nginx
◆Redis 7.0新功能和特性概述
◆漫画| Kubernetes 如果它是一个水族馆
◆提高Spring Boot吞吐量的7个妙招!
◆大型SaaS平台产品架构设计
◆代码规范&设计模式落地之路