监狱数字档案馆系统:从零搭建高可用档案存储与检索平台
一、系统架构与核心技术选型
监狱数字档案馆系统需要满足高安全性、高可靠性和长期保存三大核心需求。我们采用微服务架构,将系统拆分为独立可扩展的模块。
1.1 技术栈确定
以下是经过验证的生产级技术组合:
- 后端框架:Spring Boot 2.7.x + Spring Security
- 文件存储:MinIO(兼容S3协议的对象存储)
- 数据库:PostgreSQL 14(结构化数据)+ Elasticsearch 8.x(全文检索)
- 文件格式:OFD(国产版式文档,用于长期保存)+ PDF/A(归档PDF)
- 部署方式:Docker + Docker Compose
1.2 目录结构规划
创建项目根目录并初始化以下结构:
``` prison-archive-system/ ├── docker-compose.yml ├── config/ │ ├── application.yml │ └── nginx.conf ├── backend/ │ ├── src/main/java/com/prison/archive/ │ └── pom.xml ├── minio-data/ └── elasticsearch-data/ ```二、环境部署与依赖安装
2.1 Docker环境准备
在CentOS 7服务器上执行以下命令:
``` 卸载旧版本 sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 安装依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 添加Docker仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 安装Docker sudo yum install -y docker-ce docker-ce-cli containerd.io 启动Docker sudo systemctl start docker sudo systemctl enable docker 安装Docker Compose sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose ```2.2 基础服务部署
创建docker-compose.yml文件:
``` version: '3.8' services: postgres: image: postgres:14-alpine environment: POSTGRES_DB: prison_archive POSTGRES_USER: archive_admin POSTGRES_PASSWORD: YourStrongPassword123! volumes: - ./postgres-data:/var/lib/postgresql/data ports: - "5432:5432" restart: always minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: YourMinioPassword123! volumes: - ./minio-data:/data ports: - "9000:9000" - "9001:9001" restart: always elasticsearch: image: elasticsearch:8.9.0 environment: - discovery.type=single-node - ES_JAVA_OPTS=-Xms512m -Xmx512m - xpack.security.enabled=false volumes: - ./elasticsearch-data:/usr/share/elasticsearch/data ports: - "9200:9200" restart: always ```启动所有服务:
``` sudo docker-compose up -d ```三、核心功能实现
3.1 文件上传与存储
创建MinIO配置类:
``` @Configuration public class MinioConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } } ```文件上传服务实现:
``` @Service public class ArchiveService { @Autowired private MinioClient minioClient; public String uploadFile(MultipartFile file, String prisonerId) { // 生成唯一文件名 String originalFilename = file.getOriginalFilename(); String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); String objectName = prisonerId + "/" + UUID.randomUUID() + fileExtension; // 创建存储桶(如果不存在) try { boolean found = minioClient.bucketExists(BucketExistsArgs.builder() .bucket("prison-archive").build()); if (!found) { minioClient.makeBucket(MakeBucketArgs.builder() .bucket("prison-archive").build()); } // 上传文件 minioClient.putObject( PutObjectArgs.builder() .bucket("prison-archive") .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); return objectName; } catch (Exception e) { throw new RuntimeException("文件上传失败", e); } } } ```3.2 档案元数据管理
创建档案元数据表:
``` CREATE TABLE archive_metadata ( id BIGSERIAL PRIMARY KEY, prisoner_id VARCHAR(50) NOT NULL, file_name VARCHAR(255) NOT NULL, file_path VARCHAR(500) NOT NULL, file_size BIGINT NOT NULL, file_type VARCHAR(50) NOT NULL, upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, upload_user VARCHAR(50) NOT NULL, security_level INTEGER DEFAULT 1, -- 1:普通 2:机密 3:绝密 retention_years INTEGER NOT NULL, -- 保留年限 is_encrypted BOOLEAN DEFAULT false, checksum VARCHAR(64) NOT NULL, -- 文件校验和 INDEX idx_prisoner_id (prisoner_id), INDEX idx_upload_time (upload_time) ); ```3.3 全文检索实现
配置Elasticsearch索引:
``` PUT /prison-archive { "mappings": { "properties": { "prisonerId": {"type": "keyword"}, "fileName": {"type": "text", "analyzer": "ik_max_word"}, "content": {"type": "text", "analyzer": "ik_max_word"}, "uploadTime": {"type": "date"}, "securityLevel": {"type": "integer"}, "fileType": {"type": "keyword"} } } } ```
创建文档索引服务:
``` @Service public class SearchService { @Autowired private RestHighLevelClient elasticsearchClient; public void indexDocument(ArchiveDocument doc) { IndexRequest request = new IndexRequest("prison-archive") .id(doc.getId()) .source( "prisonerId", doc.getPrisonerId(), "fileName", doc.getFileName(), "content", extractTextContent(doc.getFilePath()), "uploadTime", doc.getUploadTime(), "securityLevel", doc.getSecurityLevel(), "fileType", doc.getFileType() ); elasticsearchClient.index(request, RequestOptions.DEFAULT); } private String extractTextContent(String filePath) { // 使用Tika提取文本内容 Tika tika = new Tika(); try { return tika.parseToString(new File(filePath)); } catch (Exception e) { return ""; } } } ```四、安全与权限控制
4.1 基于角色的访问控制
创建权限配置:
``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/admin/").hasRole("ADMIN") .antMatchers("/api/archive/upload").hasAnyRole("ARCHIVIST", "ADMIN") .antMatchers("/api/archive/search").hasAnyRole("OFFICER", "ARCHIVIST", "ADMIN") .antMatchers("/api/archive/download").hasAnyRole("OFFICER", "ARCHIVIST", "ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll() .and() .csrf().disable(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ```4.2 文件加密存储
实现AES加密工具类:
``` @Component public class FileEncryptor { private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String SECRET_KEY = "Your256BitSecretKeyHere"; // 实际使用中从配置读取 public byte[] encryptFile(byte[] fileData) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); IvParameterSpec iv = new IvParameterSpec(new byte[16]); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); return cipher.doFinal(fileData); } public byte[] decryptFile(byte[] encryptedData) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "AES"); IvParameterSpec iv = new IvParameterSpec(new byte[16]); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); return cipher.doFinal(encryptedData); } } ```五、系统监控与维护
5.1 健康检查接口
创建系统健康检查端点:
``` @RestController @RequestMapping("/api/health") public class HealthController { @Autowired private DataSource dataSource; @Autowired private MinioClient minioClient; @GetMapping public ResponseEntity