1. 项目背景与需求分析
在企事业单位日常运营中,档案管理一直是个既基础又关键的环节。记得去年帮本地一家中型企业做技术咨询时,他们的档案室堆满了纸质文件,每次查找合同都要花上半天时间。这种场景正是电子档案管理系统要解决的核心痛点。
传统档案管理存在三大硬伤:一是物理存储空间占用大,二是检索效率低下,三是存在安全隐患。我曾见过某公司因为档案室漏水导致重要合同损毁的案例,这促使我深入研究数字化解决方案。
本系统采用SpringBoot+Vue+MySQL技术栈,主要实现以下核心功能:
- 多维度档案分类管理(支持自定义分类体系)
- 基于RBAC模型的精细化权限控制
- 全文检索与高级查询功能
- 完整的操作日志审计追踪
- 可视化数据统计报表
2. 技术架构设计
2.1 整体架构设计
系统采用经典的前后端分离架构,这是我经过多个项目验证后的稳定选择。前端使用Vue 3组合式API开发,相比选项式API更利于复杂业务逻辑的组织。后端采用SpringBoot 2.7.x版本,这是目前企业级应用最稳定的LTS版本。
架构亮点在于:
- 文件存储采用"元数据+物理文件"分离模式
- 查询服务引入Elasticsearch作为二级缓存(非必须但推荐)
- 使用Spring Security OAuth2实现多端统一认证
2.2 数据库设计优化
原始设计中的三个核心表已经比较完善,我在实际落地时做了以下优化:
档案表增强设计:
sql复制ALTER TABLE archive_info ADD COLUMN file_size BIGINT COMMENT '文件大小(bytes)';
ALTER TABLE archive_info ADD COLUMN file_hash VARCHAR(64) COMMENT '文件SHA-256哈希值';
这两个字段对文件完整性校验和存储管理至关重要。
权限表扩展方案:
java复制// 使用位运算实现复合权限
public enum Permission {
READ(1), WRITE(2), DELETE(4), SHARE(8);
public static boolean hasPermission(int permissions, Permission permission) {
return (permissions & permission.value) == permission.value;
}
}
这种设计比简单的角色分类更灵活,适合需要细粒度控制的场景。
3. 核心功能实现
3.1 文件上传与存储
文件上传是系统的核心功能之一,我采用分段上传方案应对大文件场景:
java复制@PostMapping("/chunk-upload")
public ResponseEntity<?> chunkUpload(
@RequestParam MultipartFile file,
@RequestParam String chunkHash,
@RequestParam Integer chunkIndex,
@RequestParam Integer totalChunks) {
// 验证文件哈希
String computedHash = DigestUtils.sha256Hex(file.getBytes());
if(!chunkHash.equals(computedHash)){
return ResponseEntity.badRequest().body("文件校验失败");
}
// 存储分片到临时目录
String tempDir = System.getProperty("java.io.tmpdir");
Path chunkPath = Paths.get(tempDir, "uploads", chunkHash);
Files.createDirectories(chunkPath.getParent());
file.transferTo(chunkPath);
// 合并检查
if(chunkIndex == totalChunks - 1){
mergeChunks(totalChunks, chunkHash);
}
return ResponseEntity.ok().build();
}
重要提示:生产环境一定要配置上传文件类型白名单,我曾遇到过攻击者上传webshell的案例。
3.2 全文检索实现
基于MySQL的LIKE查询性能堪忧,我的解决方案是:
- 小型项目使用MySQL全文索引
sql复制ALTER TABLE archive_info ADD FULLTEXT INDEX ft_idx (archive_title, description);
- 中大型项目集成Elasticsearch
java复制@Repository
public interface ArchiveSearchRepository
extends ElasticsearchRepository<ArchiveDoc, String> {
@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content\"]}}")
Page<ArchiveDoc> search(String keyword, Pageable pageable);
}
实测对比:在10万条数据量下,ES的查询速度是MySQL的20倍以上。
4. 安全防护方案
4.1 权限控制实现
采用Spring Security + JWT的方案,关键配置如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/files/download/**")
.hasAnyAuthority("FILE_READ", "ADMIN")
.antMatchers("/api/files/upload")
.hasAnyAuthority("FILE_WRITE", "ADMIN")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(jwtDecoder());
return http.build();
}
}
4.2 审计日志增强
在基础操作日志上,我增加了变更内容快照功能:
java复制@Aspect
@Component
public class ArchiveLogAspect {
@Autowired
private LogService logService;
@AfterReturning(pointcut = "@annotation(com.example.ArchiveLog)",
returning = "result")
public void logOperation(JoinPoint jp, Object result) {
ArchiveOperation operation = ((ProceedingJoinPoint)jp).getSignature()
.getMethod().getAnnotation(ArchiveLog.class).value();
String beforeState = getArchiveState(jp.getArgs());
String afterState = JsonUtils.toJson(result);
logService.saveDiffLog(operation, beforeState, afterState);
}
}
这种实现可以完整记录数据变更前后的差异。
5. 部署与运维实践
5.1 容器化部署方案
推荐使用Docker Compose进行一体化部署:
yaml复制version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=yourstrongpassword
volumes:
mysql_data:
5.2 性能调优经验
在高并发场景下,我总结出以下优化点:
- 文件下载启用Nginx静态资源缓存
nginx复制location /files {
expires 7d;
add_header Cache-Control "public";
try_files $uri @backend;
}
- 使用Redis缓存热门档案的元数据
java复制@Cacheable(value = "archiveMeta", key = "#archiveId")
public ArchiveInfo getArchiveInfo(String archiveId) {
return archiveMapper.selectById(archiveId);
}
- 数据库连接池配置优化(以HikariCP为例)
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
6. 常见问题解决方案
6.1 文件上传中断处理
实现断点续传需要三个关键步骤:
- 前端计算文件分片哈希
- 服务端记录已接收分片
- 上传前先查询缺失分片
核心校验逻辑:
javascript复制// 前端检查接口
async function checkChunks(fileHash, totalChunks) {
const res = await axios.get(`/api/upload/check?hash=${fileHash}`);
return res.data.missingIndices || [];
}
6.2 权限缓存一致性问题
采用多级缓存失效策略:
- 用户权限变更时发送RabbitMQ事件
- 网关层监听事件清除Redis缓存
- 本地Caffeine缓存设置短TTL(5分钟)
java复制@EventListener
public void handlePermissionChange(PermissionChangeEvent event) {
redisTemplate.delete("user_perm:" + event.getUserId());
permissionCache.invalidate(event.getUserId());
}
7. 扩展与二次开发建议
7.1 与OA系统集成
通过Webhook实现档案变更通知:
java复制@Async
public void notifyOA(ArchiveEvent event) {
String payload = buildOAMessage(event);
restTemplate.postForEntity(
oaConfig.getWebhookUrl(),
payload,
String.class
);
}
7.2 移动端适配方案
建议采用Uniapp跨端方案,共享核心业务逻辑:
javascript复制// 共用Vuex模块
const archiveStore = {
state: () => ({
currentArchive: null
}),
mutations: {
setArchive(state, payload) {
state.currentArchive = payload
}
}
}
经过多个项目的实践验证,这套架构在保持扩展性的同时能满足大多数企业的档案管理需求。特别是在数据安全方面,建议企业根据等保要求补充文件加密存储、水印等增强措施。