上市公司档案管理系统:从零构建可落地的技术方案
一、技术选型与项目初始化
1.1 核心架构选择
采用前后端分离架构,后端使用Spring Boot 3.1.5,前端使用Vue 3.3.4。数据库选择PostgreSQL 15,文档存储使用MinIO对象存储。
1.2 开发环境搭建
安装JDK 17:
```bash Ubuntu/Debian sudo apt update sudo apt install openjdk-17-jdk CentOS/RHEL sudo yum install java-17-openjdk-devel 验证安装 java -version ```安装Node.js 18.17.0:
```bash 使用nvm安装 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash source ~/.bashrc nvm install 18.17.0 nvm use 18.17.0 ```1.3 项目创建
后端项目创建:
```bash 使用Spring Initializr创建 curl https://start.spring.io/starter.zip \ -d type=maven-project \ -d language=java \ -d bootVersion=3.1.5 \ -d baseDir=archive-backend \ -d groupId=com.company.archive \ -d artifactId=archive-system \ -d name=ArchiveSystem \ -d description="上市公司档案管理系统" \ -d packageName=com.company.archive.system \ -d packaging=jar \ -d javaVersion=17 \ -d dependencies=web,data-jpa,postgresql,security,validation \ -o archive-backend.zip unzip archive-backend.zip ```前端项目创建:
```bash npm create vue@latest archive-frontend cd archive-frontend npm install ```二、数据库设计与配置
2.1 PostgreSQL安装与配置
安装PostgreSQL 15:
```bash Ubuntu/Debian sudo apt install postgresql-15 postgresql-contrib-15 CentOS/RHEL sudo dnf install postgresql15-server postgresql15-contrib sudo /usr/pgsql-15/bin/postgresql-15-setup initdb sudo systemctl start postgresql-15 ```创建数据库和用户:
```sql -- 以postgres用户登录 sudo -u postgres psql -- 创建数据库 CREATE DATABASE archive_db ENCODING 'UTF8'; -- 创建用户并授权 CREATE USER archive_user WITH PASSWORD 'StrongPassword123!'; GRANT ALL PRIVILEGES ON DATABASE archive_db TO archive_user; -- 创建扩展(用于全文搜索) \c archive_db CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin; ```2.2 核心表结构设计
创建档案相关表:
```sql -- 公司基本信息表 CREATE TABLE companies ( id BIGSERIAL PRIMARY KEY, stock_code VARCHAR(20) NOT NULL UNIQUE, company_name VARCHAR(200) NOT NULL, listed_date DATE NOT NULL, industry VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 档案分类表 CREATE TABLE archive_categories ( id BIGSERIAL PRIMARY KEY, category_code VARCHAR(50) NOT NULL UNIQUE, category_name VARCHAR(100) NOT NULL, parent_id BIGINT REFERENCES archive_categories(id), retention_years INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 档案主表 CREATE TABLE archives ( id BIGSERIAL PRIMARY KEY, archive_number VARCHAR(100) NOT NULL UNIQUE, title VARCHAR(500) NOT NULL, company_id BIGINT NOT NULL REFERENCES companies(id), category_id BIGINT NOT NULL REFERENCES archive_categories(id), document_type VARCHAR(50) NOT NULL, confidential_level VARCHAR(20) NOT NULL, storage_path TEXT NOT NULL, file_size BIGINT NOT NULL, file_hash VARCHAR(64) NOT NULL, upload_user_id BIGINT NOT NULL, upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, effective_date DATE, expiry_date DATE, status VARCHAR(20) DEFAULT 'ACTIVE', version INTEGER DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 档案版本表 CREATE TABLE archive_versions ( id BIGSERIAL PRIMARY KEY, archive_id BIGINT NOT NULL REFERENCES archives(id), version_number INTEGER NOT NULL, change_description TEXT, storage_path TEXT NOT NULL, file_hash VARCHAR(64) NOT NULL, created_by BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(archive_id, version_number) ); -- 创建索引 CREATE INDEX idx_archives_company ON archives(company_id); CREATE INDEX idx_archives_category ON archives(category_id); CREATE INDEX idx_archives_status ON archives(status); CREATE INDEX idx_archives_expiry ON archives(expiry_date); CREATE INDEX idx_archives_number ON archives(archive_number); CREATE INDEX idx_archives_upload_time ON archives(upload_time); ```三、后端核心功能实现
3.1 项目依赖配置

