本系统采用前后端分离架构,后端基于Spring Boot 2.7处理业务逻辑与文件存储,前端使用Vue 3 + Element Plus构建管理界面,数据存储选用MySQL 8.0,文件对象存储选用MinIO。OCR识别模块集成Tesseract实现证照信息自动提取。为了降低环境配置难度,数据库与文件存储服务均使用Docker Compose一键部署。
在项目根目录下创建docker-compose.yml文件。该配置将启动MySQL(端口3306)和MinIO(控制台9000端口,API 9001端口)。请确保本地已安装Docker环境,直接复制以下内容:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: water_license_mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: water_license_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
minio:
image: minio/minio:latest
container_name: water_license_minio
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: admin123
ports:
- "9000:9000"
- "9001:9001"
command: server /data --console-address ":9000"
volumes:
- minio_data:/data
volumes:
mysql_data:
minio_data:
保存文件后,在终端执行启动命令:docker-compose up -d。启动成功后,访问 http://localhost:9000 可进入MinIO控制台,使用账号admin和密码admin123登录。
我们需要一张核心表来存储取水许可证的元数据及文件索引。请在终端连接MySQL,或使用Navicat连接到本地3306端口,执行以下建表SQL:
CREATE DATABASE IF NOT EXISTS water_license_db;
USE water_license_db;
CREATE TABLE water_license_archive (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
license_no VARCHAR(100) NOT NULL COMMENT '取水许可证号',
enterprise_name VARCHAR(255) NOT NULL COMMENT '取水单位名称',
file_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
file_path VARCHAR(500) NOT NULL COMMENT 'MinIO存储路径',
file_size BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
ocr_content TEXT COMMENT 'OCR识别全文内容',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '归档时间',
INDEX idx_license_no (license_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='取水许可证数字档案表';
后端使用Spring Boot构建,主要负责文件上传、对象存储交互及元数据持久化。
创建Spring Boot项目,在pom.xml中引入Web、JPA、MySQL驱动、MinIO客户端及Lombok依赖。以下是完整的dependencies节点内容:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
runtime
io.minio
minio
8.5.2
org.projectlombok
lombok
true
在src/main/resources/application.yml中配置数据库连接及MinIO参数。请注意,MinIO的endpoint地址需填9001端口:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/water_license_db?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
minio:
endpoint: http://localhost:9001
accessKey: admin
secretKey: admin123
bucketName: water-licenses
创建WaterLicenseArchive实体类,对应数据库表结构:
import javax.persistence.;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "water_license_archive")
public class WaterLicenseArchive {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String licenseNo;
@Column(nullable = false)
private String enterpriseName;
@Column(nullable = false)
private String fileName;
@Column(nullable = false)
private String filePath;
private Long fileSize;
@Column(columnDefinition = "TEXT")
private String ocrContent;
private LocalDateTime createTime;
}
创建WaterLicenseRepository接口:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface WaterLicenseRepository extends JpaRepository {
}
创建ArchiveService,实现文件上传至MinIO、元数据保存功能。此处为了演示“零门槛”,OCR部分使用模拟文本,实际项目中可替换为Tesseract调用:
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class ArchiveService {
@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() {
minioClient = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
public WaterLicenseArchive uploadLicense(MultipartFile file, String licenseNo, String enterpriseName) throws Exception {
// 1. 确保Bucket存在
boolean found = minioClient.bucketExists(bucketName);
if (!found) {
minioClient.makeBucket(bucketName);
}
// 2. 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = UUID.randomUUID().toString() + suffix;
// 3. 上传文件到MinIO
InputStream inputStream = file.getInputStream();
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
// 4. 模拟OCR识别内容(实际生产环境此处调用Tesseract API)
String mockOcrContent = "识别结果:取水许可证号 " + licenseNo + ",单位 " + enterpriseName + "。有效期至2025年12月31日。";
// 5. 构建实体并保存
WaterLicenseArchive archive = new WaterLicenseArchive();
archive.setLicenseNo(licenseNo);
archive.setEnterpriseName(enterpriseName);
archive.setFileName(originalFilename);
archive.setFilePath(objectName);
archive.setFileSize(file.getSize());
archive.setOcrContent(mockOcrContent);
archive.setCreateTime(LocalDateTime.now());
return archive;
}
}
创建ArchiveController,提供上传接口和列表查询接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
@RequestMapping("/api/archives")
public class ArchiveController {
@Autowired
private ArchiveService archiveService;
@Autowired
private WaterLicenseRepository repository;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file,
@RequestParam("licenseNo") String licenseNo,
@RequestParam("enterpriseName") String enterpriseName) {
try {
WaterLicenseArchive archive = archiveService.uploadLicense(file, licenseNo, enterpriseName);
repository.save(archive);
return "归档成功,证照ID:" + archive.getId();
} catch (Exception e) {
e.printStackTrace();
return "归档失败:" + e.getMessage();
}
}
@GetMapping("/list")
public List list() {
return repository.findAll();
}
}
前端使用Vue 3和Element Plus,提供直观的文件上传和档案列表展示。
在终端执行:npm create vue@latest water-archive-ui。进入目录后执行npm install,并安装Element Plus和Axios:npm install element-plus axios。
修改src/App.vue,编写完整的上传与列表展示逻辑。包含拖拽上传区域和数据表格:
取水许可证数字档案馆
拖拽文件到此处或 点击上传
查看OCR内容
同时,在src/main.js中引入Element Plus样式和组件:
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')
至此,所有代码与配置已完成。接下来进行启动验证。
在IDEA中运行Spring Boot主类,或在项目根目录执行Maven打包命令:mvn clean package -DskipTests,然后运行:java -jar target/xxx.jar。确保控制台无报错,端口8080监听成功。
在water-archive-ui目录下执行:npm run dev。终端会输出本地访问地址,通常是 http://localhost:5173。
1. 打开浏览器访问前端地址。
2. 在表单中输入测试用的“许可证号”和“单位名称”。
3. 准备一张JPG或PNG格式的测试图片,拖拽到上传区域。
4. 观察页面提示“上传归档成功”,下方表格应立即刷新出刚才上传的数据。
5. 点击表格中的“查看OCR内容”按钮,应弹出模拟的识别文本。
6. 登录MinIO控制台 http://localhost:9000,进入water-licenses桶,确认文件已物理存储。