Flowable会签实现:多实例任务的底层原理

Flowable 会签的技术基础是 BPMN 多实例活动:引擎进入节点时解析人员集合,为集合中的每个元素创建一个活动实例,并用多实例根执行统一维护总数、活动数和完成数。并行会签一次创建多个子执行,顺序会签始终只保持一个活动实例;每完成一个任务,引擎都会更新计数、判断 completionCondition,满足条件后结束整个多实例活动。

但"多实例任务"不等于"完整会签"。Flowable 只负责重复执行、运行时任务和结束条件,赞成票、反对票、弃权、权重、动态加签、审批意见和责任证据仍应由审批领域服务定义。理解这一边界,才能避免把 nrOfCompletedInstances 错当成赞成票数,也能解释为什么并发会签最容易在计票、监听器和动态参与者上出错。

一、核心结论与问题边界

一句话理解 Flowable 多实例任务

多实例活动可以理解为工作流引擎里的 for each:对人员集合中的每个元素执行一次 User Task。它可以并行执行,也可以顺序执行;可以等待全部实例完成,也可以用 completionCondition 提前结束。

概念 Flowable 负责什么 会签平台还要负责什么
人员集合 读取 collection 并逐项绑定 组织解析、去重、回避和人员快照
执行方式 并行或顺序创建活动实例 会签类型、轮次和前后加签语义
完成判断 计算实例完成数并执行表达式 赞成、反对、弃权、权重和一票否决
运行时任务 创建、完成和删除 User Task 意见、签名、附件、责任链和通知
历史 保存任务与活动历史 可解释的审批记录和合规证据

因此,多实例是会签的执行容器,不是会签的业务规则引擎。

源码里的核心设计:外层行为包装内层任务

Flowable 当前源码中的 MultiInstanceActivityBehavior 是一个抽象行为,直接子类是 ParallelMultiInstanceBehavior 和 SequentialMultiInstanceBehavior。它并不重新实现 User Task,而是包装原活动的 ActivityBehavior:外层负责循环、执行树、计数和结束条件,内层仍按普通用户任务逻辑创建待办、等待用户完成。

这个设计带来两个重要结论:

  • 多实例特性可以附加到 User Task、Service Task、SubProcess、Call Activity 等活动上;
  • 每个实例仍是一次真实活动执行,不是一张任务表里简单复制几行数据。

官方文档也明确说明,Gateway 和 Event 不能直接变成多实例。原因是多实例针对"可执行活动"进行重复,而网关负责路由、事件负责捕获或抛出流程事件。

一张图看懂多实例执行树

图 1:多实例根执行保存整体计数,子执行保存 loopCounter 和当前集合元素;并行模式下多个子任务同时存在。

流程令牌到达会签节点后,会形成一个多实例根执行。nrOfInstances、nrOfActiveInstances 和 nrOfCompletedInstances 属于整体循环语义;每个子执行拥有自己的 loopCounter,以及由 elementVariable 指定的当前人员,例如 assignee。

并行三人会签通常会看到一个多实例根执行、三个子执行和三个运行时任务。任务完成后,运行时任务记录消失,活动及任务历史进入历史数据;只有整个多实例满足结束条件,令牌才会离开会签节点。

二、关键概念与能力差异

BPMN 配置怎样变成运行时实例

多实例 User Task 的核心配置可以归纳为下面几项:

配置 典型值 运行时含义
isSequential false 或 true false 并行,true 顺序
collection assigneeList 参与者集合或返回集合的表达式
elementVariable assignee 当前集合元素的局部变量名
assignee ${assignee} 用当前元素分配每个任务
indexVariable loopCounter 当前实例下标,可自定义变量名
completionCondition 布尔表达式 每个实例结束后判断是否结束整体活动

进入活动时,引擎解析 loopCardinality 或 collection。官方文档说明,实例数量在进入活动时计算一次;如果 collection 是 assigneeList,初始集合有三人,nrOfInstances 就从三开始。后续修改普通流程变量里的列表,并不会自动生成第四个执行实例。

