选BPM引擎这件事,真正让人上头的从来不是“能不能画流程图”,而是任务到了用户手里以后发生的一切:待办从哪来、已办怎么查、被驳回的任务还记不记得原审批人、抄送到底算不算“任务”。这个话题在选型群里反复被问,而大多数对比文章只停留在“性能强弱”“社区活跃度”这种宏观层面,很少人用“任务交互层”的视角去拆。所谓任务交互层,就是流程引擎暴露给普通用户的那些入口和动作——待办列表、已办列表、发起列表、抄送/共享任务,以及背后支撑这些入口的任务创建、流转、终结三个处理器。
这篇文章我用驰骋BPM独有的“四大菜单”和“三大处理器”作为分析框架,逐一对照Flowable、Camunda、Activiti三款Java系引擎的任务交互层设计。适合正在做技术选型、或者已经在用其中某一款但被待办查询、会签、退回、催办等问题折磨的开发者和架构师。
1. 从“四大菜单”说起:一张待办页面背后的任务数据模型差异
1.1 四大菜单的真实拆分:待办、已办、发起、抄送各查哪张表
接触过驰骋BPM(CCFlow/JFlow系列)的朋友应该对它的工作台布局有印象:打开系统,左面菜单栏最显眼的通常就是我的发起、我的待办、我的已办、我的抄送四个入口。这套菜单设计不是产品经理拍脑袋想出来的,而是直接映射了工作流任务在一套业务系统中的真实生命周期:流程由我发起,所以我关心“我发起的”;流程走到我这里,所以“我待办”;我处理完了,所以“我已办”;某个环节产生的信息需要让我知道但不需要我处理,于是有了“我的抄送”。
在驰骋BPM的实现里,这四个菜单背后是明确的数据表划分和查询维度。待办表记录当前正在运行、等待某人或某角色处理的任务;已办表记录当前用户处理过的所有任务实例;发起表记录该用户发起的流程实例;抄送表则记录那些“与我相关但不需要我审批”的任务。这四个维度的分离,让“我的工作台”几乎不需要做复杂的历史数据聚合,查哪张表就是哪个菜单,这对最终用户来说是认知负担最低的一种交互设计。
反过来看Flowable、Camunda、Activiti,它们的默认工作台远没有这么贴心。这三款引擎是给开发者用的BPMN引擎,不是给业务人员用的BPM产品,所以它们只保证“有表可查”,不保证“开箱即用的工作台”。你拿到的是一个运行时任务表ACT_RU_TASK、一个历史任务表ACT_HI_TASKINST,至于“我的待办”“我的已办”怎么拼装,那是开发者的活儿。
1.2 Flowable/Camunda/Activiti的任务表:ACT_RU_TASK 与历史表的割裂
Java系三兄弟的表结构脉络同源。Activiti 5/6、Flowable 6/7、Camunda 7 这套体系里,运行时任务放在 ACT_RU_TASK 表,任务完成之后记录转移到历史的 ACT_HI_TASKINST(Camunda 是 ACT_HI_TASKINST,Activiti/Flowable 则拆得细一点,有 ACT_HI_ACTINST、ACT_HI_TASKINST)。待办列表的查询逻辑非常直接:查 ACT_RU_TASK,过滤 assignee_ 或者 candidate 相关的表;已办列表就没那么简单了,不能只查 ACT_HI_TASKINST,因为一个流程经过10个节点,当前用户可能只处理过其中3个,你需要按照流程实例ID聚合任务,再剔除还在运行的任务、剔除被认领但未完成的任务,最后按完成时间倒序分页。这一套SQL写下来,性能稍不注意就会被历史表的数据量拖垮。
所以不少人后来学精了:在Flowable/Camunda之上再冗余一份“用户任务流水表”,每完成一个任务就往里面写一行业务快照,包含办理人、任务名称、流程单号、操作时间、处理结果。已办列表直接查这张业务表,把历史表留给流程审计去用。这个方案本质上就是向驰骋BPM“四大菜单”的数据模型回归——用空间和一点实时性,换查询逻辑的简单可靠。
1.3 这个模型差异决定了你的“已办查询”会不会被搞疯
结合真实项目经验说结论:用Flowable/Camunda/Activiti做一个“待办列表”,三款引擎差别不大,都是查运行时表;但做一个“已办列表”,如果不用冗余表,基本都会在数据量上来之后遇到性能问题。原因在于历史表里每一行是一次节点执行记录,而业务侧关心的是“用户处理过哪些流程”,两个粒度对不上。用驰骋BPM那种稳定的“四表分菜单”模型,一开始就把查询维度定死,虽然新增任务类型时需要额外维护表关系,但查询侧稳定太多。
提示:选型阶段不要只看引擎能不能跑通Hello World,先让它跑一个真实规模的“已办列表”看看SQL长什么样、索引怎么建、数据量到50万行以后还压不压得住。这个指标基本决定你未来的运维体验。
2. 三大处理器并不神秘:任务从出生到消失经历了哪些钩子
2.1 先澄清:为什么说是“三大处理器”,而不是四个
驰骋BPM资料里提到的处理器体系不止三个,除了启动、调度、删除,还有计算类处理器等。但站在任务交互层的视角看,真正决定一个任务从生成到消失全过程的,是三类核心处理器:任务创建处理器、任务流转调度处理器、任务终结处理器。创建负责在流程走到某个节点时按规则生成待办并分配办理人;调度负责处理转办、委派、催办、加签、退回等动作;终结负责任务完成、驳回、终止、删除时的状态迁移。标题里说三大处理器,就是从任务交互这个横切面提炼出来的。计算类那些偏表单联动、字段计算的逻辑,我认为不属于任务交互层,所以不放进这三大类里讨论。
拿一个现实场景来理解这三者的分工:销售提交报销单,流程启动,创建处理器算出下一步应该由部门经理审批,于是在部门经理的待办列表里生成一条待办;部门经理出差了,把待办转办给副经理,流转调度处理器记下原办理人和转办人,并更新待办的assignee;副经理审批同意,终结处理器把这条运行时任务删除,在历史表里留下“部门经理节点已通过”的记录,同时计算器继续计算下一步是否要转给财务。三者串联,才构成一次完整的任务交互。
2.2 任务创建处理器:谁在决定“下一步发给谁”
在Flowable、Camunda、Activiti这一派里,任务创建的本质是BPMN引擎执行到UserTask节点,实例化一个任务对象,写入ACT_RU_TASK,然后触发TaskListener的create事件。你可以在这个事件里动态修改assignee、候选人、到期时间。注意这里的执行顺序有讲究:Flowable和Activiti是先创建任务再触发create事件,Camunda也是类似,但事件监听器里对任务属性的修改,最终是否能正确同步到数据库,取决于你用的API是否开启了强制更新。
驰骋BPM的启动处理器在我看来更贴近“低代码组件”的思路:它不是监听器,而是一个可以在流程设计器里面直接挂到节点上的处理类。开发者写好处理类的全类名,配置好参数,节点流转时引擎自动执行。这个模式的好处是对业务人员更友好——不用理解“事件”和“监听器”的概念,只需要理解“这个节点开始的时候要执行一个动作”。代价则是没有Java系引擎那种自由度,任何节点行为都要往处理器这个壳里装。
2.3 流转与调度处理器:转办、催办、加签背后的执行逻辑
任务分配出去之后,真正的交互差异才开始显现。转办(Delegate)在三款Java引擎里都有对应API:Flowable是taskService.delegateTask,Camunda是taskService.delegateTask或setAssignee,Activiti同样有delegateTask。语义上三者也基本一致:把任务转给另一个处理人,原办理人被记录为owner。区别在于转办后的任务查询方式,Camunda对candidate user和assignee的索引更细致,Flowable在历史记录里会额外写入一个delegation字段。
催办和加签则是典型的“引擎不提供、业务自己造”的功能。Flowable靠定时Job配合流程变量实现“任务到期未办理则通知”;Camunda有更灵活的Job Executor,甚至可以专门写一个外部定时任务去扫ACT_RU_TASK的due date;Activiti则需要开发者自己调度。驰骋BPM这里反而省事,调度处理器直接嵌入引擎逻辑,在流程设计器里指定触发时间、触发方式、处理动作,运维侧少写很多定时代码。
这个环节我特别想说一个坑:不管你用哪个引擎,催办功能不要直接在任务表上扫全表,一定要用due_date_字段建索引,并尽量把“催办状态”放到独立的业务表里。否则当待办数量达到几十万行,定时任务每分钟扫一次,数据库IO会被打爆。
2.4 终结处理器:完成、驳回、终止时任务状态机怎么走
任务的“消失”方式不是只有完成一种。正常完成、驳回到上一节点、流程终止、人工删除,这几条路径在ACT_RU_TASK里的表现都是删行数据,区别写在哪?写在历史表的DELETE_REASON_字段和流程实例状态里。Flowable完成任务的DELETE_REASON_一般是completed,Activiti和Camunda也是这个约定,但驳回操作在三款引擎中都不会直接给任务写一个“rejected”标记,而是通过流程引擎的跳转指令,先终结当前任务,再在新的节点生成任务。此时历史表里当前节点的记录可能只有删除原因,没有处理结果,如果你的已办列表需要一个“审批不通过”的标识,就得自己在扩展字段里记录。
驰骋BPM在终结处理上有一个比较明显的产品化优势:它把驳回、退回、终止这些动作直接做成了标准操作按钮,点击之后引擎自动处理历史任务的结束时间、处理结果、回退目标,普通用户不需要理解BPMN的边界事件和中间捕获事件这些概念。这在面向业务人员的工程化项目中,能省去大量“教用户怎么点”的成本。
3. 同一句“发给王主管审批”,四个引擎的代码路径完全不同
3.1 流程图部署与节点模型,决定交互层二次开发的方式
Flowable、Camunda、Activiti都是BPMN 2.0规范路线,流程定义是一个XML文件,部署到引擎后生成流程定义ID和版本号。开发者要在一个节点上做文章,基本就是在XML里写extensionElements,在节点属性里挂监听器,或者通过引擎API动态修改。Camunda的Modeler是桌面建模工具,Flowable也维护了自己的Flowable Modeler,Activiti的流程设计器在IDEA插件或Web端可用,但体验参差不齐。
驰骋BPM不走纯BPMN路线,它的核心资产是表单引擎+流程引擎的绑定关系。节点上挂的不只是处理器,还有表单、操作按钮、可见字段、数据权限。这意味着在驰骋里做“节点级定制”不需要碰XML,在设计器里点几下就能完成。代价是模型不标准,想导出BPMN和别的系统做流程交换比较困难,一旦深度绑定,迁移成本很高。
3.2 候选人、候选组、表达式:指定审批人的写法对照
这是一个特别容易让初学者困惑的环节。三款Java引擎在“指定审批人”上都支持直接指定assignee用户ID,写法差别不大。真正的差异在候选人和候选组上:
- Activiti传统写法是 flowable:candidateUsers="张三,李四"、flowable:candidateGroups="manager",运行时这两个列表先出现在身份表中,用户认领任务后assignee才被写入。
- Flowable延续了这套模型,但API更灵活,支持通过TaskCandidateUser或TaskCandidateGroup查询任务。
- Camunda在BPMN XML里写 camunda:candidateUsers 和 camunda:candidateGroups,运行时用IdentityLink记录任务与用户/组的关系,查询Candidate任务时有专门的TaskQuery接口。
驰骋BPM则是另一套打法。它的任务分配直接面向“岗位/角色/部门”建模,配置一个接收人规则,引擎在运行时根据发起人部门、表单字段、流程变量解析出具体办理人集合。这种“按规则找人”的模式在国内的组织审批场景里比单纯的候选人表达式更贴合实际,因为国内企业的组织架构复杂,经常需要按“发起人的直属上级”“所在部门的部门经理”这类关系自动定位办理人,用Java系引擎实现这套逻辑,得写一个自定义的AssigneeExpression,本质上也是在造规则引擎。
3.3 会签、或签、票签:多实例任务交互是重灾区
多实例任务是任务交互层最容易踩坑的地方。Flowable和Activiti用BPMN的multiInstanceLoopCharacteristics,配置collection和elementVariable控制循环集合;Camunda大致相同,但它对多实例的并发控制做得更细,支持通过表达式动态控制循环数量。表面看三款引擎用法一致,实际坑点在于:
- 加签和减签操作没有标准API。Flowable官方不提供运行时动态加签的通用接口,减签更麻烦,需要开发者操作任务列表并且自己维护历史关系。
- 会签完成策略不好做。一个会签节点三个人处理,一个人驳回了,另外两个人的待办按理说应该撤销。用Flowable实现,你要么在多实例结束条件里配置驳回计数,要么用网关分支判断,但“撤销未处理待办”这个动作引擎不会自动做。
- Camunda虽然在BPMN语义上更严谨,但对“会签驳回后重新发起”这类业务模式支持同样有限,仍需通过流程变量+条件表达式组合实现。
驰骋BPM天然把会签、或签、票签做成节点类型。配置节点时选择“会签”或“或签”,业务人员按照审批人数动态增加要发送的下一节点办理人;会签驳回后的待办撤销由调度处理器统一处理,用户感知是“一个人驳回,其他人待办自动消失”。这对复杂的内部审批场景来说,省掉的不只是开发量,还有大量流程测试成本。
3.4 加急、催办、撤回:交互增强功能的实现成本
做真实业务系统,尤其是ToB的协同办公场景,“加急”“催办”“撤回”这种高频功能几乎一定会上。Java三兄弟里,催办靠定时任务扫描due date,加急靠流程变量改变表单展示样式,撤回靠调用runtimeService删除当前运行实例并重置到上一节点,每一步都是半手工实现。Camunda的External Task模式在任务交互增强上有一定优势,它允许外部系统通过Worker拉取任务并执行,适合做独立的通知服务、会议系统对接;但External Task本身也是独立一套任务模型,会和传统的UserTask待办产生“两套待办”的混淆问题。
驰骋BPM因为自带调度处理器和催办规则,这些增强功能的实现成本明显偏低。调度处理器可以定时触发,拿到超时未处理的任务列表后,可以直接调用消息推送服务或企业微信/钉钉Webhook,整个链路都在引擎内部闭环。这一点放到实际交付里是非常加分的,因为企业一般要求“催办记录要留痕”,驰骋的处理器可以在催办时同时写业务日志,而Java系引擎通常要靠额外的代码去维护这些日志表。
4. 从热搜词看真实需求:Spring Boot整合与国产数据库适配
4.1 为什么那么多人搜“Flowable整合SpringBoot”和“Activiti数据库版本”
看热搜词很有意思:“flowable整合springboot实战”“springboot使用flowable”“flowable快速入门”“camunda工作流”“activiti数据库版本”“idea activiti插件离线安装”“flowable 6.7.2 适配达梦数据库”——这些搜索背后是两类人群。一类是还在踏入门槛的开发者,正在纠结怎么把引擎跑起来,需要一份清晰的Spring Boot集成指南;另一类是已经在做国产化替代的交付团队,遇到的是“引擎跑起来了,但是连不上达梦”这种更残酷的落地问题。
“activiti数据库版本”这个搜索词特别典型。Activiti 5/6在MySQL、Oracle、PostgreSQL上的建表脚本各不相同,到了Activit 7还引入了一些兼容性变化;Flowable 6.7.x虽然兼容主流数据库,但国产数据库这块官方支持基本为零。所谓的“适配达梦数据库”,实践中大多是靠Hibernate/MyBatis方言配置加手工改脚本跑通的,而不是开箱即用。
4.2 三个Java引擎的集成体验:Starter、平台、插件
Flowable的Spring Boot Starter在三个引擎里做得最顺手。引入flowable-spring-boot-starter-process,配置数据源,启动应用时自动部署classpath下的processes目录,过程几乎零门槛。Flowable还自带IDM用户管理和App接口,但一般做业务系统都会绕过IDM直接用业务用户体系。
Camunda的集成路径分两派:Camunda Platform 7可以像Flowable一样嵌入Spring Boot应用,提供REST API和Tasklist界面;Camunda 8是分布式架构,引入了Zeebe,需要额外部署集群组件,对单体业务系统来说太重。选型时一定先分清是7还是8,两个体系的技术栈、部署方式、任务模型都不一样。
Activiti 7的集成相比前两个要粗糙一些。它同样提供Spring Boot Starter,但社区运营明显不如Flowable活跃,ActBPM插件在老版本IDEA里能用,新版IDEA支持很差,所以“idea activiti插件离线安装”才会成为热搜词——说白了就是插件市场搜不到,得手动下载zip本地安装,装上之后画图体验也只能说凑合。
4.3 驰骋BPM的双语言与信创数据库适配
驰骋BPM在国内信创项目里出现频率高,不是因为它比Flowable功能强多少,而是因为它有Java和.NET双版本,并且官方就做了国产数据库适配。达梦、人大金仓、GaussDB、南大通用这些库,在驰骋的数据库脚本列表里可以直接选。对做政务、国企项目的团队来说,这一点几乎是决定性的——Flowable要适配达梦,得自己处理序列、自增主键、函数差异,光建表脚本就要改不少地方,搞不好还要给引擎写自定义方言;驰骋这边选择数据库类型后,引擎初始化脚本自动生成,省下的是几周到一个月不等的适配工时。
双语言版本也是一个容易被低估的优势。能跑.NET版本的驰骋BPM,意味着企业可以把它嵌入到老旧的.NET系统中,同时Java项目也能用同一套流程设计资产。这一点对存量系统改造非常有价值,因为我见过太多企业因为流程引擎跟现有技术栈不匹配,被迫推倒重来。
4.4 实际并发下,任务表慢查询的优化思路
不管选哪个引擎,任务交互层最终都会遇到性能问题。我处理过的项目里,最先扛不住的往往是两个查询:“我的待办列表”和“我的已办列表”。待办表ACT_RU_TASK在数据量到几十万行之后,如果ASSIGNEE_字段没有索引或者查询语句中用了函数包裹字段,分页查询性能断崖式下跌。正确的姿势是:把assignee查询直接作为第一过滤条件,并确保ACT_RU_TASK表的ASSIGNEE_和PROC_INST_ID_上都有联合索引;不推荐在运行时表上做LIKE模糊查询。
“我的已办”的优化,前面提过,首选方案是冗余业务流水表。如果不想引入冗余表,就一定要把ACT_HI_TASKINST按END_TIME_分区,并且查询时固定带上流程类型或业务分类字段,避免全表扫描。驰骋BPM的四表模型之所以在同等数据量下表现更好,是因为查询维度从数据库表设计上就被拆成“我的一亩三分地”,天然规避了跨表聚合。
5. 新势力入场之后:LangGraph、WarmFlow 对任务交互层的冲击与融合
5.1 为什么有人把 LangGraph 和 Flowable 放在一起比较
LangGraph出现在Flowable的关联词汇里让我一点都不意外。这两年AI Agent工作流概念火了之后,很多做智能客服、自动化处理、Agent编排的团队发现,LangGraph这类图编排框架能描述复杂的状态流转和分支条件,而且天然支持LLM节点,所以有人提出“LangGraph代替Flowable”。这个命题在特定场景下是成立的:纯粹的Agent任务编排、状态节点少、没有强审计要求、没有人机协同审批环节、不要求事务性补偿,LangGraph可以干得很爽。
但LangGraph解决不了的是企业级任务交互层的核心诉求:谁在几点几分处理了哪个任务、任务被谁转办过、会签节点几个人同意几个人反对、流程被驳回时历史轨迹如何留痕。这些东西在LangGraph里不是不能做,而是一切靠自己造轮子。所以更务实的做法不是“二选一”,而是“LangGraph结合Flowable”:LangGraph负责智能路由和Agent决策,Flowable负责审批流程、任务分配、历史审计。这种组合我最近已经在一些客户的智能审批项目中落地了,效果比纯LangGraph或纯Flowable都好。
5.2 WarmFlow 与 Flowable:轻量框架的真实优劣势
WarmFlow是个相对新面孔,主打轻量、内嵌、API简单,很多中小团队喜欢拿它跟Flowable比。WarmFlow的优点很明显:引入成本低,几百KB的jar包,表结构简单,任务模型直观,非常适合那种“我只需要一个流程状态机,不需要完整BPMN引擎”的项目。团队如果一周内就要交付一个带审批流的MVP,WarmFlow这类轻量框架能把流程引擎的集成时间压缩到半天。
它的劣势同样明显:BPMN支持不完整、多实例会签没有标准方案、缺少Camunda/Flowable那样的流程引擎管理后台、异常补偿和回滚机制薄弱、生态和社区规模远不如Flowable。一旦流程复杂度上来,比如需要子流程、消息边界事件、条件事件、定时补偿,轻量框架就会成为瓶颈。我的建议是:原型验证或内部小工具用WarmFlow完全可行;面向客户交付的正式产品,还是Flowable/Camunda这种经过大量生产环境检验的引擎更稳妥。
5.3 传统BPM交互层的护城河不会轻易被拆掉
新框架密集出现,传统BPM引擎会不会被淘汰?我的判断是不会,至少三到五年内不会。原因不在引擎本身,而在企业软件对“确定性”的需求。企业审批流程里的每一步、每一个按钮、每一条记录都要能追溯到人,任务状态必须依赖数据库持久化,流程异常时要有明确的补偿与回滚机制,这些能力是LangGraph、WarmFlow这类框架所欠缺的。传统BPM的任务交互层背后有完整的事务边界、审计日志、权限模型、多租户隔离,这些才是最硬的护城河。
未来的演进方向大概率是融合:BPM引擎继续作为审批主链路和任务状态存储的中心,AI Agent作为智能决策和节点路由的插件接入。用户在待办列表里会看到AI预审意见,退回某个节点时AI会给出退回原因摘要,但这些交互动作最终仍然落在“任务创建-流转-终结”三个处理器构成的稳定骨架里。这也是为什么我建议现在选型还是要优先选择生态成熟、任务交互层可扩展性好的引擎,别被“新框架炫技”带偏了路。
6. 踩坑实录:从 Activiti 迁移到 Flowable/Camunda 时任务交互层的典型问题
6.1 任务ID不兼容,历史数据对接为何头痛
Activiti和Flowable底层同源,任务表和流程定义表的字段也高度相似,但ID生成策略不同,任务ID的格式对不上,导致做过Activiti老系统的团队在迁移时,历史数据不能无缝复制到Flowable。尤其是“已办列表”要展示历史任务的原始ID,如果旧系统里大量业务表用任务ID做外键,迁移后这些关联关系要全部重做。我的建议是迁移期间在Flowable的扩展字段里保留旧任务ID,并写一个对照表,等业务侧确认无需回传旧链路后再下线。
6.2 已办列表重复数据:一张历史表里同一个任务出现多行
这可能是Flowable/Activiti新手最容易困惑的问题。ACT_HI_ACTINST表按活动实例记录数据,同一个用户任务在经历“退回-重新提交-再审批”之后,会产生多条活动记录;ACT_HI_TASKINST表虽然按任务实例记录,但一个流程实例里同一节点被多次执行,也会出现多个任务实例。写“我的已办”时,如果不关心这个,直接SELECT DISTINCT任务ID,出来的数据就会明显不对劲。正确做法是:已办列表按流程实例ID聚合,展示最新一条用户任务实例作为“该流程的最近一次我处理的状态”,历史明细单独用时间线组件展示。
6.3 待办提醒Job把历史表写脏,以及如何治理
做待办提醒的常规思路是从ACT_RU_TASK中扫描due date过期的任务,推送给办理人。问题出在“扫描”的时机和范围上:如果基于历史任务表做扫描,ACT_HI_TASKINST的行会在任务完成时更新END_TIME_,定时任务可能会把已经处理完的历史任务再次当成“超时未处理”扫出来,导致用户收到已经审批过的催办通知。治理方案很朴素:定时Job只扫ACT_RU_TASK这个运行时表,扫描出来的任务ID与业务待办流水表做差集;一旦任务从运行表消失,立即取消与该任务ID关联的待办提醒状态,防止重复推送。
6.4 候选人慢查询与组织架构join的指数级放大
当业务里大量使用candidate group而不是直接指定assignee时,“我的待办”查询就变成:先查当前用户所属的所有用户组,再查这些用户组关联的所有任务。两段join如果稍不注意,SQL的数量和复杂度都会爆炸,尤其是组织架构层级深、一个用户属于几十个组的时候。优化思路:一是把用户-用户组关系缓存到引擎侧,或者直接在业务表中维护“用户待办视图”;二是尽可能将候选人任务在业务层直接转换为按assignee过滤的运行时任务,减少每打开一次待办页面就扫描身份表。Camunda在这一块因为IdentityLink表设计得较清晰,比Flowable好一些,但本质问题是一样的。
6.5 多租户下忘记传 tenant_id,待办互相串台
Flowable和Activiti都支持多租户,但如果你在部署流程定义和启动流程实例时没有统一传递tenantId,默认的tenantId会是空字符串,导致不同租户的任务混在一起。这个问题排查起来非常隐蔽,因为开发环境和测试环境数据量小,串了也看不出来,一旦上生产多租户并行,A租户的审批人打开待办列表看到了B租户的单子。踩过这次坑之后,我总结出的铁律是:代码里统一封装一个流程服务接口,启动流程、查询任务、完成任务所有方法都强制附带租户ID,不接受缺省值,另外在业务表中把租户ID作为所有任务查询的必填条件。
回到开头的问题,给一个我个人的实测体会:如果项目面向国内政企、强调国产化适配和“开箱即用的工作台”,驰骋BPM的四菜单模型和三处理器体系能省很多交互层开发量;如果项目需要深度定制流程逻辑、有大量开发者参与、追求BPMN规范和社区生态,Flowable、Camunda仍然是稳妥选择;处理AI Agent和工作流结合的场景,LangGraph只负责决策,任务审批与审计交给传统BPM引擎,两者各干各擅长的部分。选型没有绝对的“最好”,只有哪一家的任务交互模型更贴近你真实业务的人机协作边界。