一、 系统架构与核心技术选型
一个稳定、可扩展的数字档案馆系统,其技术栈的选型至关重要。本系统采用前后端分离的微服务架构,确保高内聚、低耦合,便于后期维护与扩展。
1.1 后端技术栈
后端采用Spring Boot + MyBatis-Plus框架,数据库使用MySQL 8.0,全文检索与文档分析使用Elasticsearch 7.x,文件存储使用MinIO(兼容S3协议的对象存储)。
- Spring Boot 2.7.x:提供快速启动和自动配置。
- MyBatis-Plus 3.5.x:简化数据库操作,内置分页、代码生成器等实用功能。
- MySQL 8.0:存储结构化数据,如档案元数据、用户信息、系统日志。
- Elasticsearch 7.17.x:用于档案内容的全文检索、智能分类和数据分析。
- MinIO:存储非结构化的档案文件(如扫描件、图片、视频),替代传统FTP或本地磁盘存储。
1.2 前端技术栈
前端使用Vue 3 + Element Plus,构建现代化、响应式的管理界面。
- Vue 3 (Composition API):提供更好的逻辑复用和类型推断。
- Element Plus:基于Vue 3的UI组件库,快速搭建后台界面。
- Axios:处理HTTP请求。
二、 环境准备与基础服务部署
在开始编码前,需要先部署好所有依赖的中间件服务。请确保服务器(或本地开发机)已安装Docker和Docker Compose,这将极大简化部署过程。
2.1 使用Docker Compose一键启动基础服务
在服务器上创建一个名为docker-compose.yml的文件,内容如下:
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: archive-mysql
environment:
MYSQL_ROOT_PASSWORD: YourStrongPassword123!
MYSQL_DATABASE: digital_archive
ports:
- "3306:3306"
volumes:
- ./mysql_data:/var/lib/mysql
command: --default-authentication-plugin=mysql_native_password
elasticsearch:
image: elasticsearch:7.17.9
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
minio:
image: minio/minio
container_name: archive-minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: YourMinioPassword123!
volumes:
- ./minio_data:/data
command: server /data --console-address ":9001"
```
保存文件后,在终端执行以下命令启动所有服务:
```
docker-compose up -d
```
执行docker ps命令检查三个容器(mysql, elasticsearch, minio)是否正常运行。
2.2 初始化MinIO存储桶
浏览器访问 http://你的服务器IP:9001,使用上面配置的用户名admin和密码YourMinioPassword123!登录MinIO管理控制台。
- 点击左侧菜单“Buckets”。
- 点击“Create Bucket”按钮。
- 输入Bucket名称:real-estate-archive,点击创建。
三、 后端核心模块开发
我们将创建一个Spring Boot项目,并实现档案上传、元数据管理、全文检索三个核心模块。
3.1 项目初始化与依赖配置
使用Spring Initializr(https://start.spring.io)生成项目,依赖选择:Spring Web, MyBatis Framework, MySQL Driver, Lombok。
在生成的pom.xml中,手动添加以下依赖:
```xml
com.baomidou
mybatis-plus-boot-starter
3.5.3.1
io.minio
minio
8.5.2
org.elasticsearch.client
elasticsearch-rest-high-level-client
7.17.9
```
3.2 配置文件
在application.yml中配置所有连接信息:
```yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/digital_archive?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: YourStrongPassword123!
driver-class-name: com.mysql.cj.jdbc.Driver
minio:
endpoint: http://localhost:9000
accessKey: admin
secretKey: YourMinioPassword123!
bucketName: real-estate-archive
elasticsearch:
host: localhost
port: 9200
```
3.3 实现档案文件上传与存储
创建MinioService,封装文件上传、下载、删除等操作。
```java
@Service
public class MinioService {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.bucketName}")
private String bucketName;
private MinioClient minioClient;
@PostConstruct
public void init() {
this.minioClient = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
public String uploadFile(MultipartFile file, String objectName) throws Exception {
// 检查存储桶是否存在,不存在则创建
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
// 上传文件
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
// 返回文件访问URL(生产环境需配置为域名或通过网关)
return endpoint + "/" + bucketName + "/" + objectName;
}
}
```
创建ArchiveController,接收前端上传的档案文件,并调用MinioService保存。
```java
@RestController
@RequestMapping("/api/archive")
public class ArchiveController {
@Autowired
private MinioService minioService;
@Autowired
private ArchiveService archiveService;
@PostMapping("/upload")
public ApiResult uploadArchive(@RequestParam("file") MultipartFile file,
@RequestParam String projectName,
@RequestParam String archiveType) throws Exception {
// 1. 生成唯一文件名,防止覆盖
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = UUID.randomUUID().toString() + fileExtension;
// 2. 上传到MinIO
String fileUrl = minioService.uploadFile(file, objectName);
// 3. 保存档案元数据到MySQL
ArchiveEntity archive = new ArchiveEntity();
archive.setProjectName(projectName);
archive.setArchiveType(archiveType);
archive.setFileName(originalFilename);
archive.setFileUrl(fileUrl);
archive.setFileSize(file.getSize());
archiveService.save(archive);
// 4. 异步提取文本内容并存入Elasticsearch(针对PDF、Word等)
archiveService.asyncIndexContent(archive.getId(), file);
return ApiResult.success("档案上传成功", archive.getId());
}
}
```
3.4 实现档案全文检索

创建ElasticsearchService,用于将档案的文本内容(通过OCR或文档解析获得)索引到Elasticsearch。
```java
@Service
public class ElasticsearchService {
private RestHighLevelClient client;
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@PostConstruct
public void init() {
client = new RestHighLevelClient(
RestClient.builder(new HttpHost(host, port, "http"))
);
}
public void indexDocument(Long archiveId, String content, String projectName) throws IOException {
IndexRequest request = new IndexRequest("archive_docs");
Map
jsonMap = new HashMap<>();
jsonMap.put("id", archiveId);
jsonMap.put("content", content);
jsonMap.put("projectName", projectName);
jsonMap.put("timestamp", new Date());
request.id(archiveId.toString()).source(jsonMap);
client.index(request, RequestOptions.DEFAULT);
}
public List search(String keyword) throws IOException {
SearchRequest searchRequest = new SearchRequest("archive_docs");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 使用match query进行全文检索,匹配content字段
sourceBuilder.query(QueryBuilders.matchQuery("content", keyword));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
List resultIds = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
resultIds.add(Long.valueOf(hit.getId()));
}
return resultIds;
}
}
```
在ArchiveService中,调用Tika等库解析上传的文档(如PDF、Word),提取纯文本,然后调用ElasticsearchService.indexDocument方法建立索引。
四、 前端管理界面开发
使用Vue CLI创建一个新项目,并安装Element Plus和Axios。
4.1 档案上传组件
创建ArchiveUpload.vue组件,提供表单用于上传档案。
```vue
点击上传
支持PDF, Word, Excel, 图片等格式,单文件不超过50MB
```
4.2 档案检索组件
创建ArchiveSearch.vue组件,提供搜索框,调用后端检索接口并展示结果。
```vue
预览
下载
```
五、 系统部署与上线
开发完成后,需要将前后端应用部署到生产环境。
5.1 后端应用打包与部署
在后端项目根目录下,执行Maven打包命令:
```
mvn clean package -DskipTests
```
打包完成后,会在target目录下生成一个.jar文件。将其上传到服务器,使用以下命令启动:
```
nohup java -jar digital-archive-backend-1.0.0.jar --spring.profiles.active=prod > app.log 2>&1 &
```
注意:你需要创建一个application-prod.yml配置文件,将其中数据库、MinIO、Elasticsearch的连接地址改为生产服务器的内网IP或域名,切勿使用localhost。
5.2 前端应用构建与部署
在前端项目根目录下,安装依赖并构建生产环境代码:
```
npm install
npm run build
```
构建完成后,将dist目录下的所有文件,复制到Nginx或Apache的Web服务器目录下(例如/usr/share/nginx/html)。
配置Nginx,将API请求代理到后端服务:
```
server {
listen 80;
server_name your-domain.com; 你的域名
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080; 后端服务地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
重启Nginx服务使配置生效:
```
sudo systemctl restart nginx
```
六、 关键问题排查与优化
6.1 文件上传失败
检查MinIO服务是否正常运行,网络是否通畅。检查后端应用的application-prod.yml中MinIO的endpoint配置是否正确(应为内网地址或可解析的域名)。检查服务器防火墙是否开放了MinIO的端口(默认9000)。
6.2 全文检索无结果
首先检查Elasticsearch服务状态,访问http://服务器IP:9200查看是否返回版本信息。然后检查后端日志,查看文档解析和索引过程是否报错。确保上传的文档(如PDF)不是扫描