这也是"修改人员列表"和"动态加签"不能混为一谈的原因。真正的运行中加签需要调用受支持的多实例运行时 API,或通过审批领域服务建立新的会签轮次。

并行会签和顺序会签的底层差异

并行多实例会先创建全部子执行,再逐个调度原活动行为。Flowable 源码特别把"创建全部执行"和"执行原行为"分成两个循环,避免无等待活动执行过快时对子执行进行裁剪,造成集合尚未完整创建就被提前结束。

顺序多实例只创建一个子执行。当前实例完成后,引擎增加 loopCounter 和 nrOfCompletedInstances,清理上一实例的局部变量,再让同一个执行进入下一轮。对普通 User Task 来说,表现为上一人的任务完成后才创建下一人的任务。

对比项 并行多实例 顺序多实例
初始子执行 按集合大小创建多个 只创建一个
活动任务数 多个任务可同时存在 同一时刻通常只有一个
nrOfActiveInstances 随完成逐步减少 通常保持为 1
办理顺序 无天然先后 按集合迭代顺序
主要风险 并发更新、提前结束、剩余任务清理 人员顺序、跳过规则、单点阻塞

如果业务要求"同一批人全部可见、同时表态",使用并行;如果要求"部门经理处理后才到分管领导",使用顺序。不要用任务创建时间碰巧先后来模拟顺序规则。

三、模型架构与运行机制

四个变量分别代表什么

Flowable 多实例最常见的四个变量是:

  • nrOfInstances:进入多实例时确定的实例总数;
  • nrOfActiveInstances:当前仍处于活动状态的实例数;
  • nrOfCompletedInstances:已经完成的实例数;
  • loopCounter:当前子实例在集合中的下标,默认从 0 开始。

前三个变量描述整个多实例,loopCounter 描述单个实例。集合元素变量也应放在子执行局部作用域,否则并行实例会互相覆盖 assignee,最终可能出现多张任务指向同一个人。

最容易犯的错误是把 nrOfCompletedInstances 理解成"同意人数"。它只表示有多少实例结束。某个用户点击驳回并完成任务,完成数同样会增加。审批结果必须另存为结构化决定,再由会签策略统计 approvedCount、rejectedCount 和 abstainedCount。

一个任务完成后,引擎内部发生什么

图 2:TaskService 完成一个任务后,内层活动离开,外层多实例行为更新计数并判断是否继续或结束。

以并行会签为例,一次任务提交大致经历以下步骤:

  1. 业务服务校验办理人、任务版本和会签策略;
  2. 保存本人的结构化决定与审批意见;
  3. 调用 TaskService.complete 完成当前 User Task;
  4. User Task 的内层行为请求离开活动;
  5. ParallelMultiInstanceBehavior 使当前子执行失活并更新完成计数;
  6. 引擎执行活动结束监听器和变量聚合;
  7. 引擎计算 completionCondition;
  8. 条件未满足则继续等待,满足则结束多实例并沿顺序流前进。

Flowable 当前并行实现还会锁定首个父 scope,避免多个子实例同时完成时都认为自己是"最后一个"。当前主分支对并行循环计数采用专门的变量类型,同时保留对旧运行中实例的兼容逻辑。这说明并发正确性属于引擎内部实现细节,应用不应直接修改计数变量或 ACT_RU_EXECUTION。

四、核心场景与处理策略

completionCondition 应该怎样写

completionCondition 会在每个实例结束后求值,结果必须是布尔值。它适合表达"什么时候结束整个多实例",但表达式引用什么数据,决定了会签语义是否正确。

会签规则 推荐判断 不应使用的判断
全员处理 nrOfCompletedInstances 等于 nrOfInstances 只判断当前任务已完成
任一同意 approvedCount 大于等于 1 nrOfCompletedInstances 大于等于 1
60% 同意 approvedCount 乘 100 大于等于 nrOfInstances 乘 60 完成数除以总数大于等于 0.6
一票否决 rejectedCount 大于等于 1 任意任务完成即否决
过半且无否决 approvedCount 过半且 rejectedCount 为 0 只看赞成票,不处理否决票

