会计数字档案馆系统从零到一部署实操手册
一、环境准备与依赖安装
在开始构建会计数字档案馆系统之前,必须先准备好基础运行环境。本系统采用Java作为后端核心语言,MySQL存储结构化数据,MinIO存储非结构化会计凭证文件。请严格按照以下步骤操作,确保环境版本一致,避免兼容性问题。
1. 基础软件安装
首先安装JDK 17和Maven 3.8+。在CentOS 7系统下,执行以下命令直接安装:
sudo yum install -y java-17-openjdk java-17-openjdk-devel
sudo yum install -y maven
安装完成后,验证版本:
java -version
mvn -v
2. 数据库与对象存储部署
为了实现快速落地,我们使用Docker容器化部署MySQL和MinIO。若未安装Docker,请先执行curl -fsSL https://get.docker.com | bash启动Docker服务。
创建一个docker-compose.yml文件,内容如下,直接复制保存:
```yaml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: archive_mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: accounting_archive
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
minio:
image: minio/minio:latest
container_name: archive_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin123
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
volumes:
mysql_data:
minio_data:
```
在文件所在目录执行启动命令:
docker-compose up -d
等待约30秒,待容器完全启动。此时MySQL已监听3306端口,MinIO控制台可通过http://服务器IP:9001访问,API端口为9000。
二、数据库表结构设计
会计数字档案馆的核心在于对电子会计凭证的结构化存储。我们需要设计一张表来记录文件元数据、会计期间以及凭证号等关键信息。请登录MySQL数据库创建表结构。
执行以下命令连接数据库:
docker exec -it archive_mysql mysql -uroot -proot123456 accounting_archive
在数据库命令行中执行以下SQL脚本,建立核心业务表:
```sql
CREATE TABLE `digital_archive` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`original_filename` varchar(255) NOT NULL COMMENT '原始文件名',
`storage_path` varchar(500) NOT NULL COMMENT 'MinIO存储路径',
`file_hash` varchar(64) NOT NULL COMMENT '文件SHA256哈希值,用于去重',
`file_size` bigint NOT NULL COMMENT '文件大小(字节)',
`account_period` varchar(7) NOT NULL COMMENT '会计期间,格式YYYY-MM',
`voucher_no` varchar(50) DEFAULT NULL COMMENT '凭证号',
`subject_code` varchar(20) DEFAULT NULL COMMENT '会计科目编码',
`upload_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '归档时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_file_hash` (`file_hash`),
KEY `idx_period` (`account_period`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会计数字档案表';
```
此表包含了会计档案特有的account_period(会计期间)和subject_code(科目编码),并利用file_hash建立唯一索引,防止同一份凭证重复归档。
三、后端项目搭建与配置
使用Spring Boot 3.x快速搭建后端服务。为了方便管理,我们创建一个Maven项目。
1. 项目初始化
创建项目目录结构,并编写pom.xml引入必要依赖。请确保包含Web、MyBatis、MinIO及Lombok。
以下是完整的pom.xml依赖部分:
```xml
```
2. 配置文件编写
在src/main/resources/application.yml中配置数据库连接及MinIO参数。请替换IP地址为你本机的实际IP(Docker环境通常用宿主机IP)。
```yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.1.100:3306/accounting_archive?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
minio:
endpoint: http://192.168.1.100:9000
accessKey: minioadmin
secretKey: minioadmin123
bucketName: accounting-files
```
四、核心业务代码实现
本部分实现文件上传、自动计算哈希值、写入对象存储及保存元数据到数据库的全流程逻辑。
1. MinIO配置类
创建MinioConfig.java,初始化MinIO客户端并自动创建存储桶(Bucket)。
```java
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")

private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.bucketName}")
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
@Bean
public String initBucket(MinioClient client) throws Exception {
boolean found = client.bucketExists(bucketName);
if (!found) {
client.makeBucket(bucketName);
}
return bucketName;
}
}
```
2. 实体类与Mapper
创建DigitalArchive.java实体类及对应的Mapper接口。
```java
import lombok.Data;
import java.util.Date;
@Data
public class DigitalArchive {
private Long id;
private String originalFilename;
private String storagePath;
private String fileHash;
private Long fileSize;
private String accountPeriod;
private String voucherNo;
private String subjectCode;
private Date uploadTime;
}
```
Mapper接口DigitalArchiveMapper.java:
```java
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DigitalArchiveMapper {
@Insert("INSERT INTO digital_archive(original_filename, storage_path, file_hash, file_size, account_period, voucher_no, subject_code) " +
"VALUES({originalFilename}, {storagePath}, {fileHash}, {fileSize}, {accountPeriod}, {voucherNo}, {subjectCode})")
int insert(DigitalArchive archive);
}
```
3. 文件上传与归档逻辑
创建ArchiveController.java,实现核心上传接口。此处包含SHA256计算和MinIO上传逻辑。
```java
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@RestController
@RequestMapping("/api/archive")
public class ArchiveController {
@Autowired
private MinioClient minioClient;
@Autowired
private DigitalArchiveMapper mapper;
@Value("${minio.bucketName}")
private String bucketName;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("accountPeriod") String period,
@RequestParam(value = "voucherNo", required = false) String voucherNo,
@RequestParam(value = "subjectCode", required = false) String subjectCode) {
try {
String hash = calculateSHA256(file.getInputStream());
String fileName = file.getOriginalFilename();
String objectName = period + "/" + hash + "-" + fileName;
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
DigitalArchive archive = new DigitalArchive();
archive.setOriginalFilename(fileName);
archive.setStoragePath(objectName);
archive.setFileHash(hash);
archive.setFileSize(file.getSize());
archive.setAccountPeriod(period);
archive.setVoucherNo(voucherNo);
archive.setSubjectCode(subjectCode);
archive.setUploadTime(LocalDateTime.now());
mapper.insert(archive);
return "归档成功,文件ID: " + objectName;
} catch (Exception e) {
e.printStackTrace();
return "归档失败: " + e.getMessage();
}
}
private String calculateSHA256(InputStream is) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) != -1) {
digest.update(buffer, 0, read);
}
byte[] hash = digest.digest();
return new BigInteger(1, hash).toString(16);
}
}
```
五、系统启动与实操验证
代码编写完毕后,进行编译打包和运行测试。
1. 编译与启动
在项目根目录下执行Maven打包命令:
mvn clean package -DskipTests
打包成功后,执行以下命令启动Spring Boot应用:
java -jar target/你的项目名-0.0.1-SNAPSHOT.jar
看到控制台输出Started Application in x.xxx seconds即表示启动成功。
2. 接口测试验证
使用curl命令模拟前端上传一份会计凭证文件。假设你当前目录有一份名为2023-10-voucher.pdf的文件。
执行以下命令进行测试:
curl -F "file=@2023-10-voucher.pdf" -F "accountPeriod=2023-10" -F "voucherNo=记-001" -F "subjectCode=6602" http://localhost:8080/api/archive/upload
预期结果:
若返回“归档成功,文件ID: 2023-10/xxxxx-2023-10-voucher.pdf”,说明文件已成功存入MinIO,且元数据已写入MySQL。
数据校验:
再次执行相同的上传命令。由于数据库中file_hash字段设有唯一索引,且代码逻辑中未处理重复异常,第二次上传会报错提示主键冲突。这证明了系统的去重机制已生效,符合会计档案“不可篡改、不重复归档”的严格要求。