1. 为什么我们需要简化审批流程?
作为一名Java开发者,我经历过太多繁琐的请假审批流程。传统方式往往需要编写大量重复代码:创建审批表、设计状态机、实现审批逻辑、处理回调通知...整个过程下来,光是基础代码就要写几百行。更糟的是,每次业务规则变更都需要重新修改代码,测试和上线流程又得走一遍。
SpringBoot生态中其实早有成熟的解决方案。工作流引擎(Workflow Engine)就是专门为解决这类业务流程而生的工具。它通过可视化配置和规则引擎,将业务流程从代码中解耦出来。当审批规则变化时,只需调整配置,无需修改代码。
实际案例:在我参与过的一个电商后台系统中,促销活动审批流程经历了17次变更。如果采用传统编码方式,每次变更都需要1-2天开发测试。而使用工作流引擎后,产品经理自己就能在界面上调整流程,变更时间缩短到10分钟。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Activiti vs Flowable:工作流引擎选型
目前Java领域主流的工作流引擎有两个选择:
| 特性 | Activiti | Flowable |
|---|---|---|
| 出身 | Alfresco公司维护 | Activiti原团队分支开发 |
| 社区活跃度 | 较高 | 非常高 |
| BPMN 2.0支持 | 完整 | 完整且扩展 |
| SpringBoot集成 | 官方starter | 官方starter |
| 性能 | 中等 | 优化更好 |
| 动态流程 | 有限支持 | 强大支持 |
| 学习曲线 | 平缓 | 略陡峭 |
经过实际项目验证,我推荐新项目选择Flowable。它不仅性能更优,对动态流程的支持也更好。比如我们可以实时修改进行中的审批流程,这在Activiti中很难实现。
2.1 核心依赖引入
在SpringBoot项目中集成Flowable只需添加以下依赖:
xml复制<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Flowable会自动创建28张数据库表来存储流程定义和运行时数据。建议单独准备一个数据库实例,因为工作流产生的数据量会随着业务增长快速膨胀。
3. 用BPMN设计请假审批流程
业务流程模型与标记法(BPMN)是工作流引擎的标准建模语言。即使没有专业工具,我们也能用简单的XML定义流程:
xml复制<process id="leaveApproval" name="请假审批流程">
<startEvent id="start"/>
<userTask id="applyLeave" name="提交请假申请"
flowable:assignee="${applicant}"/>
<sequenceFlow sourceRef="start" targetRef="applyLeave"/>
<userTask id="leaderApprove" name="主管审批"
flowable:candidateGroups="leader"/>
<sequenceFlow sourceRef="applyLeave" targetRef="leaderApprove"/>
<exclusiveGateway id="decisionGateway"/>
<sequenceFlow sourceRef="leaderApprove" targetRef="decisionGateway"/>
<sequenceFlow sourceRef="decisionGateway" targetRef="hrRecord"
flowable:condition="${approved}"/>
<sequenceFlow sourceRef="decisionGateway" targetRef="notifyReject"
flowable:condition="${!approved}"/>
<userTask id="hrRecord" name="HR备案"
flowable:candidateGroups="hr"/>
<endEvent id="endApproved"/>
<sequenceFlow sourceRef="hrRecord" targetRef="endApproved"/>
<serviceTask id="notifyReject" name="发送拒绝通知"
flowable:class="com.example.LeaveRejectNotifier"/>
<endEvent id="endRejected"/>
<sequenceFlow sourceRef="notifyReject" targetRef="endRejected"/>
</process>
这个流程定义了:
- 员工提交申请
- 主管审批(通过/拒绝)
- 通过则HR备案,拒绝则发送通知
3.1 可视化设计工具
对于复杂流程,推荐使用Flowable Modeler进行可视化设计。它提供:
- 拖拽式流程设计界面
- 实时流程校验
- 表单设计器
- 测试模拟器
安装也很简单,官方Docker镜像开箱即用:
bash复制docker run -p 8080:8080 flowable/all-in-one
4. 一行代码启动审批流程
核心代码比想象中简单得多:
java复制@RestController
public class LeaveController {
@Autowired
private RuntimeService runtimeService;
@PostMapping("/leave/apply")
public String applyLeave(@RequestBody LeaveRequest request) {
Map<String, Object> variables = new HashMap<>();
variables.put("applicant", request.getUserId());
variables.put("days", request.getDays());
variables.put("reason", request.getReason());
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveApproval", variables);
return "流程已启动,ID:" + instance.getId();
}
}
这就是标题所说的"一行代码"精髓——runtimeService.startProcessInstanceByKey()。但为了让这行代码真正工作,我们需要做好前置准备:
- 将BPMN文件放在
resources/processes/目录 - 配置Flowable自动部署
- 实现审批人分配逻辑
4.1 自动部署配置
在application.yml中添加:
yaml复制flowable:
check-process-definitions: true
process-definition-location-prefix: classpath:/processes/
process-definition-suffixes: .bpmn20.xml
这样SpringBoot启动时会自动加载所有BPMN流程定义。
5. 审批操作与任务查询
审批环节的核心API同样简洁:
java复制@RestController
public class ApprovalController {
@Autowired
private TaskService taskService;
// 查询待办任务
@GetMapping("/tasks")
public List<Task> listTasks(@RequestParam String userId) {
return taskService.createTaskQuery()
.taskCandidateOrAssigned(userId)
.list();
}
// 处理审批
@PostMapping("/approve")
public void approve(@RequestBody ApprovalRequest request) {
Map<String, Object> variables = new HashMap<>();
variables.put("approved", request.isApproved());
taskService.complete(request.getTaskId(), variables);
}
}
实际项目中,我们通常会封装更丰富的查询方法,比如:
java复制// 查询我发起的流程
runtimeService.createProcessInstanceQuery()
.startedBy(userId)
.list();
// 查询历史审批记录
historyService.createHistoricProcessInstanceQuery()
.startedBy(userId)
.finished()
.list();
6. 高级功能与性能优化
6.1 动态分配审批人
实际场景中,审批人可能要根据规则动态确定。有两种实现方式:
- 使用监听器:
java复制public class DynamicAssigneeListener implements TaskListener {
@Override
public void notify(DelegateTask task) {
String dept = (String) task.getVariable("department");
String leader = departmentService.getLeader(dept);
task.setAssignee(leader);
}
}
- 使用表达式:
xml复制<userTask id="leaderApprove" name="主管审批"
flowable:assignee="${departmentService.getLeader(department)}"/>
6.2 性能优化建议
- 异步执行:配置服务任务异步执行
xml复制<serviceTask id="notify" flowable:async="true" .../>
- 批量操作:对于批量审批场景,使用Bulk API
java复制taskService.createBulkTaskCompletion()
.taskIds(taskIds)
.variable("approved", true)
.complete();
- 历史数据归档:定期清理历史数据
sql复制DELETE FROM ACT_HI_TASKINST WHERE END_TIME_ < DATE_SUB(NOW(), INTERVAL 3 MONTH);
7. 监控与管理
Flowable自带管理控制台,通过简单配置即可启用:
yaml复制flowable:
admin:
enabled: true
rest:
enabled: true
访问/flowable-admin可以看到:
- 运行的流程实例
- 待办任务统计
- 流程定义版本管理
- 历史数据分析
对于生产环境,建议集成Prometheus监控:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metrics() {
return registry -> registry.config().commonTags("application", "leave-approval");
}
8. 常见问题与解决方案
问题1:流程定义修改后不生效?
解决方案:确保修改后版本号递增,SpringBoot会重新部署。或者调用
repositoryService.setDeploymentKey()强制更新。
问题2:高并发时出现乐观锁异常?
解决方案:配置重试机制或使用
BulkTaskCompletion批量操作。
问题3:如何实现会签(多人审批)?
解决方案:使用多实例任务:
xml复制<userTask id="groupApprove">
<multiInstanceLoopCharacteristics
flowable:isSequential="false"
flowable:collection="${approvers}"
flowable:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
问题4:审批超时自动处理?
解决方案:配置边界定时事件:
xml复制<boundaryEvent id="timeout" attachedToRef="leaderApprove">
<timerEventDefinition>
<timeDuration>PT24H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
在实际项目中,我建议将工作流引擎与业务系统松耦合。可以通过事件驱动架构,在工作流引擎和业务系统之间引入消息队列(如Kafka),这样即使工作流引擎暂时不可用,也不会影响核心业务功能。
