基础环境快速搭建
实现数字档案馆的高效关键词搜索,首选方案是Elasticsearch。为了确保零门槛快速启动,我们使用Docker Compose进行部署。这种方式无需手动配置Java环境,直接拉取镜像即可运行。
在服务器本地创建一个docker-compose.yml文件,直接复制以下内容。该配置定义了单节点ES集群,并禁用了安全认证以简化本地开发测试流程。
```yaml
version: '3'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
container_name: archive_es
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
volumes:
es_data:
```
保存文件后,在当前目录执行以下命令启动服务:
```bash
docker-compose up -d
```
启动成功后,必须安装中文分词插件ik,否则无法正确处理中文档案的检索。执行以下命令进入容器并安装插件:
```bash
docker exec -it archive_es /bin/bash
elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.0/elasticsearch-analysis-ik-7.17.0.zip
exit
docker restart archive_es
``>
等待容器重启完成,通过curl http://localhost:9200验证服务状态,返回JSON数据即代表环境就绪。
索引结构与分词器配置
数字档案馆的数据通常包含案卷题名、文件编号、归档年度以及最重要的OCR全文内容。我们需要设计一个Mapping,将题名和全文内容设置为ik_max_word分词,以支持细粒度的中文检索。
在Kibana Dev Tools或通过curl发送以下PUT请求创建索引digital_archives:
```json
PUT digital_archives
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": {
"analyzer": {
"ik_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word"
}
}
}
},
"mappings": {
"properties": {
"archive_id": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "ik_analyzer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"file_code": {
"type": "keyword"
},
"archive_year": {
"type": "integer"
},
"ocr_content": {
"type": "text",
"analyzer": "ik_analyzer"
},
"create_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
```
注意:title字段使用了fields参数,既保留了分词后的全文检索能力,又保留了keyword类型用于精确匹配或聚合统计。
Spring Boot项目集成与配置
后端采用Spring Boot框架,利用官方提供的spring-boot-starter-data-elasticsearch进行交互。首先在pom.xml中引入依赖,版本需与ES服务端版本保持一致(7.17.x)。
```xml
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.projectlombok
lombok
true
```
在application.yml中配置连接地址:
```yaml
spring:
elasticsearch:
rest:
uris: http://localhost:9200
```
创建档案实体类ArchiveDocument.java,使用@Document注解映射索引:
```java
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
@Data
@Document(indexName = "digital_archives")
public class ArchiveDocument {
@Id
private String archiveId;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Keyword)
private String fileCode;
@Field(type = FieldType.Integer)
private Integer archiveYear;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String ocrContent;
@Field(type = FieldType.Date, format = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
private Date createTime;
}
```
档案数据批量写入实操
数字档案馆的数据量通常较大,单条写入效率极低。我们需要使用RestHighLevelClient提供的BulkRequest进行批量操作。以下是一个完整的Service层代码示例,模拟批量写入档案数据。
```java
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
@Service
public class ArchiveDataService {
@Autowired
private RestHighLevelClient client;
private static final ObjectMapper mapper = new ObjectMapper();
public void bulkImportArchives() throws Exception {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
// 模拟构造1000条档案数据
for (int i = 0; i < 1000; i++) {
Map
dataMap = new HashMap<>();
dataMap.put("archive_id", "ARC-" + System.currentTimeMillis() + i);
dataMap.put("title", "2023年度财务审计报告卷" + i);
dataMap.put("file_code", "CW-2023-" + i);
dataMap.put("archive_year", 2023);
dataMap.put("ocr_content", "这是关于财务审计的详细内容,包含资金流向、发票号码以及审批人签字等关键信息...");
dataMap.put("create_time", "2023-10-27 10:00:00");
IndexRequest request = new IndexRequest("digital_archives");
request.id(dataMap.get("archive_id").toString());
request.source(mapper.writeValueAsString(dataMap), XContentType.JSON);
bulkRequest.add(request);
}
// 执行批量写入
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) {
System.err.println("批量写入存在错误: " + bulkResponse.buildFailureMessage());
} else {
System.out.println("批量写入成功,共处理: " + bulkResponse.getItems().length + " 条");
}
}
}
```
关键点:务必设置timeout参数,防止数据量过大导致请求超时。生产环境中建议将批次大小控制在1000-5000条之间。
多维关键词搜索核心代码

这是系统的核心功能,要求用户输入一个关键词,系统能同时在“题名”和“OCR全文”中进行检索,并支持按“归档年度”过滤。我们使用NativeSearchQuery构建查询条件。
```java
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ArchiveSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
public List searchArchives(String keyword, Integer year, int pageNum, int pageSize) {
// 1. 构建布尔查询
// must: 用户必须输入的关键词,在title和ocr_content中跨字段匹配
// filter: 非评分过滤,例如归档年度
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "ocr_content")
.field("title", 2.0f) // 提升题名的权重,题名匹配的排在前面
.analyzer("ik_max_word")
)
.withFilter(QueryBuilders.termQuery("archive_year", year))
.withPageable(PageRequest.of(pageNum, pageSize))
.build();
// 2. 执行搜索
SearchHits searchHits = elasticsearchRestTemplate.search(query, ArchiveDocument.class);
// 3. 提取结果
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
}
```
上述代码实现了基础的搜索逻辑。为了提升用户体验,通常需要对匹配的关键词进行高亮显示。我们需要修改NativeSearchQuery的构建部分,添加高亮配置。
搜索结果高亮与分页处理
在返回给前端之前,我们需要将原始文本中的关键词替换为HTML标签(如)以便前端渲染红色高亮。以下是集成高亮功能的完整搜索方法。
```java
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
public List