计票变量必须并发安全。简单在监听器里读取 approvedCount、加一再写回,两个事务可能读到同一个旧值并覆盖彼此。更可靠的做法是保存一人一票的业务记录,以唯一键防止重复表态,再在同一锁或同一事务边界内计算聚合结果。

提前结束时,剩余任务去了哪里

completionCondition 一旦满足,Flowable 会结束多实例活动并清理仍在运行的子执行。对用户界面而言,其他人的运行时待办会消失;对审计而言,这些任务不能被误写成"已同意"或"正常完成"。

业务系统应为剩余参与者记录明确原因,例如:达到通过比例、触发一票否决、管理员终止或边界事件中断。历史任务的结束时间只能说明任务生命周期结束,不能单独证明办理人作出了什么决定。

提前结束还会影响催办、移动端缓存和待办索引。推荐在事务提交后发布"会签整体结束"事件,由投影服务撤销未办提醒、更新工作台并保留取消原因,而不是只依赖前端下一次刷新发现任务不见了。

五、数据、规则与状态设计

执行表、任务表和历史表怎样分工

多实例运行时通常会涉及 ACT_RU_EXECUTION、ACT_RU_TASK 和运行时变量;完成后,活动与任务轨迹进入历史表。理解表的职责有助于排查问题,但业务代码应优先使用 RuntimeService、TaskService 和 HistoryService 查询,不应直接更新引擎表。

  • ACT_RU_EXECUTION 体现流程实例、scope、多实例根和子执行关系;
  • ACT_RU_TASK 保存当前仍需人工处理的 User Task;
  • 运行时变量保存集合、计数、局部人员变量和业务上下文;
  • 历史活动记录可观察每个活动实例的开始、结束和删除原因;
  • 历史任务记录可观察办理人、任务生命周期和完成时间。

排查"三人会签为什么只有两张任务"时,应先看人员集合快照,再看执行树和局部 assignee,最后看是否有 completionCondition、边界事件或监听器提前结束。只查任务表容易把执行树问题误判成任务创建问题。

赞成、反对和弃权应该存在哪里

推荐把审批决定存入独立业务记录,而不是只写一个全局流程变量。每条记录至少包含 processInstanceId、taskId、activityId、roundNo、userId、decision、comment、operationId 和完成时间,并对 taskId 或"轮次加人员"建立唯一约束。

这样做有三个好处:

  • 审批决定与任务完成解耦,能够解释每一票;
  • 并行提交可以通过唯一键和事务锁避免重复计票;
  • 动态加签、撤回意见和多轮会签可以保留版本,而不是覆盖一个变量。

流程变量适合保存轻量、需要驱动流程的聚合结果,例如 approvedCount、rejectedCount 和 meetingResult;原始意见、附件、签名和人员快照应由业务库承担。

六、工程实现与系统集成

动态加签为什么比增加一个任务复杂

Flowable RuntimeService 提供 addMultiInstanceExecution 和 deleteMultiInstanceExecution 等公开能力,可以向正在运行的多实例父执行增加或删除实例。但 API 解决的是执行树变更,不会替企业定义会签规则。

运行中加签至少要决定:

  • 新人员是否计入 nrOfInstances 和通过比例分母;
  • 当前已经满足的比例是否需要重新计算;
  • 新任务属于原轮次还是新轮次;
  • 被删除实例算弃权、取消还是管理员调整;
  • 加签人与被加签人的责任关系怎样记录;
  • 重复请求和并发完成同时发生时怎样保证幂等。

因此,加签入口必须由领域命令服务统一封装。不要让前端直接拿 parentExecutionId 调用引擎 API,更不要手工插入任务和执行记录。

监听器为什么经常执行两遍

多实例活动上的 execution listener 与普通活动不同。官方文档说明,进入多实例整体时会触发一次 start,此时 loopCounter 尚未设置;每个实际实例开始时还会再触发 start,此时 loopCounter 有值。end 事件同理:每个实例结束触发一次,整体结束还会再触发一次。

