1. 业务系统为什么最终都会长出审批流模块
工作流开发这件事,说到底是每个业务系统绕不过去的一道坎。你做个OA系统要有请假审批,做个电商后台要有订单审核,做个内容平台要有发布审核,做个财务系统得有报销流程。最初大家都会觉得“不就是状态字段吗,我在业务表上加一个status,if套if就搞定了”,等流程复杂到需要会签、或签、驳回、跳转、条件分支、超时提醒的时候,那套if-else就会变成无人敢动的祖传代码。
我自己接过不少让人血压升高的项目,业务表里十几个状态字段互相纠缠,setState散落在各处,需求加一个“金额超过一万需要总监审批”这样的小改动,要翻遍多个service才能找到改哪。这不是代码能力问题,是状态机和流程逻辑耦合在了一起。工作流引擎解决的正是这个本质问题:把过程控制和业务逻辑解耦。流程怎么走、走到哪一步、谁能操作、什么条件下往哪走,这些事情交给流程引擎管;业务系统只关心“这个节点具体干什么活儿”。
Flowable是目前Java生态里用得最多的工作流引擎之一,它脱胎于Activiti 5,2016年前后Activiti创始人离开后另立门户创立的项目。经过这些年的迭代,Flowable在性能、扩展性、云原生支持上都走过不少弯路也积累了大量实践,被集成到各类OA、ERP、低代码平台里。说白了,Flowable做的事情就是三件:画得好(BPMN建模)、跑得动(流程实例执行)、管得住(历史、权限、监控)。
按我的经验,一个团队上手工作流开发,最合理的路径是:先搞懂BPMN规范里那几个关键符号的含义,再通过Spring Boot把Flowable跑起来,接着处理前后端交互和权限问题,最后才是深入到流程变量、监听器、子流程这些进阶玩法。这篇文章会按照这条实战路径走一遍,不走偏门,不堆概念,以能落地为首要目的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. BPMN建模前必须搞懂的几类核心元素
2.1 流程图的“语法”:不了解符号等于写代码不查文档
BPMN的全称是Business Process Model and Notation,业务流程建模与标注。它是图形化的流程描述标准,相当于给流程画了一套路标。Flowable引擎只认符合BPMN 2.0规范的流程文件,所以你在Flowable Modeler里拖拽出来的图,本质上是一个XML文件,里面描述着一个个元素以及它们之间的连接关系。
BPMN 2.0规范里的元素类型很多,但实际项目里高频使用的就那么几个。我把它们按务实的维度分一下类:
- 事件(Event):用圆圈表示,是流程的触发器。开始事件是圆圈单线,结束事件是圆圈粗线,中间事件用双圈。需要重点掌握的有定时器事件、消息事件、错误事件,后面排错会经常碰到。
- 活动(Activity):用圆角矩形表示,是流程中实际执行的工作。最常用的是用户任务(UserTask,带一个人形小图标)和服务任务(ServiceTask,带齿轮小图标)。用户任务指需要人来处理的事项,服务任务指系统自动执行的逻辑。
- 网关(Gateway):用菱形表示,用来控制流程的分支走向。这个后面单独展开。
- 顺序流(Sequence Flow):带箭头的实线,表示元素之间的流转路径。
- 泳道(Pool/Lane):用来区分不同角色或部门的职责范围。
还有一个容易忽略但很重要的点是:BPMN图里面看不到业务数据和表单。流程引擎只负责“走”,不负责“表单长什么样”。表单是你业务系统的事,流程变量的数据承载也是你业务系统的事。很多新手把流程图当业务架构图来画,在图上标各种字段,这是认知上的一个常见误区。
2.2 网关选择错了,流程图就会“乱走”
网关是BPMN里面最容易出错也最体现设计水平的地方。Flowable支持四种网关,分别是排他网关、并行网关、包容网关和事件网关。
排他网关(Exclusive Gateway)。菱形里面画一个叉,也可以用线条上标注条件来实现,但排他网关是显式收敛分支逻辑的。它的行为是:从上到下依次判断出口的条件表达式,第一个条件为true的出口生效,其他出口忽略。如果没有一个出口的条件为true,引擎会抛出异常。这一点很关键,我见过线上流程报错 “No outgoing sequence flow of the exclusive gateway... could be selected”,原因就是条件没覆盖全。设计时的经验法则是:排他网关必须保证至少一个分支命中,最好加一条默认流兜底。
并行网关(Parallel Gateway)。菱形里面画一个加号。它的逻辑是:拆分时所有出口全部执行,不判断任何条件;汇聚时等待所有分支都到达才继续往下走。并行网关最容易踩的坑是汇聚死锁——如果你在并行分支里面又加了排他网关,且某条分支可能被跳过,那么汇聚时它永远等不到那条分支到达,流程就卡死了。解决方法是:要么确保每条并行分支最终都走向汇聚,要么用包容网关代替。
包容网关(Inclusive Gateway)。菱形里面画一个圆圈。它介于排他和并行之间:拆分时,判断所有出口条件,所有为true的分支都执行(类似于并行但带条件过滤);汇聚时,等待所有被激活的分支都到达。包容网关能力很强,但引擎要做复杂的条件分析和合并等待,性能和可读性都不如前面两种。我的建议是:能拆成并行+排他的组合就用组合,包容网关作为最后的选择。
事件网关(Event Gateway)。菱形里面画一个圆圈带星号,用得相对少,它和中间捕获事件配合,表示“谁先触发就走谁那条分支”。典型的场景是超时提醒:人工审批节点和定时器事件节点二选一,谁先触发谁生效。
2.3 实战示例:一张请假审批图的正确画法
用一个最常见的请假流程把上面这些串起来。流程逻辑是:
- 员工提交请假申请;
- 请假天数小于等于3天,部门经理审批即可;
- 请假天数大于3天,部门经理审批后还需要总监审批;
- 任何人审批不通过就直接结束,“审批状态”置为驳回;
- 全部审批通过后,“请假状态”置为通过。
这个流程在Flowable Modeler里需要画的元素包括:一个开始事件、两个用户任务节点(经理审批、总监审批)、一个排他网关分流、一个结束事件。部门经理审批节点之后接排他网关,网关出两条分支,一条条件为 leaveDays <= 3 直接走向结束,另一条条件为 leaveDays > 3 走向总监审批节点,再由总监节点走向结束。
很多人在这里会问:驳回分支怎么画?两个选择:一是给每个审批节点加一条条件流指向结束节点;二是通过边界事件和网关组合做全局驳回。对于简单场景,我建议直接画一条驳回流向结束节点,因为驳回后的重新提交,应该由业务系统通过“重新发起一个新的流程实例”或“流程变量修改后重新流转”来处理,而不是在流程图上画一张复杂的返工循环图。流程图越复杂,维护成本成倍上升,能简单就不要画复杂。
2.4 条件表达式的写法与常见语法坑
顺序流上写的条件表达式,在Flowable里使用UEL(Unified Expression Language,统一表达式语言)。最常用的是在排他网关出口线上写 ${leaveDays > 3} 这样的表达式。这里的 leaveDays 来自流程变量(process variables),也就是你启动流程实例时通过 variables.put("leaveDays", 5) 传进去的值。
这里有几个容易踩的坑:
- 变量名不一致。流程图里写的是
${leaveDays},代码里put的时候写成了leaveDay,条件永远为false,最后触发网关无出口异常。 - 类型不匹配。
leaveDays传的是字符串"5",写条件是leaveDays > 3,在做比较时可能抛出异常,或者因为类型转换产生意外结果。建议在业务入口做类型校验,统一转成Integer。 - 表达式里调用方法不当。UEL允许
${userService.isManager(assignee)}这种Spring Bean方法调用,但这样的表达式会带来排查困难,而且流程审批时如果Bean挂了直接影响流程执行,我建议条件表达式里只用流程变量做比较,复杂判断放到业务代码里,把判断结果作为变量传入流程。
理解了这些,你在设计流程图时就不会把希望寄托在“画完就完事”上,而是会反过来思考:每个条件判断我用什么变量、变量的类型是什么、这个变量在哪个环节被写入。这才是不返工的核心。
3. Spring Boot项目接入Flowable的完整落地过程
3.1 版本选型:为什么我推荐Flowable 6.x
接入Flowable,第一步是选版本。Flowable的版本更迭里,6.x是目前生产环境最广泛使用的线。7.x已经推出了,但7.x在架构上做了不少调整,尤其是对Spring Boot 3和Jakarta命名空间的支持,社区和第三方组件的适配还处在爬坡期。如果你的项目已经用了Spring Boot 3.x,那可以考虑7.x;如果你还在Spring Boot 2.x的稳定期,6.7.2或者6.8.x都是比较靠谱的选择。
我在实际项目中用的组合是 Spring Boot 2.7.x + Flowable 6.7.2,这套组合在Maven中央仓库和各类文档里资料最齐全,遇到问题基本都能找到答案。不建议盲目追新版本,工作流引擎这种基础组件,稳定性和社区生态比新特性重要得多。
Maven依赖这样加:
xml复制<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
这一个starter会带入流程引擎的核心库、Spring Boot自动配置、REST API相关依赖。如果你的项目里已经有Spring Boot的Web依赖,建议把Flowable自带的REST接口通过配置关掉,避免端口冲突和接口暴露问题:
yaml复制flowable:
rest:
app:
authentication-mode: verify-privilege
database-schema-update: true
async-executor-activate: false
database-schema-update: true 表示启动时自动检查并更新Flowable的数据表结构。async-executor-activate: false 在生产环境建议设为false——异步执行器是Flowable用来跑定时任务、异步连续任务的后台线程池,默认开true的话,启动时就会有一堆线程跑起来,如果业务没有用到定时器事件,关掉可以减少无谓的资源消耗。
3.2 数据库初始化:建表策略与运维注意事项
Flowable使用一组以 ACT_ 前缀开头的数据表来存储流程定义、流程实例、任务、历史等信息。常见的有:
| 表前缀 | 存储内容 | 示例表 |
|---|---|---|
| ACT_RE_ | 流程定义与模型等静态资源 | ACT_RE_PROCDEF(流程定义表) |
| ACT_RU_ | 运行中的流程实例、任务、变量 | ACT_RU_TASK(待办任务表)、ACT_RU_VARIABLE(流程变量表) |
| ACT_HI_ | 历史数据,已结束或进行中实例的审计数据 | ACT_HI_TASKINST(任务历史表)、ACT_HI_PROCINST(流程实例历史表) |
| ACT_GE_ | 通用数据 | ACT_GE_BYTEARRAY(二进制资源表,流程文件存这里) |
有人第一次启动Flowable项目时,在控制台看到几十张表被自动创建,心里会咯噔一下。这是正常现象,Flowable会自动建表,不需要你手动写SQL。但有几个运维细节得注意:
- 生产环境不建议依赖自动建表。把
database-schema-update在正式环境设为false,在测试环境用自动更新机制生成一份完整的DDL脚本后,再由DBA审阅后执行到生产库。 - Flowable不区分库大小写的情况偶尔会坑人。MySQL环境要注意表名字段名的大小写敏感性配置,Linux的MySQL默认大小写敏感,如果之前装别的组件时动过小写表名配置,可能影响Flowable建表。
- 不允许手动去改Flowable的表结构。有些团队为了让某张表加个字段,直接ALTER TABLE,这在Flowable升级版本的时候会带来巨大的兼容性问题。业务数据需要关联流程数据的,请存在自己的业务表里,通过
businessKey关联,而不是动引擎的表。
3.3 部署流程定义:写代码与写配置的边界
流程设计器里画好的图,保存后是一个 .bpmn20.xml 文件。把这个文件放进 src/main/resources/processes/ 目录,项目启动时Flowable会自动扫描并部署。这是最省事的方式,适合流程数量不多、变更不频繁的场景。
但注意,自动部署有个隐患:每次重启应用,如果bpmn文件内容没变,Flowable不会重复部署;文件一旦改动,会生成一个新的流程定义版本。这本身是好事,版本管理是Flowable的重要能力。但如果你的流程文件里的流程key(processDefinitionKey)设计不合理,会导致同一个流程多个版本共存,启动流程实例时默认用最新版,而历史审批中的实例还在旧版本上跑。这是正常的行为,不过要理解它,否则你会困惑为什么“流程改了但新发起没生效”或者“老流程还在跑”。
除了自动部署,还有两种做法在项目里也应该掌握:
- 代码部署:把bpmn文件放在数据库或文件服务器上,通过
RepositoryService的createDeployment()接口动态部署。适合做“流程在线设计+发布”的管理后台。 - 基于Model的部署:先在Flowable Modeler里设计模型(Model),保存后通过
RepositoryService.addModel存入ACT_RE_MODEL,再调用接口部署。Jeecg这类低代码平台用的就是这种方式,流程在页面上配置好后,点“发布”按钮触发部署。
3.4 核心API实操:发起、查询、审批、驳回
跑通Flowable最核心的几个操作,这里给出可以直接抄作业的代码。假设我已经按上面方式部署了请假流程,流程定义key是 leaveProcess。
发起流程实例:
java复制@Service
public class LeaveWorkflowService {
private final RuntimeService runtimeService;
private final TaskService taskService;
public LeaveWorkflowService(RuntimeService runtimeService, TaskService taskService) {
this.runtimeService = runtimeService;
this.taskService = taskService;
}
public String startLeaveProcess(String userId, Integer leaveDays, String businessKey) {
Map<String, Object> variables = new HashMap<>();
variables.put("starter", userId);
variables.put("leaveDays", leaveDays);
// businessKey用来关联你自己的业务表主键,比如请假单ID
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("leaveProcess", businessKey, variables);
return processInstance.getId();
}
}
startProcessInstanceByKey 里的 businessKey 关联的通常是你业务表里的请假单ID,这样可以通过 runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult() 反查流程实例。
查询待办任务:
java复制public List<Task> getTodoTasks(String assignee) {
return taskService.createTaskQuery()
.taskAssignee(assignee)
.active()
.orderByTaskCreateTime()
.desc()
.list();
}
审批人字段的设置,我建议一开始就做对:如果流程是固定审批人,就直接在bpmn的UserTask节点上指定 flowable:assignee="zhangsan";如果审批人是动态算出来的,就在流程启动后、流转到该节点前,通过监听器或者代码设置 taskService.setAssignee(taskId, userId)。
审批(同意):
java复制public void completeTask(String taskId, Map<String, Object> variables) {
taskService.complete(taskId, variables);
}
调用 complete 方法后,Flowable会沿着当前用户任务的出线继续流转。如果下一步是网关,会在此时计算条件;如果下一步还是用户任务,会给新任务设置审批人。
审批(驳回):
驳回的本质也是完成任务,只不过走的是驳回分支。比如流程图上我画了一条“不通过”的条件流,条件是 ${approved == false},那审批的时候传入变量:
java复制Map<String, Object> variables = new HashMap<>();
variables.put("approved", false);
taskService.complete(taskId, variables);
这里一定要理清楚:Flowable没有“驳回”这个原生操作,驳回是流程设计者通过网关和分支画出来的业务动作。理解了这一点,你就不会被各种“怎么实现驳回”的问题绕晕。
3.5 数据库报错排查:从启动到运行最常见的三类问题
搜索热词里频繁出现“flowable工作流数据库报错”,说明这是新手集中踩坑的地方。我这里把实际遇到频率最高的三类情况列出来,并给出完整的排查思路。
第一类:启动时表不存在或列不存在。报错类似 Table 'act_ru_task' doesn't exist 或者 Unknown column 'xxx' in 'field list'。排查链路是:先确认 database-schema-update 的配置是否生效;如果没生效,检查数据源连接的用户是否有DDL权限;如果权限没问题,确认启动日志里有没有Flowable的建表SQL执行记录。实在不行,手动执行Flowable对应版本的SQL脚本也可以,脚本在依赖jar包的 org/flowable/db/create/ 目录下。
第二类:MySQL的字段长度或字符集问题。Flowable 6.x早期版本在MySQL 5.7以上遇到索引超出长度限制的报错,原因多半是字符集不是utf8mb4导致索引长度溢出。解决方法是:建库时用 utf8mb4 字符集,排序规则用 utf8mb4_0900_ai_ci(MySQL 8)或 utf8mb4_general_ci(MySQL 5.7)。
第三类:生产环境数据库连接池连接耗尽。Flowable自带的后台任务线程会定期扫描引擎表,如果连接池配置太小,业务高峰期容易报连接获取超时。建议为Flowable单独配置一个数据库连接池,或者把 async-executor-activate 在不需要的时候关掉。
4. 流程变量的生命周期与代码操作细节
4.1 流程变量是业务和流程之间的“中转站”
流程变量(Process Variables)是Flowable里相当核心的概念。它的本质是KV键值对,作用域范围可以是整个流程实例,也可以是某个任务级别。所有流程条件判断、表达式计算、监听器入参,都从变量里取值。
流程变量的存取方式大概有这些:
- 发起时传入:
startProcessInstanceByKey(key, businessKey, variables)。 - 任务完成时传入:
taskService.complete(taskId, variables),这些变量会合并到流程实例变量中。 - 运行时设置:
runtimeService.setVariable(processInstanceId, "key", value)。 - 任务级设置:
taskService.setVariableLocal(taskId, "key", value),加Local的是局部变量,只对当前任务可见,任务完成后局部变量不会自动转到流程实例级别。
实际项目里用得最多的是“流程实例级变量”,因为网关条件判断是全局的。任务级变量主要用在多人会签场景里记录每个人的审批意见——每个任务自己的意见不能互相覆盖,必须用局部变量。
4.2 业务表的关联设计:一张业务表还是两张业务表
接入Flowable时,业务数据怎么存,是每个团队都要做的一次架构决策。我在项目里见过两种模式:
模式A:主业务表 + 流程关联字段。在请假单表里加 process_instance_id 字段,存流程实例ID,业务数据和流程数据一对一或一对零关联。查询详情时,根据这个ID到Flowable查当前任务节点和历史记录。这种模式简单直观,适合绝大多数业务场景。
模式B:业务数据全部放流程变量。发起时把表单数据全塞进variables里,不在业务库建表。这种模式开发速度快,但数据检索、统计分析、报表导出都会受限制,只适合极轻量的审批场景,比如一个简单的确认流程。用这种模式要谨慎,因为流程变量在 ACT_RU_VARIABLE 表里是KV结构,SQL查询性能很一般。
我自己的习惯是模式A。在工作流相关代码里,我喜欢定义一个统一入口服务,对外暴露 startSignal、agree、reject、revoke 这样语义化强的方法,内部再调用上面的RuntimeService和TaskService。这样业务代码和Flowable API之间有一层防腐层,后面就算换引擎(比如换Camunda),改动也能集中在一个包里。
4.3 查询历史审批记录:给前端“流程轨迹”供数
审批流页面一般都要展示“谁在什么时间做了什么操作”,这个数据来自Flowable历史表。核心查询方式:
java复制public List<HistoricActivityInstance> getHistoryActivities(String processInstanceId) {
return historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
}
拿到的是节点级别的流转记录,包含了开始事件、用户任务、网关、结束事件等所有经过的节点。前端渲染时可以根据记录的 activityType 过滤出用户任务节点,配合 ACT_HI_TASKINST 表查出每个审批任务的办理人、开始时间、结束时间。
这里有个经验:历史任务表里审批意见最好自己存。Flowable本身在 taskService.addComment(taskId, processInstanceId, message) 里提供了评论功能,表是 ACT_HI_COMMENT。但评论功能比较弱,很多团队不做二次开发的话,直接在自己的业务表里存一条审批意见记录,通过 taskId 关联,反而更灵活。审批意见往往要和业务表字段一起展示,放业务库查询效率高、关联方便,也不受Flowable表的限制。
5. 前端集成与流程可视化:不只是把接口调通那么简单
5.1 待办、已办列表的接口设计思路
前端集成Flowable,绕不开待办列表、已办列表、流程详情、审批操作这几个页面。接口设计上,我的建议是:不要直接暴露Flowable的REST API给前端,而是封装一层业务API。
为什么不直接用Flowable自带的REST接口?一方面安全不好控制,Flowable自带的REST API权限模型是它自己的一套用户体系,和你的业务系统用户体系是两回事;另一方面,自带的REST返回的数据结构是通用的,前端拿到的待办任务里没有业务表单数据(比如请假天数、请假事由),你最终还是得写代码去批量关联业务表补全信息。
推荐的做法是后端封装三个核心接口:
code复制POST /api/workflow/process/start 发起流程(入参:流程key、业务单号、表单数据)
GET /api/workflow/task/todo?userId=xxx 待办列表(分页返回任务ID、节点名、业务摘要、到达时间)
POST /api/workflow/task/complete 审批操作(入参:任务ID、审批动作、审批意见、表单更新数据)
待办列表返回时,后端一次性把任务和业务表单数据join好,前端拿到直接渲染,不用多次请求。列表的SQL性能要注意:Flowable的 ACT_RU_TASK 表要关联你的业务表,如果数据量大,建议在 ACT_RU_TASK 表的 assignee 和 CREATE_TIME_ 字段建好索引,业务关联的查询尽量用子查询和IN而非逐条查。
5.2 流程进度跟踪图的三种实现方式
“当前卡在哪个环节”是用户特别关心的事情,实现方案大致有三条路:
- 用Flowable自带的流程图高亮API。Flowable提供了
ProcessDiagramGenerator,可以生成PNG格式的流程图,当前活动的节点会用红色高亮。这个方案实现简单,适合后端渲染图片返回给前端展示,但样式不可定制的痛点也很明显,字体、颜色、形状都不是企业想要的。 - 前端解析BPMN文件自行渲染。前端用
bpmn-js加载bpmn文件,再根据后端返回的当前活动节点ID集合,用前端API高亮节点。这种方式灵活性最高,可以做点击节点查看详情、点击审批人头像等交互,但是开发量明显更大,需要前端熟悉bpmn-js的API。 - 手工画节点流程图。后端把流程节点列表(包括已完成、进行中、未到达的状态)返回给前端,前端用普通div/canvas渲染成时间轴或步骤条。这是实现成本最低的方案,也是很多中后台项目的务实选择。
我的建议是:如果产品不要求精确还原BPMN图,用方案3(节点步骤条)就够了;如果产品一定要看到原汁原味的流程图,选方案2(bpmn-js),后期维护和扩展都优于后端生成图片。
5.3 前端集成时的几个权限细节
前端集成Flowable最容易被忽略的是“按钮权限”。同一个审批任务页面,不同角色应该看到不同的操作区。比如发起人看到的是“撤回”,审批人看到的是“同意/驳回”,管理员看到的是“转办/加签”。这些按钮权限的判定在后端接口里做,判定的依据包括:当前登录用户是否是任务的assignee或candidate、流程是否还处于激活状态、任务是否已经被认领。
流程的“签收”机制也值得提一下:当一个任务设置了多个候选人(candidateUsers)而不是单个assignee时,用户需要先签收(claim)任务,之后任务才会出现在他的个人待办里。很多前端集成做出来之后发现待办列表为空,就是因为在设计流程图时任务节点配的是候选人组,而前端直接按 taskAssignee 查待办,自然查不到。要么在查询待办时同时查 taskCandidateUser,要么不让用户签收、直接由后端通过监听器指定审批人。
6. Flowable与低代码/现有平台的集成经验
6.1 集成方式:API层集成还是引擎级别集成
现在不少项目的现状是团队已经在用JeecgBoot这类低代码平台,平台本身没有完整的工作流能力,需要接入Flowable来补上审批流这块拼图。这类集成一般有两种做法。
做法一:独立流程服务,通过API对接。Flowable跑在一个独立的后端服务里,暴露流程相关接口,JeecgBoot通过HTTP调用发起流程、查询待办、提交审批。这样做的好处是两个系统解耦,Flowable服务升级、流量扩缩容都不影响主业务系统;坏处是业务表单数据在两边同步,需要处理分布式事务或者最终一致性。
做法二:在JeecgBoot项目里直接引入Flowable依赖。JeecgBoot里加Flowable的starter,bpmn文件放在JeecgBoot的resources里,流程接口在同一个进程内跑。好处是业务数据访问和流程API在同一个事务里,开发调试方便;坏处是JeecgBoot升级时可能遇到依赖冲突,尤其是 Spring Boot 版本和Flowable要求的版本不一致时。
个人经验是:如果平台项目比较新、团队有维护独立服务的能力,优先选做法一。审批流这种基础能力独立成服务之后,后续多个系统都可以复用它,不用每个系统都集成一遍Flowable。如果只是一个小工具性质的内部系统,做法二更轻量,能省不少部署成本。
6.2 JeecgBoot集成Flowable的典型坑:用户体系与多租户
JeecgBoot集成Flowable时最常见的坑是用户体系对不上。JeecgBoot的用户表是 sys_user,Flowable的用户概念是在 ACT_ID_* 表里。如果你在流程设计时指定assignee为JeecgBoot的 sys_user 表里的id,那Flowable在查询任务时可以用这个id直接匹配(因为assignee本身就是一个字符串),但如果你用了Flowable自带的用户同步或身份管理,两边ID不一致就会查不到。
最简单的处理方式:把assignee直接设为JeecgBoot的用户ID,不行使用用户账号(username)也行,只要你能保证这个字符串在流程的整个生命周期内不变。在查询待办时后端先拿到当前登录用户的username,再去 taskQuery().taskAssignee(username) 查。这样绕过了Flowable的身份管理模块,相当于Flowable只做流程搬运,用户身份完全交给业务系统。
多租户场景的话,Flowable本身支持 tenantId 概念,部署流程定义和启动实例时都可以指定租户ID,这样不同租户的流程定义、实例、历史在逻辑上隔离。JeecgBoot本身有租户体系,集成时建议把JeecgBoot的租户ID映射到Flowable的tenantId上。需要注意:流程定义部署时如果不指定tenantId,该流程对所有租户可见;指定了tenantId的流程定义,其他租户发起时会报找不到定义的错误。这块的规则容易在项目里被忽略,等到上了生产环境,租户A能发起的流程租户B发起报错,那时候再排查就费劲了。
6.3 生产环境上的流程监控与运维建议
Flowable跑起来之后,运维这块也一样重要。生产环境至少要关注以下几件事:
- 定期清理历史数据。
ACT_HI_*历史表增长很快,尤其是审批频次高的系统。建议写定时任务,归档超过N个月的历史流程实例数据到历史库,再从Flowable的ACT_HI表删除。注意要先归档再删除,并且要保证归档数据在需要审计时可回溯。 - 监控流程实例的卡单情况。写一个定时任务扫描
ACT_RU_TASK,找出创建时间超过阈值(比如3天)仍然pending的任务,发消息通知相关人员。实际项目里“流程走到某人那里一直不审批”是最常见的投诉,靠人去盯不现实。 - 流程版本管理。每次部署新版本流程定义,都建议记一个内部版本号。想在代码里获取当前用的版本,可以用
repositoryService.createProcessDefinitionQuery().processDefinitionKey("leaveProcess").latestVersion().singleResult()去拿。发布前最好在测试环境完整跑一遍主流程,因为Flowable对流程文件的校验是有一套规则的,但很多业务层面的错误(比如条件分支永远走不到)它校验不出来。
7. 最后分享几个我实践下来最有用的习惯
工作流开发这块内容很宽,前端、后端、流程设计、数据库、运维全都涉及。文章最后不做什么宏观总结,就分享几个让我少踩坑的习惯,都是实际项目里吃过亏换来的。
第一,流程图里的节点ID一定要起有意义的名字。Flowable生成bpmn文件时默认的ID是类似 Event_1a2b3c 这样的随机字符串,如果你直接用,代码里查任务定位节点、排查日志时全靠猜。我习惯把每个UserTask的ID改成类似 manager_approve、director_approve 这样的语义化名字,后续通过 task.getTaskDefinitionKey() 判断当前节点时,代码可读性会好很多。
第二,所有流程操作入口打上操作日志。Flowable的ACT_HI表记录了引擎内部的行为,但它不知道业务的上下文,比如“这份请假单是哪个用户提交的、经理点了什么按钮、填了什么意见”。我在业务服务层统一记录操作日志,字段包括流程实例ID、任务ID、操作人、动作、时间、业务快照。排查线上问题的时候,这份日志比Flowable历史表好用得多。
第三,流程定义变更和生产数据升级要分开走。需求变了,流程加了一个审批节点,这种情况在流程引擎里属于流程定义的版本变更。老版本流程实例要不要迁移到新版本?答案是在没有充分验证前一定不要自动迁移。实际做法是:新流程从下一个发起单开始生效,老流程实例继续按老版本跑完,等业务确认新版本稳定后,再选择一个时间点把未完结的老实例做批量迁移或者强制作废,不要让两种版本长期混跑到不可控。
工作流开发上手并不难,难的是把BPMN设计、引擎API、业务架构、前端交互、数据一致性这一整个链路想清楚。希望这篇文章能把你在网上零零散散看到的知识串成一条可执行的路线,从画图到跑通,从跑通到稳定上线,少走几个我走过的弯路。
