1. 项目背景与核心价值
去年接手某中型制造企业的信息化改造项目时,我深刻体会到传统纸质审批流程的效率瓶颈。采购申请平均耗时5.7天,跨部门协作文档版本混乱,这正是促使我开发这套OA系统的初衷。基于SSM框架的企业OA管理系统,通过标准化流程引擎和数字化协作平台,可将常规审批周期缩短至2小时内,实现90%以上业务流程的线上化管控。
这个系统最核心的价值在于:
- 流程可视化:所有审批流图形化展示,支持拖拽式流程设计
- 移动办公:响应式前端适配手机/平板,关键操作支持钉钉/企业微信集成
- 数据联动:报销单自动关联预算模块,超支申请触发预警机制
- 安全审计:所有操作留痕,支持版本追溯和数字签名
2. 技术架构设计解析
2.1 整体技术栈选型
选择SSM(Spring+SpringMVC+MyBatis)组合主要基于以下考量:
- Spring 5.2:控制反转和AOP特性完美支持模块化开发,实测单服务QPS可达1200+
- MyBatis 3.5:动态SQL生成能力比Hibernate更适合复杂业务查询,配合PageHelper分页插件效率提升40%
- 前端方案:放弃Vue/React选用JSP+JQuery的组合,主要考虑:
- 企业用户对IE11的强依赖
- 内部系统对SEO无要求
- 开发团队现有技术栈匹配度
2.2 数据库设计要点
MySQL 8.0采用以下优化策略:
sql复制CREATE TABLE `oa_process` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '使用雪花算法生成ID',
`process_key` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '流程定义键',
`version` int NOT NULL DEFAULT '1' COMMENT '乐观锁版本控制',
`form_data` json DEFAULT NULL COMMENT '动态表单JSON',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_key_version` (`process_key`,`version`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
关键设计决策:
- 所有业务表使用utf8mb4_bin排序规则,支持emoji和精确字符串匹配
- 流程数据采用JSON字段存储,避免过度范式化带来的联表查询开销
- 启用InnoDB压缩存储,实测空间占用减少35%
3. 核心功能实现细节
3.1 动态表单引擎
采用XML配置+Velocity模板的方案实现动态表单渲染:
java复制public class FormParser {
private static final Pattern FIELD_PATTERN =
Pattern.compile("<field:(.*?)\\s(.*?)/>");
public String parse(String template, Map<String, Object> model) {
StringBuffer result = new StringBuffer();
Matcher matcher = FIELD_PATTERN.matcher(template);
while (matcher.find()) {
String fieldType = matcher.group(1);
String attrs = parseAttrs(matcher.group(2));
String html = generateHtml(fieldType, attrs, model);
matcher.appendReplacement(result, html);
}
return result.toString();
}
}
开发中遇到的典型问题:
- XSS防护:采用OWASP ESAPI进行输出编码
jsp复制<input value="<%=ESAPI.encoder().encodeForHTML(data)%>"> - 性能优化:模板预编译缓存使渲染时间从120ms降至15ms
3.2 工作流引擎实现
基于状态模式设计轻量级流程引擎:
java复制public interface ProcessState {
void handle(ProcessContext context);
}
public class DraftState implements ProcessState {
@Override
public void handle(ProcessContext context) {
if ("SUBMIT".equals(context.getAction())) {
context.getProcess().setState(new PendingState());
// 生成审批任务
taskService.createApprovalTask(context);
}
}
}
状态转换规则存储在数据库表中,支持动态配置:
| 当前状态 | 触发动作 | 下一状态 | 执行操作 |
|---|---|---|---|
| DRAFT | SUBMIT | PENDING | 创建审批任务 |
| PENDING | APPROVE | APPROVED | 触发后续流程 |
4. 性能优化实战
4.1 数据库层优化
-
索引策略:
- 为所有外键字段创建索引
- 联合索引遵循最左匹配原则
- 使用
EXPLAIN分析执行计划,避免filesort
-
查询优化:
java复制@Select({ "SELECT * FROM oa_document", "WHERE delete_flag = 0", "AND create_time >= #{startDate}", "ORDER BY urgency DESC, create_time DESC", "LIMIT #{offset}, #{pageSize}" }) List<Document> queryDocuments(@Param("startDate") Date startDate, @Param("offset") int offset, @Param("pageSize") int pageSize);
4.2 缓存方案选型
采用多级缓存架构:
- 本地Caffeine缓存:热点数据(组织架构等)
java复制@Bean public CacheManager cacheManager() { CaffeineCacheManager manager = new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES)); return manager; } - Redis集群:分布式会话和流程实例数据
- 缓存击穿防护:使用Redisson分布式锁实现互斥重建
5. 安全防护体系
5.1 认证授权设计
RBAC模型扩展实现:
java复制@PreAuthorize("hasPermission(#docId, 'document', 'read')")
public Document getDocument(Long docId) {
return documentMapper.selectById(docId);
}
权限表达式支持:
- 部门数据权限:
deptId in (用户可访问部门列表) - 字段级权限:
@PostFilter("filterObject.status != 'SECRET'")
5.2 审计日志实现
采用Spring AOP实现操作日志:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(auditLog)",
returning = "result")
public void afterReturning(JoinPoint jp, AuditLog auditLog, Object result) {
String operation = auditLog.value();
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
AuditLogEntry entry = new AuditLogEntry();
entry.setIp(IpUtils.getIpAddr(request));
entry.setOperation(operation);
entry.setParams(JsonUtils.toJson(jp.getArgs()));
auditLogService.save(entry);
}
}
日志存储采用分表策略,按月份水平拆分,避免单表过大影响查询性能。
6. 典型问题排查实录
6.1 并发提交问题
现象:快速双击提交按钮导致重复流程实例
解决方案:
- 前端防抖处理
javascript复制$('#submitBtn').click(_.debounce(function(){ submitForm(); }, 500)); - 后端幂等控制
java复制@Transactional public void startProcess(String bizKey) { if(processInstanceDao.exists(bizKey)){ throw new BusinessException("流程已存在"); } // 创建流程... }
6.2 大文件上传中断
现象:超过100MB的附件上传经常失败
优化方案:
- 前端分片上传
javascript复制const chunkSize = 5 * 1024 * 1024; // 5MB const chunks = Math.ceil(file.size / chunkSize); - 后端合并校验
java复制public boolean mergeChunks(String fileMd5, int totalChunks) { // 校验所有分片MD5 for(int i=0; i<totalChunks; i++){ if(!checkChunkExist(fileMd5, i)){ return false; } } // 合并操作... }
这套系统经过三个月的实际运行,日均处理流程实例2300+,平均响应时间保持在800ms以下。最大的收获是认识到:企业级系统开发中,稳定性比炫技更重要,所有技术选型都应该服务于业务场景的实际需求。