如果监听器不区分"整体事件"和"实例事件",就可能重复发消息、重复写审批记录或重复更新业务状态。判断时应结合 loopCounter、execution 是否为 multi-instance root、事件类型和 operationId,而不是看到 activityId 相同就认为是同一次回调。

Task listener 则面向每张具体用户任务,更适合记录任务创建、指派和完成事件;跨系统通知仍建议通过事务 Outbox 在提交后异步发送,避免监听器远程调用拖长引擎事务。

七、安全、性能与治理要求

边界事件和超时会怎样影响会签

多实例活动仍然是普通 BPMN 活动,因此可以挂载边界事件。官方文档说明,中断型边界事件被捕获时,所有仍活动的实例都会被销毁。例如会签节点设置三天超时,定时器触发后可以统一转入升级审批,而不是让每个人各自进入一条升级分支。

若业务要求"每个人分别超时",边界定时器应放在内部任务语义上并明确是否中断单个实例。两种模型的影响范围完全不同:一个中断整个会签,一个只处理当前参与者。

超时处理也必须保存原因。取消任务、自动同意、自动弃权和转交上级是四种不同的审批语义,不能只用任务已结束来表示。

一套可靠的会签领域架构

图 3:审批领域服务负责人员、投票和审计,Flowable 多实例负责执行树、任务和流程推进。

推荐把会签分成五个职责:

  1. Participant Resolver:按部门、岗位、逐级主管等规则生成并冻结人员快照;
  2. Vote Service:保存赞成、反对、弃权、权重和一票否决等结构化决定;
  3. Counter Policy:根据当前轮次与人员快照计算是否达到结束条件;
  4. Flowable Adapter:完成任务、增加或删除多实例执行,并隔离版本差异;
  5. Audit and Projection:生成审批时间线、待办投影、取消原因和通知事件。

Flowable 只接收经过校验的命令。所有 PC、移动端、开放 API 和管理员入口都经过同一个 operationId 幂等检查,避免同一人双击、重试或多端提交产生两次投票。

八、平台落地、测试与选型

低代码平台应该封装哪些配置

云程低代码开发平台可以在 BPMN.js 属性面板中把底层多实例配置包装成业务可理解的会签选项:并行或顺序、人员来源、全员或比例通过、一票否决、超时策略、是否允许加签、人员快照时机和剩余任务处理方式。

设计器发布时应把这些选项转换为版本化节点策略,并校验 collection、elementVariable、assignee 和 completionCondition 是否一致。运行时仍由统一会签服务完成计票和审计,避免每条流程复制监听器脚本。

相关推荐
愚农搬码2 天前
Flowable工作流引擎如何适配国产数据库?以达梦数据库举例说明
工作流引擎
songgeb2 天前
mspec体验:基于SDD的轻量AI工作流
ai编程·工作流引擎
大龄码农有梦想2 天前
企业工作流系统如何设计用户、部门、角色、岗位、动态关系五类流程办理人?
工作流引擎·flowable·流程引擎·oa·工作流系统·选人规则·bpm平台
愚农搬码5 天前
工作流里的转办、委托和工作移交的业务语义与技术实现
工作流引擎
大龄码农有梦想5 天前
工作流中的子流程节点能否驳回到父流程?
工作流引擎·流程引擎·oa·bpm·子流程·驳回·退回
愚农搬码5 天前
工作流中的子流程节点能否驳回到父流程?
工作流引擎
愚农搬码5 天前
流程图上的回退线与运行时动态回退,应该选择哪一种?
工作流引擎
大龄码农有梦想6 天前
开源流程引擎 Camunda 如何实现任意节点跳转?
工作流引擎·流程引擎·camunda·oa·bpn·流程跳转·会签
愚农搬码9 天前
在开源流程引擎 Flowable 里如何实现任意节点跳转?
工作流引擎
Behavior13 天前
一条指令,让 Claude Code 每天定时帮你追热点:动态工作流Workflows从 0 到 1 全流程
claude·workflow·工作流引擎