系统架构与技术选型
本系统采用前后端分离架构,旨在解决研发过程中文档版本混乱、检索困难的问题。核心组件包括:Spring Boot 3.2作为后端框架,Vue 3 + Element Plus作为前端界面,MinIO负责私有化文件存储,MySQL存储元数据,Elasticsearch实现毫秒级全文检索。
第一步:基础设施环境搭建
为了确保环境一致性,我们使用Docker Compose一次性启动MySQL、Redis、MinIO和Elasticsearch。请在服务器本地创建docker-compose.yml文件,并将以下内容完整复制进去。
1. 编写Docker编排文件
此配置文件包含了所有中间件的初始化设置,包括MinIO的默认AccessKey和SecretKey,以及Elasticsearch的内存分配。
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: archive_mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: dev_archive
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7-alpine
container_name: archive_redis
ports:
- "6379:6379"
minio:
image: minio/minio:latest
container_name: archive_minio
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin123
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
elasticsearch:
image: elasticsearch:8.11.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:
mysql_data:
minio_data:
es_data:
```
2. 启动服务
在docker-compose.yml同级目录下执行以下命令启动所有容器:
```bash
docker-compose up -d
``>
执行docker ps确认所有容器状态为Up。注意:Elasticsearch启动较慢,需等待约30秒直至9200端口可连通。
第二步:数据库设计与初始化
我们需要两张核心表:t_project用于关联研发项目,t_archive用于存储档案文件的具体信息。请连接MySQL(端口3306,用户root,密码root123456),选中数据库dev_archive并执行以下SQL。
```sql
CREATE TABLE IF NOT EXISTS `t_project` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '项目ID',
`project_name` varchar(100) NOT NULL COMMENT '项目名称',
`project_code` varchar(50) NOT NULL COMMENT '项目编码',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`project_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='研发项目表';
CREATE TABLE IF NOT EXISTS `t_archive` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '档案ID',
`project_id` bigint NOT NULL COMMENT '关联项目ID',
`file_name` varchar(255) NOT NULL COMMENT '原始文件名',
`file_type` varchar(20) NOT NULL COMMENT '文件类型后缀',
`file_size` bigint NOT NULL COMMENT '文件大小(字节)',
`object_name` varchar(500) NOT NULL COMMENT 'MinIO对象存储路径',
`uploader` varchar(50) DEFAULT NULL COMMENT '上传人',
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
`file_status` tinyint DEFAULT 1 COMMENT '状态 1:正常 0:删除',
PRIMARY KEY (`id`),
KEY `idx_project` (`project_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字档案表';
```
第三步:后端核心功能开发
后端使用Spring Boot 3.2.1,集成JPA、MinIO Client和Elasticsearch Repository。
1. Maven依赖配置
在pom.xml中添加以下关键依赖,版本号由Spring Boot父工程管理:
```xml
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-data-elasticsearch
mysql
mysql-connector-java
8.0.33
io.minio
minio
8.5.7
org.projectlombok
lombok
true
```
2. application.yml完整配置
请务必修改server.port和数据库连接信息,MinIO地址填写Docker容器IP或主机IP。
```yaml
server:
port: 8080
spring:
application:
name: archive-system
datasource:
url: jdbc:mysql://localhost:3306/dev_archive?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
elasticsearch:
uris: http://localhost:9200
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin123
bucketName: dev-archives
```
3. 核心实体类与Repository
创建Archive.java,使用JPA注解映射MySQL,同时使用@Document映射ES索引。
```java
package com.dev.archive.entity;
import jakarta.persistence.;
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;
@Data
@Entity
@Table(name = "t_archive")
@Document(indexName = "archive_index")
public class Archive {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long projectId;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String fileName;
private String fileType;
private Long fileSize;
private String objectName;
private String uploader;
}
```
创建ArchiveRepository.java继承JPA和ES的Repository接口以同时支持两种存储操作。
```java
package com.dev.archive.repository;
import com.dev.archive.entity.Archive;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ArchiveRepository extends JpaRepository
, ElasticsearchRepository {
}
```
4. 文件上传与索引业务逻辑

在ArchiveService.java中实现MinIO上传和双写(MySQL+ES)逻辑。这里包含了一个重要的初始化Bucket的操作。
```java
package com.dev.archive.service;
import com.dev.archive.entity.Archive;
import com.dev.archive.repository.ArchiveRepository;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.MakeBucketArgs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class ArchiveService {
private final MinioClient minioClient;
private final ArchiveRepository archiveRepository;
@Value("${minio.bucketName}")
private String bucketName;
public ArchiveService(MinioClient minioClient, ArchiveRepository archiveRepository) {
this.minioClient = minioClient;
this.archiveRepository = archiveRepository;
initBucket();
}
private void initBucket() {
try {
boolean found = minioClient.bucketExists(bucketName);
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
throw new RuntimeException("MinIO Bucket初始化失败", e);
}
}
public Archive uploadArchive(Long projectId, MultipartFile file, String uploader) {
try {
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
String objectName = UUID.randomUUID() + "." + suffix;
// 上传至MinIO
try (InputStream inputStream = file.getInputStream()) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
}
// 保存元数据到MySQL并同步索引到ES
Archive archive = new Archive();
archive.setProjectId(projectId);
archive.setFileName(originalFilename);
archive.setFileType(suffix);
archive.setFileSize(file.getSize());
archive.setObjectName(objectName);
archive.setUploader(uploader);
return archiveRepository.save(archive);
} catch (Exception e) {
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
}
}
}
```
5. 控制器层实现
提供上传接口和搜索接口,确保跨域配置正确以便前端调用。
```java
package com.dev.archive.controller;
import com.dev.archive.entity.Archive;
import com.dev.archive.repository.ArchiveRepository;
import com.dev.archive.service.ArchiveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.web.bind.annotation.;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/archives")
@CrossOrigin(origins = "")
public class ArchiveController {
@Autowired
private ArchiveService archiveService;
@Autowired
private ArchiveRepository archiveRepository;
@PostMapping("/upload")
public String upload(@RequestParam Long projectId,
@RequestParam("file") MultipartFile file,
@RequestParam String uploader) {
Archive archive = archiveService.uploadArchive(projectId, file, uploader);
return "上传成功,档案ID: " + archive.getId();
}
@GetMapping("/search")
public Page search(@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
// 简单的ES查询,生产环境建议使用NativeSearchQueryBuilder构建复杂Query
return archiveRepository.findByFileNameContaining(keyword, PageRequest.of(page, size));
}
}
``>
注意:需要在ArchiveRepository中添加ES查询方法定义:Page findByFileNameContaining(String keyword, Pageable pageable);
第四步:前端界面开发
使用Vue 3 Vite版构建,Element Plus提供UI组件,Axios处理HTTP请求。
1. 项目初始化
执行命令创建项目并安装依赖:
```bash
npm create vue@latest archive-frontend
cd archive-frontend
npm install element-plus axios
```
2. 配置Axios与Element Plus
在src/main.js中全局引入Element Plus:
```javascript
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus)
app.mount('app')
```
3. 核心页面组件实现
修改src/App.vue,实现文件上传和列表搜索功能。这是一个包含完整逻辑的单文件组件。
```html
研发项目数字档案馆
拖拽文件到此处或 点击上传
搜索
{{ (scope.row.fileSize / 1024).toFixed(2) }} KB
```
第五步:运行与验证
完成所有代码编写后,按以下顺序启动系统进行验证。
1. 启动后端
在IDEA中运行ArchiveApplication主类,或使用Maven命令打包运行:
```bash
mvn clean package
java -jar target/archive-system-0.0.1-SNAPSHOT.jar
``>
观察控制台日志,确认MinIO Bucket初始化成功且ES连接正常。
2. 启动前端
在Vue项目根目录下执行:
```bash
npm run dev
``>
终端会输出本地访问地址,通常是http://localhost:5173。
3. 功能验证步骤
- 文件上传:在浏览器打开前端页面,输入项目ID(如100)和上传人,将一个本地的PDF或Word文档拖入上传框。等待提示“上传成功”。
- 数据校验:登录MySQL,查询
select from t_archive;,确认新增了一条记录。
- 存储校验:访问MinIO控制台
http://localhost:9001,使用minioadmin/minioadmin123登录,查看dev-archives桶中是否存在文件。
- 检索测试:在前端搜索框输入刚才上传文件名中的关键字,按回车。下方表格应能即时展示出匹配的文档记录,验证Elasticsearch索引生效。