pom.xml关键依赖:
```xml3.2 应用配置文件
application.yml配置:
```yaml server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:postgresql://localhost:5432/archive_db username: archive_user password: StrongPassword123! driver-class-name: org.postgresql.Driver hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000 jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect hibernate: ddl-auto: update show-sql: false properties: hibernate: format_sql: true jdbc: batch_size: 20 servlet: multipart: max-file-size: 500MB max-request-size: 500MB archive: storage: type: minio minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket-name: archive-documents local: path: /data/archive/files security: jwt: secret: your-jwt-secret-key-at-least-256-bits-long expiration: 86400000 ```3.3 文件上传服务实现
创建MinIO配置类:
```java @Configuration public class MinioConfig { @Value("${archive.storage.minio.endpoint}") private String endpoint; @Value("${archive.storage.minio.access-key}") private String accessKey; @Value("${archive.storage.minio.secret-key}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } } ```文件存储服务实现:
```java @Service @Slf4j public class FileStorageService { @Value("${archive.storage.minio.bucket-name}") private String bucketName; private final MinioClient minioClient; public FileStorageService(MinioClient minioClient) { this.minioClient = minioClient; initializeBucket(); } private void initializeBucket() { try { boolean exists = minioClient.bucketExists( BucketExistsArgs.builder() .bucket(bucketName) .build() ); if (!exists) { minioClient.makeBucket( MakeBucketArgs.builder() .bucket(bucketName) .build() ); // 设置bucket策略 String policy = """ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "", "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::%s/" ] } ] } """.formatted(bucketName); minioClient.setBucketPolicy( SetBucketPolicyArgs.builder() .bucket(bucketName) .config(policy) .build() ); } } catch (Exception e) { log.error("初始化MinIO存储桶失败", e); } } public String uploadFile(MultipartFile file, String filePath) { try { // 生成唯一文件名 String originalFilename = file.getOriginalFilename(); String fileExtension = getFileExtension(originalFilename); String uniqueFileName = UUID.randomUUID() + fileExtension; String objectName = filePath + "/" + uniqueFileName; // 计算文件哈希 String fileHash = calculateFileHash(file); // 上传到MinIO minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); return objectName; } catch (Exception e) { throw new RuntimeException("文件上传失败", e); } } private String getFileExtension(String filename) { if (filename == null || !filename.contains(".")) { return ""; } return filename.substring(filename.lastIndexOf(".")); } private String calculateFileHash(MultipartFile file) throws IOException { try (InputStream is = file.getInputStream()) { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[8192]; int read; while ((read = is.read(buffer)) > 0) { digest.update(buffer, 0, read); } byte[] hashBytes = digest.digest(); return HexFormat.of().formatHex(hashBytes); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("哈希算法不可用", e); } } } ```3.4 档案上传接口实现
档案上传DTO:
```java @Data public class ArchiveUploadDTO { @NotBlank(message = "档案编号不能为空") private String archiveNumber; @NotBlank(message = "档案标题不能为空") private String title; @NotNull(message = "公司ID不能为空") private Long companyId; @NotNull(message = "分类ID不能为空") private Long categoryId; @NotBlank(message = "文档类型不能为空") private String documentType; @NotBlank(message = "密级不能为空") private String confidentialLevel; private LocalDate effectiveDate; private LocalDate expiryDate; @NotNull(message = "文件不能为空") private MultipartFile file; } ```档案上传控制器:
```java @RestController @RequestMapping("/api/archives") @RequiredArgsConstructor public class ArchiveController { private final ArchiveService archiveService; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity四、前端实现
4.1 项目依赖安装
安装必要依赖:
```bash cd archive-frontend npm install axios@1.5.0 element-plus@2.3.8 @element-plus/icons-vue@2.1.0 vue-router@4.2.4 pinia@2.1.6 ```4.2 档案上传组件实现
创建ArchiveUpload.vue:
```vue
将文件拖到此处,或点击上传
支持PDF、Word、Excel、图片等格式,最大500MB