动态可配置的OA多级审批系统设计:从硬编码到灵活表结构的演进
在创业公司快速迭代的业务环境中,审批流程的频繁调整已成为常态。传统硬编码审批层级的开发方式,往往导致每次业务规则变更都需要重新修改代码、测试和部署。这种开发模式不仅效率低下,更会成为业务发展的技术瓶颈。本文将揭示如何通过两张核心表的设计,实现真正动态可配置的多级审批系统。
1. 硬编码审批流程的四大痛点
许多开发团队在初期为了快速上线,常采用硬编码方式实现审批流程。这种看似简单的方案,在实际运营中会暴露出严重问题:
- 维护成本高:每次增减审批层级都需要修改代码逻辑,甚至需要重新部署服务
- 业务适应性差:无法支持跨部门、跨项目的差异化审批规则配置
- 历史数据迁移困难:流程变更后,原有审批记录与新流程的兼容性问题
- 扩展性受限:难以支持会签、或签等复杂审批模式
-- 典型硬编码审批逻辑示例 IF @busiType = 'OVERTIME' BEGIN -- 第一级审批 INSERT INTO audit_log VALUES (@flowNo, '部门主管', 1) -- 第二级审批 INSERT INTO audit_log VALUES (@flowNo, '部门经理', 2) -- 固定三级审批 INSERT INTO audit_log VALUES (@flowNo, '总经理', 3) END提示:上例展示了典型的硬编码审批实现,这种写法将审批规则固化在代码中,任何调整都需要开发介入。
2. 动态审批系统的核心表设计
真正灵活的审批系统只需要两张核心表即可支撑任意复杂的审批场景。这种设计将审批流程的配置权交还给业务人员,实现技术架构与业务规则的解耦。
2.1 审批主表(audit_flow)
作为审批流程的入口表,记录审批实例的全局信息:
| 字段名 | 类型 | 描述 |
|---|---|---|
| flow_no | VARCHAR(50) | 审批流水号(业务唯一标识) |
| busi_type | VARCHAR(20) | 业务类型编码(如:OVERTIME) |
| title | NVARCHAR(100) | 审批标题 |
| applicant | VARCHAR(50) | 申请人ID |
| apply_time | DATETIME | 申请时间 |
| status | TINYINT | 整体状态(1待审 2进行中 3通过 4驳回) |
2.2 审批明细表(audit_flow_detail)
记录具体的审批环节和审批人信息,支持无限级联:
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | BIGINT | 自增主键 |
| flow_no | VARCHAR(50) | 关联主表流水号 |
| auditor | VARCHAR(50) | 审批人ID |
| audit_order | INT | 审批顺序(从1开始) |
| audit_type | TINYINT | 审批类型(1单人 2会签 3或签) |
| status | TINYINT | 当前状态(1待处理 2已通过 3已驳回) |
| comment | NVARCHAR(500) | 审批意见 |
| operate_time | DATETIME | 操作时间 |
-- 动态审批表示例数据 -- 主表记录 INSERT INTO audit_flow VALUES ( '202308011200001', 'OVERTIME', '张三月末加班申请', 'EMP1001', GETDATE(), 1 ) -- 明细表记录(三级动态审批) INSERT INTO audit_flow_detail VALUES (NULL, '202308011200001', 'MGR2001', 1, 1, 1, NULL, NULL), (NULL, '202308011200001', 'DIR3001', 2, 1, 1, NULL, NULL), (NULL, '202308011200001', 'CEO4001', 3, 2, 1, NULL, NULL)3. 动态审批的业务实现逻辑
基于核心表结构,审批系统的业务逻辑可以分为三个关键阶段:
3.1 流程发起阶段
- 前端收集业务表单数据(如加班时数、事由等)
- 根据业务类型加载预设审批流程模板
- 用户调整审批人和审批顺序(可选)
- 后端事务处理:
- 保存业务表单数据
- 写入审批主表记录
- 批量插入审批明细记录
- 发送首环节审批通知
# 伪代码示例:审批发起逻辑 def submit_approval(busi_data, approvers): with transaction.atomic(): # 保存业务数据 busi_record = BusiModel.objects.create(**busi_data) # 创建审批流程 flow = AuditFlow.objects.create( flow_no=generate_flow_no(), busi_type=busi_data['type'], title=f"{busi_data['user']}的{busi_data['type']}申请", applicant=busi_data['user'], status=1 ) # 添加审批环节 for idx, approver in enumerate(approvers, 1): AuditFlowDetail.objects.create( flow_no=flow.flow_no, auditor=approver['id'], audit_order=idx, audit_type=approver['type'], status=1 if idx > 1 else 2 # 首环节设为"待我审批" ) # 发送首环节通知 send_notification(approvers[0]['id'], flow.flow_no)3.2 审批处理阶段
审批人操作包含三个关键处理逻辑:
通过/驳回判断:
- 检查当前用户是否有待处理的审批任务
- 验证审批数据的版本一致性(防并发修改)
状态机转换:
- 更新当前审批环节状态
- 根据审批结果决定后续流程:
- 通过时:激活下一审批环节或完成流程
- 驳回时:终止整个审批流程
通知与回调:
- 发送下一环节审批通知或结果通知
- 触发业务状态更新回调
注意:在多环节审批中,需要特别处理"会签"(所有环节必须通过)和"或签"(任一环节通过即可)两种特殊审批模式。
3.3 流程监控与统计
完善的审批系统还应提供:
- 实时流程追踪:可视化展示当前审批进度和待办环节
- 审批时效分析:统计各环节平均处理时长
- 退回率监控:识别高频驳回点和业务瓶颈
- 代理人机制:支持审批人委托和自动转派
4. 高级应用场景与优化实践
4.1 条件审批流程实现
通过扩展审批模板表,可以实现基于业务属性的条件审批:
-- 审批模板表示例 CREATE TABLE audit_template ( id INT PRIMARY KEY, busi_type VARCHAR(20), condition_field VARCHAR(50), -- 条件字段名 condition_operator VARCHAR(10), -- 条件运算符 condition_value VARCHAR(100), -- 条件值 approvers JSON -- 审批人配置 ); -- 示例数据:不同时长的加班申请走不同流程 INSERT INTO audit_template VALUES (1, 'OVERTIME', 'hours', '<=', '4', '[{"role":"dept_leader"}]'), (2, 'OVERTIME', 'hours', '>', '4', '[{"role":"dept_leader"},{"role":"hr"}]')4.2 性能优化方案
当审批量达到百万级时,需考虑以下优化策略:
读写分离:
- 审批查询走从库
- 审批操作走主库
智能分表:
- 按业务类型分表
- 按时间范围分表
缓存策略:
- 热点审批模板缓存
- 用户待办列表缓存
// 审批列表查询优化示例 public Page<AuditTask> queryUserTasks(String userId, Pageable pageable) { String cacheKey = "user_tasks:" + userId; Page<AuditTask> tasks = cacheService.get(cacheKey); if (tasks == null) { tasks = auditRepository.findByAuditorAndStatus( userId, AuditStatus.PENDING, pageable); cacheService.put(cacheKey, tasks, 5, TimeUnit.MINUTES); } return tasks; }4.3 与BPM引擎的集成方案
对于超复杂审批场景,可以考虑集成开源BPM引擎:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自研核心表 | 轻量、易维护 | 功能有限 |
| Activiti | 功能全面 | 学习成本高 |
| Flowable | 性能优异 | 资源消耗大 |
| Camunda | 可视化强 | 架构复杂 |
在实际项目中,我们曾遇��一个特殊需求:市场活动审批需要同时经过技术、法务、财务三个部门的并行审批。这种场景下,我们在核心表基础上增加了parallel_group字段,通过同一审批顺序值实现并行审批的逻辑处理。