革命历史档案数字化管理系统的全栈开发实战指南

一、系统架构与核心技术选型

本系统采用前后端分离架构,确保系统的可维护性和扩展性。前端使用Vue 3 + TypeScript构建响应式界面,后端采用Spring Boot 2.7提供RESTful API,数据库使用PostgreSQL 14存储结构化数据,MinIO用于档案文件的存储管理。

1.1 技术栈详细配置

项目基础环境要求:

  • Node.js 18.0+
  • Java 17
  • PostgreSQL 14+
  • Redis 7.0+

二、开发环境搭建

2.1 数据库初始化

创建数据库和基础表结构:


-- 创建数据库
CREATE DATABASE revolutionary_archive;
-- 创建档案表
CREATE TABLE archives (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
archive_number VARCHAR(50) UNIQUE NOT NULL,
title VARCHAR(500) NOT NULL,
content TEXT,
archive_date DATE NOT NULL,
classification_level VARCHAR(20),
physical_location VARCHAR(200),
digital_path VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建档案分类表
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
category_code VARCHAR(20) UNIQUE NOT NULL,
category_name VARCHAR(100) NOT NULL,
parent_id INTEGER REFERENCES categories(id)
);
-- 创建索引
CREATE INDEX idx_archive_number ON archives(archive_number);
CREATE INDEX idx_archive_date ON archives(archive_date);

2.2 后端项目配置

创建Spring Boot项目,配置application.yml:


server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://localhost:5432/revolutionary_archive
username: postgres
password: your_password
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
redis:
host: localhost
port: 6379
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
bucket: archive-files

三、核心功能实现

3.1 档案上传接口

实现支持大文件分片上传的REST接口:


@RestController
@RequestMapping("/api/archives")
public class ArchiveController {
@PostMapping("/upload")
public ResponseEntity uploadArchive(
@RequestParam("file") MultipartFile file,
@RequestParam("archiveNumber") String archiveNumber,
@RequestParam("title") String title) {
try {
// 验证文件类型
String contentType = file.getContentType();
if (!Arrays.asList("application/pdf", "image/jpeg", "image/png").contains(contentType)) {
return ResponseEntity.badRequest().body("不支持的文件类型");
}
// 生成唯一文件名
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
// 上传到MinIO
minioClient.putObject(
PutObjectArgs.builder()
.bucket("archive-files")
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(contentType)
.build()
);
// 保存到数据库
Archive archive = new Archive();
archive.setArchiveNumber(archiveNumber);
archive.setTitle(title);
archive.setDigitalPath(fileName);
archive.setFileSize(file.getSize());
archiveRepository.save(archive);
return ResponseEntity.ok("上传成功");
} catch (Exception e) {
return ResponseEntity.internalServerError().body("上传失败");
}
}
}

3.2 档案检索功能

实现全文检索和条件查询:


@Service
public class ArchiveSearchService {
@Autowired
private ArchiveRepository archiveRepository;
public Page searchArchives(String keyword,
LocalDate startDate,
LocalDate endDate,
String category,
Pageable pageable) {
Specification spec = Specification.where(null);
if (StringUtils.hasText(keyword)) {
spec = spec.and((root, query, cb) ->
cb.or(
cb.like(root.get("title"), "%" + keyword + "%"),
cb.like(root.get("content"), "%" + keyword + "%"),
cb.like(root.get("archiveNumber"), "%" + keyword + "%")
)
);
}
if (startDate != null) {
spec = spec.and((root, query, cb) ->
cb.greaterThanOrEqualTo(root.get("archiveDate"), startDate)
);
}
if (endDate != null) {
spec = spec.and((root, query, cb) ->
cb.lessThanOrEqualTo(root.get("archiveDate"), endDate)
);
}
return archiveRepository.findAll(spec, pageable);
}
}

四、前端界面开发

4.1 档案管理页面

使用Vue 3 + Element Plus构建管理界面:

革命历史档案数字化管理系统的全栈开发实战指南




4.2 档案上传组件

实现带进度显示的文件上传组件:




