最近在做 Workforce Hub 的审批模块时,一个需求让我头疼了好几天——请假审批流程。
说白了就是一个"员工提交 → 主管审批 → 人事复核"的流程,听着挺简单的对吧?但架不住产品经理(导师)要求多:不同假期类型走不同审批链、三天以内免人事复核、超时自动提醒、驳回原因必填……
如果全用 if-else 硬编码状态机,写到后面我估计连自己都看不懂了。
后来硬着头皮引入了 Flowable,踩了不少坑,但也确实解决了问题。今天就来聊聊这个过程。
一、为什么需要工作流引擎?——几个 if-else 引发的惨案
其实一开始我没想用工作流引擎。请假审批嘛,不就是改个状态:
// 我一开始天真的想法
if (leave.getStatus() == PENDING) {
leave.setStatus(approved ? APPROVED : REJECTED);
leaveMapper.updateById(leave);
}
这代码跑了一周没问题。直到需求变成了这样:
年假:员工 → 主管 → 人事
事假:员工 → 主管
病假:员工 → 主管 → 人事(附诊断证明)
三天以内:跳过人事复核
主管超过 24 小时未审批 → 自动提醒
驳回后允许重新提交
血泪教训:当审批链开始分叉、节点开始有动态条件时,纯写 if-else 的代码复杂度是指数级增长的。更别提后续还要支持审批历史追溯、流程可视化这些需求。
这时候工作流引擎的价值就体现出来了——它把"流程怎么走"从业务代码里抽离出来,变成可配置的 BPMN 流程图。
二、为什么选 Flowable?——一个不太成熟的选择
市面上 Java 生态的工作流引擎主要有三个:Activiti、Flowable、Camunda。
为了做出选择,我对比了一下:
说实话,选 Flowable 很大程度上是因为它是 Activiti 原班人马出来做的——Activiti 5 的核心团队不满 Alfresco 的方向,出来另起炉灶。而且 Spring Boot 官方有 starter 支持,对咱们这种"能用就行"的学生项目来说最友好。
当然 Camunda 在企业级领域确实更强,但学习曲线太陡了。大三学生搞个课程项目,Flowable 够用了。
我的理解:Flowable 本质上就是把 BPMN 2.0(业务流程建模标注)这个标准规范用 Java 实现了一遍,然后包装成了 Spring Boot 能直接用的组件。
三、流程设计实例——请假审批的 BPMN 之旅
下面是我给 Workforce Hub 设计的请假审批流程,先看一张全景图:
整个流程分为三步:
第一步:AI 预审。这个比较特别——员工提交请假后,先不直接发给主管,而是过一个 AI 校验节点。比如检查剩余年假天数够不够、有没有重叠的请假记录、病假有没有附诊断证明。不符合规则的直接驳回并告诉原因,不用等主管手动发现。
这个设计思路来自我们项目的核心创新点——“Agent 深度嵌入业务链路”。AI 不只是聊天机器人,它实实在在地参与到了审批流程里。
第二步:部门主管审批。AI 校验通过后,流程到达主管节点。主管可以看到请假详情和 AI 预审结果,然后审批。
第三步:人事复核。如果请假天数超过 3 天,流程自动进入人事复核节点。不到 3 天的直接归档。
审批通过后,自动触发四个服务任务:考勤重算、余额扣减、站内通知、日历同步。
先看一下整体架构——Flowable 在 Spring Boot 里是怎么嵌进去的:
四、Flowable 与 Spring Boot 的整合——到底写了多少代码?
引入 Flowable 其实不复杂,加个依赖就行:
<!-- pom.xml -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.0</version>
</dependency>
启动 Spring Boot 后,Flowable 会自动创建 70+ 张表来管理流程定义、实例、任务、历史记录。小发现:这些表名都以 ACT_ 开头,跟 Activiti 一样的命名约定——毕竟同根同源。
核心代码其实就几个接口:
// 1. 启动一个请假流程
@PostMapping("/leave/submit")
public Result submitLeave(@RequestBody LeaveRequest req) {
// 构建流程变量
Map<String, Object> vars = new HashMap<>();
vars.put("leaveType", req.getLeaveType());
vars.put("leaveDays", req.getLeaveDays());
vars.put("applicantId", SecurityUtils.getCurrentUserId());
// 启动流程 - processDefinitionKey 对应 BPMN 文件的 process id
ProcessInstance pi = runtimeService
.startProcessInstanceByKey("leave-approval", vars);
return Result.ok(pi.getId());
}
// 2. 查询待办任务
@GetMapping("/tasks/todo")
public Result getTodoTasks() {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(SecurityUtils.getCurrentUserId())
.list();
return Result.ok(tasks);
}
// 3. 审批任务
@PostMapping("/tasks/{taskId}/complete")
public Result completeTask(@PathVariable String taskId,
@RequestBody ApprovalDto dto) {
Map<String, Object> vars = new HashMap<>();
vars.put("approved", dto.isApproved());
vars.put("comment", dto.getComment());
taskService.complete(taskId, vars);
return Result.ok();
}
踩坑经历一:一开始我直接用 taskService.complete() 就完了,没传流程变量——结果下个节点根本不知道这个任务是"通过"还是"驳回",网关判断直接报错。花了一个下午在 ACT_HI_VARINST 表里翻历史变量,才明白流程变量是 BPMN 里网关判断的唯一依据。
踩坑经历二:processDefinitionKey 写错了——我在 BPMN 文件里定义的 process id="leaveApproval"(驼峰),代码里写的是 "leave-approval"(横线分隔)。Flowable 不会报"找不到",而是直接抛一个巨长的异常栈,关键信息藏在第 37 行——我是搜了半天 StackOverflow 才定位到的。
踩坑经历三:多实例任务(比如同时抄送多人)默认是串行的——我以为是并发,结果等了三分钟第一个人没处理,第二个人就一直在排队。后来加了 flowable:async="true" 才解决问题。
五、AI Agent 怎么嵌入工作流?——这个比较有意思
传统工作流引擎的节点是固定的——用户任务、服务任务、网关。但我们在 Flowable 的基础上做了一个扩展:自定义任务类型——AI 审核节点。
先看链路——AI Agent 是怎么嵌进 Flowable 的:
实现方式是在 BPMN 流程里定义一个 Service Task,通过 JavaDelegate 调用 AI Agent:
@Component("aiLeaveChecker")
public class AILeaveCheckerDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// 获取流程变量
String applicantId = (String) execution.getVariable("applicantId");
int leaveDays = (int) execution.getVariable("leaveDays");
String leaveType = (String) execution.getVariable("leaveType");
// 调用 AI Agent 校验规则
AILeaveCheckResult result = aiAgentService.checkLeaveRules(
applicantId, leaveType, leaveDays);
// 将校验结果写入流程变量,供网关判断
execution.setVariable("aiCheckPassed", result.isPassed());
execution.setVariable("aiCheckReason", result.getReason());
}
}
然后在 BPMN 里这样引用:
<serviceTask id="aiCheck" name="AI预审"
flowable:delegateExpression="${aiLeaveChecker}"/>
网关会根据 aiCheckPassed 变量自动路由到"主管审批"或"驳回"分支——全程不需要写 if-else。
说实话,这个设计一开始我心里也没底——把 AI 的判断直接作为流程的路由依据,万一 AI 判断错了呢?
我在测试的时候遇到过:AI 预审说员工"年假余额不足",但员工截图过来显示还剩 3 天。排查了半天发现是缓存问题——余额扣减后 Redis 更新了,但 pgvector 里旧的员工文档还写着旧余额,AI 查到了旧数据。
后来加了两层兜底:
数据一致性:AI 检索时强制绕过缓存,直查 MySQL 最新余额
人工申诉:AI 校验不通过的情况下,员工可以走"人工申诉"通道,绕过 AI 节点直接到主管
申诉通道的流程大概是这样的:
小发现:加了申诉通道之后,流程图的复杂度直接翻倍。这让我意识到一个问题——BPMN 图不是越复杂越好,每个兜底分支都是在给维护埋坑。后来我把申诉逻辑抽成了独立的子流程(callActivity),主流程才瘦回来。
六、流程可视化——调试利器
Flowable 自带了一个流程设计器和监控面板,开发阶段可以直接看到流程跑到哪了:
当然生产环境一般不会开这个面板。我在项目里自己画了一个轻量的流程跟踪页面,让用户能看到自己提交的请假当前在哪个环节、谁在审批、预计多久能完成。
总结
我的收获:
工作流引擎的核心价值是"流程与代码解耦"——不是说用上 Flowable 代码就变少了,而是流程变可维护了。加一个新审批节点,改 BPMN 图就行,不用翻几千行 Java 代码
Flowable 的学习曲线比我想象中平——不用上来就啃 BPMN 2.0 完整规范,看懂开始/结束/任务/网关/连线这五个元素就够走完一个请假流程了
AI + 工作流的化学反应比我预期的强——Agent 从"聊天机器人"变成了"流程的守门员",这个体验很不一样
默认评论
Halo系统提供的评论