五、安全与权限控制

5.1 JWT认证配置

配置Spring Security实现基于JWT的认证:


@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.antMatchers("/api/auth/").permitAll()
.antMatchers("/api/archives/download/").permitAll()
.antMatchers("/api/archives/").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}

5.2 数据加密存储

敏感数据加密处理:


@Component
public class DataEncryptionService {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private final SecretKey secretKey;
public DataEncryptionService(@Value("${encryption.key}") String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
this.secretKey = new SecretKeySpec(decodedKey, "AES");
}
public String encrypt(String plaintext) throws Exception {
byte[] iv = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
byte[] encrypted = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(cipherText, 0, encrypted, iv.length, cipherText.length);
return Base64.getEncoder().encodeToString(encrypted);
}
}

六、部署与运维

6.1 Docker容器化部署

创建Docker Compose配置文件:


version: '3.8'
services:
postgres:
image: postgres:14
environment:
POSTGRES_DB: revolutionary_archive
POSTGRES_PASSWORD: your_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
minio:
image: minio/minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio_data:/data
ports:
- "9000:9000"
- "9001:9001"
backend:
build: ./backend
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/revolutionary_archive
SPRING_REDIS_HOST: redis
depends_on:
- postgres
- redis
- minio
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
postgres_data:
minio_data:

6.2 数据库备份脚本

创建自动备份脚本:


!/bin/bash
BACKUP_DIR="/backups/archives"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/archive_backup_$DATE.sql"
创建备份目录
mkdir -p $BACKUP_DIR
执行备份
pg_dump -U postgres -h localhost revolutionary_archive > $BACKUP_FILE
压缩备份文件
gzip $BACKUP_FILE
删除7天前的备份
find $BACKUP_DIR -name ".gz" -mtime +7 -delete
记录日志
echo "$(date): 备份完成,文件: ${BACKUP_FILE}.gz" >> /var/log/archive_backup.log

七、性能优化

7.1 数据库查询优化

添加查询缓存和索引优化:


@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
@Service
@CacheConfig(cacheNames = "archives")
public class ArchiveService {
@Cacheable(key = "archiveNumber")
public Archive findByArchiveNumber(String archiveNumber) {
return archiveRepository.findByArchiveNumber(archiveNumber);
}
@CacheEvict(key = "archiveNumber")
public void updateArchive(String archiveNumber, Archive archive) {
// 更新逻辑
}
}

7.2 文件处理优化

实现文件分片上传和断点续传:


@RestController
@RequestMapping("/api/upload")
public class ChunkUploadController {
@PostMapping("/chunk")
public ResponseEntity uploadChunk(
@RequestParam("chunk") MultipartFile chunk,
@RequestParam("chunkNumber") Integer chunkNumber,
@RequestParam("totalChunks") Integer totalChunks,
@RequestParam("identifier") String identifier) {
try {
// 保存分片文件
String chunkName = identifier + "_" + chunkNumber;
Path chunkPath = Paths.get("/tmp/uploads", chunkName);
Files.write(chunkPath, chunk.getBytes());
// 检查是否所有分片都上传完成
if (chunkNumber.equals(totalChunks - 1)) {
mergeChunks(identifier, totalChunks);
}
return ResponseEntity.ok().build();
} catch (IOException e) {
return ResponseEntity.internalServerError().body("分片上传失败");
}
}
private void mergeChunks(String identifier, int totalChunks) throws IOException {
Path outputPath = Paths.get("/tmp/uploads", identifier + ".complete");
try (OutputStream outputStream = new FileOutputStream(outputPath.toFile())) {
for (int i = 0; i < totalChunks; i++) {
Path chunkPath = Paths.get("/tmp/uploads", identifier + "_" + i);
Files.copy(chunkPath, outputStream);
Files.delete(chunkPath);
}
}
}
}
AI咨询
热线电话

028-85154420

15388110056

全国售前咨询电话

扫码咨询
安答联动微信公众号二维码

微信扫码关注安答联动

申请试用
热线电话
申请试用

安答联动档案管理系统