工作流并行网关中的一个分支驳回,其他分支怎么办?
并行网关中的一个分支驳回后,其他分支没有唯一、自动且普遍正确的处理方式。系统必须先确定这次驳回属于哪一种业务语义:如果只是当前分支补正,其他分支可以继续办理或保留已完成结果;如果任何一个分支驳回都代表本轮审批整体失败,就要取消或收拢同一并行作用域内的全部执行令牌,再整体退回到 Fork 之前;如果其他分支结果仍有效但不允许继续变化,则应冻结未完成任务,让已完成分支停在 Join 等待重审。
最危险的做法,是只把驳回分支移动到 Fork 之前,却保留其他分支原有令牌。流程再次经过 Fork 后会重新生成全部分支,旧令牌与新令牌并存,可能造成重复待办、Join 提前通过、流程卡死或重复执行业务动作。
一句话结论:先定义“驳回当前分支”还是“驳回整个并行审批轮次”,再决定保留、冻结还是收拢其他分支;不要让并行网关替业务规则做决定。
一、核心结论与问题边界
先给答案:其他分支只有三种合理去向
同一个流程图,可能对应三种完全不同的产品规则。
| 业务语义 | 当前驳回分支 | 其他未完成分支 | 已完成并等待 Join 的分支 | 推荐目标 |
|---|---|---|---|---|
| 局部补正 | 回到本分支前序节点 | 继续办理 | 保留结果 | 仍在同一 Fork—Join 区间内 |
| 保留结果、暂停本轮 | 回到本分支前序节点 | 冻结或限制提交 | 保留结果 | 修正后重新汇合 |
| 本轮整体失败 | 取消当前任务并回退 | 一并取消 | 收拢等待令牌 | Fork 之前的单一节点 |
这三种策略没有谁天然更高级。财务、法务分别审查同一份合同,法务要求补材料而财务结论仍有效,适合局部补正;“任意部门不同意即退回申请人”的行政审批,通常适合整体驳回;多个分支共同修改同一组业务字段时,则往往需要先冻结,再由产品规则决定是否保留结果。

图 1:保留、冻结和整体收拢对应三种业务承诺,不能只靠一个“驳回”按钮隐式决定。
并行网关为什么不会替你处理其他分支
BPMN 2.0 的并行网关解决的是令牌分裂与同步问题,不是审批意见裁决问题。Fork 为每条流出顺序流创建并行执行路径;Join 等待所需的流入路径到齐后再继续。它不知道“不同意”是什么意思,也不知道财务分支的结论能否在法务补正后继续有效。
因此,“A 分支驳回”至少包含两个层面的决策:
- 流程层:哪些执行令牌要移动、取消或保留;
- 业务层:哪些审批意见、业务字段、附件和外部副作用仍然有效。
把变量 approved=false 写入流程实例,并不会自动撤销 B、C 分支;把 A 的 Execution 移到某个活动,也不会自动推导企业想保留哪些审批结果。网关只执行模型语义,审批平台必须补上产品语义。
二、关键概念与能力差异
驳回发生时,其他分支可能处于三种状态
设计处理逻辑前,不能只查询“还有几个待办”。同一并行作用域中的其他分支,至少可能处于以下状态:
1. 仍有活动任务
例如法务分支驳回时,财务任务仍在某位审批人待办箱中。系统要明确它是否还能提交。若允许继续,后续结论可能基于旧版本材料;若不允许继续,应用层需要给任务加业务锁或统一取消本轮任务。
2. 已完成并等待 Join
财务任务可能已经完成,其执行令牌正在并行 Join 处等待法务分支。此时数据库里未必还有财务待办,但并不代表该分支“消失了”。局部驳回时应保留这个等待令牌;整体驳回时则必须把它纳入迁移集合。
3. 已产生不可逆副作用
有的分支完成后已经调用付款、发消息、写外部台账或盖章。流程引擎移动令牌不会自动补偿这些动作。若分支可能驳回,副作用应尽量放到 Join 之后,或使用可补偿、可幂等的服务任务。
这三种状态决定了其他分支应“继续、等待、取消还是补偿”。只看 ACT_RU_TASK 会漏掉已经到达 Join 的令牌,也会漏掉外部系统状态。
策略一:只驳回当前分支,其他分支继续或等待
局部驳回适用于各分支相对独立、已经完成的意见允许复用,并且驳回目标仍处于同一 Fork—Join 区间内的场景。它的运行时含义是:只移动当前分支对应的执行实例,不触碰兄弟分支。
以 Flowable 8.0.0 为例,公开的变更状态入口是 RuntimeService.createChangeActivityStateBuilder()。如果当前任务已经解析出唯一 executionId,可以只移动该 Execution:
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveExecutionToActivityId(currentExecutionId, targetActivityId)
.changeState();
这里的关键参数是 executionId,不是只有业务含义的 activityId。并行分支、循环重入和多实例都可能让同一个活动 ID 同时存在多个运行实例。只传活动 ID,容易误伤同名的其他实例。
局部驳回还要满足四个约束:
- 目标节点与当前节点处在同一个并行作用域内;
- 不能跨回分裂网关 Fork 之前;
- 其他分支已经形成的结果允许复用;
- 当前分支重新完成后,仍能以一个正确令牌到达对应 Join。
若 B、C 分支已经完成并停在 Join,A 分支退回 A1 后重新审批,再到达 Join,三个令牌仍可正常同步。这是一种“重跑 A、复用 B/C”的审批语义,不是重新发起整轮并行审批。
三、模型架构与运行机制
策略二:保留已完成结果,冻结未完成分支
有些企业既不想撤销已经完成的审批意见,也不希望其他审批人在材料变化期间继续提交。这时可以采用“保留 + 冻结”策略:
- 已完成分支的历史意见和 Join 等待令牌保留;
- 当前驳回分支回到本分支补正节点;
- 其他仍活动的待办在业务层标记为
WAITING_FOR_CORRECTION; - 补正完成后,重新校验材料版本,再解除冻结。
这里的“冻结”通常不是引擎提供的单分支 Suspend。多数引擎的挂起 API 面向整个流程实例或流程定义;对一个用户任务做业务暂停,更稳妥的方式是由任务服务在查询和完成接口上校验业务锁,不允许被冻结任务提交。
推荐给每一轮并行审批保存以下字段:
| 字段 | 作用 |
|---|---|
| roundNo | 标识第几轮并行审批 |
| branchKey | 标识法务、财务等业务分支 |
| materialVersion | 审批依据的材料版本 |
| decisionStatus | 同意、驳回、待重审、失效 |
| reusable | 材料变化后该意见是否仍可复用 |
| engineTaskId / executionId | 关联引擎运行实例 |
补正导致关键字段变化时,即使执行令牌仍可保留,原审批意见也可能不再有效。系统应按字段敏感性或规则表把相关分支标记为“待重审”,而不是把技术上的可保留误当成业务上的可复用。
策略三:整体驳回到 Fork 之前,必须先收拢所有令牌
如果业务规则是“任何一个分支驳回,本轮审批全部作废”,就不能只移动当前分支。正确模型是先定位同一并行作用域内的全部相关执行实例,包括活动任务、已经在 Join 等待的执行以及必要的子作用域,再把它们合并到 Fork 之前的一个目标节点。
Flowable 8.0.0 的 ChangeActivityStateBuilder 明确提供了 moveExecutionsToSingleActivityId(List<String>, String)。官方 Javadoc 说明它用于并行/包容网关、多实例和事件子流程等并行执行场景:
List<String> scopeExecutionIds = parallelScopeResolver
.resolveExecutions(processInstanceId, forkId, joinId);
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveExecutionsToSingleActivityId(scopeExecutionIds, targetBeforeFork)
.changeState();
真正困难的是 resolveExecutions。它不能简单理解为“查询该流程实例的所有 Execution”,否则可能把事件子流程、嵌套子流程或不相关并行域一起收掉。生产实现至少要校验:
- Execution 是否属于目标 Fork—Join 的同一流程作用域;
- 是否存在嵌套并行网关或多实例根 Execution;
- 当前活动是否已经越过 Join;
- 是否还有边界事件、事件订阅、作业和子流程实例;
- 目标节点是否确实位于 Fork 之前并能重新生成一轮干净的分支。
整体收拢会结束其他分支的当前运行实例。系统还需要给历史任务写清楚删除原因,例如“并行轮次整体驳回”,并将对应业务审批意见标记为失效,而不是物理删除审计记录。

图 2:整体驳回不是循环调用多个单节点跳转,而是先计算完整执行集合,再由一次状态变更完成收拢。
四、核心场景与处理策略
最危险的错误:只把当前分支退到 Fork 之前
假设 Fork 后产生 A、B、C 三条分支。B、C 已经完成并在 Join 等待,A 驳回。若系统只把 A 的 Execution 移到 Fork 之前,流程重新经过 Fork 时会再次创建 A2、B2、C2;与此同时,旧的 B1、C1 仍存在。
最终可能出现:
- B、C 各出现两套执行令牌和重复待办;
- Join 收到超过模型预期的令牌;
- 旧令牌与新令牌混合,提前触发或永远无法满足同步条件;
- 下游服务任务被重复执行;
- 历史轨迹看起来像同一审批人无故审批两次。

图 3:跨过 Fork 的回退必须处理整个并行域,否则旧 B/C 与新 B/C 会同时存在。
因此可以把安全边界写成一条硬规则:
目标仍在当前分支内,可以移动单个 Execution;目标跨到 Fork 之前,必须先收拢当前并行作用域的全部令牌。
Flowable 8.0.0 的实现边界
截至 2026 年 8 月 2 日,Flowable 官方 GitHub 最新正式版为 8.0.0。其 ChangeActivityStateBuilder 为不同令牌数量提供了不同方法,选错 API 本质上就是选错运行时拓扑。
| 迁移拓扑 | 推荐 API | 并行驳回中的典型用途 |
|---|---|---|
| 一个 Execution 到一个节点 | moveExecutionToActivityId | 只退当前分支 |
| 多个 Execution 合并到一个节点 | moveExecutionsToSingleActivityId | 整体退到 Fork 前 |
| 一个 Execution 拆到多个节点 | moveSingleExecutionToActivityIds | 受控重建并行路径 |
| 一个活动 ID 到另一个活动 ID | moveActivityIdTo | 仅在活动实例唯一且已严格校验时使用 |
| 多个活动 ID 合并到一个节点 | moveActivityIdsToSingleActivityId | 活动级批量收拢,仍需防同名多实例 |
changeState() 最终交给动态状态变更逻辑处理 Execution 的销毁、合并与目标活动启动。它解决的是引擎运行时一致性,不会自动处理业务表、外部 API 补偿、审批意见有效性和消息幂等。
Flowable 8.0.0 发布说明中还包含“修复把并行多实例活动移动到单一活动的问题”。这提醒我们:并行网关与多实例叠加时,不能把 API 存在等同于所有拓扑都天然安全。升级引擎后仍应回归本企业的嵌套网关、会签、事件子流程和连续驳回用例。
五、数据、规则与状态设计
Camunda 7 与 Camunda 8 如何对应理解
Camunda 7 可以通过 Process Instance Modification 组合“启动目标活动”和“取消源活动实例”。单分支补正应取消精确的 activityInstanceId;整体驳回则要遍历 Activity Instance Tree,覆盖同一并行作用域中的活动实例和必要的 Transition Instance。仅使用 cancelAllForActivity(activityId) 可能误伤循环或多实例中的同名活动。
Camunda 8 的 Process Instance Modification 使用元素实例键精确定位,并在当前版本提供 Move Instruction。官方文档特别提醒,修改可以制造模型正常执行无法到达的状态;错误地在并行 Join 之前再次激活活动,可能让实例卡在网关。因此,Flowable 的“多 Execution 合一”方案不能机械翻译成 Camunda 7/8 的一组 API 调用,必须按各自的实例树、作用域和修改语义重新实现。
| 引擎 | 精确标识 | 单分支处理 | 整体处理重点 |
|---|---|---|---|
| Flowable 8 | executionId | 移动单个 Execution | 收集并合并并行域 Execution |
| Camunda 7 | activityInstanceId | 取消源实例并启动目标 | 遍历 Activity Instance Tree,成组取消 |
| Camunda 8 | elementInstanceKey | Move 精确元素实例 | 计算元素实例与祖先作用域,防止非法状态 |
六、工程实现与系统集成
历史轨迹和业务状态应该怎样记录
一次并行驳回至少要形成三类可审计记录。
第一类是操作日志:谁在什么时间,对哪个分支执行了何种驳回策略,目标节点是什么,理由是什么。第二类是引擎历史:原任务正常结束、被取消还是被状态变更删除,新的任务属于哪个审批轮次。第三类是业务审批意见:原意见继续有效、暂时冻结还是因整轮驳回而失效。
不要“恢复”旧的已完成 Task。重审应创建新的任务实例,并通过 roundNo、previousOpinionId 或 supersededBy 关联旧记录。这样才能回答:第二次审批依据的是哪一版材料,第一次意见为什么失效,谁触发了整轮回退。
并行分支写共享流程变量也要谨慎。A、B 同时写 approved 或 comment,最后提交者会覆盖前者。更稳妥的方式是使用分支局部变量或带分支键的结构,例如 decision.legal、decision.finance,在 Join 后由聚合服务统一生成总状态。
七、安全、性能与治理要求
低代码平台怎样把它做成可配置能力
企业级低代码平台不应只提供一个“驳回”开关,而应在流程设计阶段显式配置并行驳回策略:驳回范围、其他活动任务如何处理、已完成意见能否复用、材料变化触发哪些分支重审、跨 Fork 时如何收拢、外部动作怎样补偿。

云程低代码开发平台可以把这些规则沉淀为节点和网关级元数据,并在发布流程前做静态校验:如果目标跨越 Fork,却选择“仅移动当前执行”,设计器直接阻止发布;如果选择保留其他分支,却存在共享可写变量或不可逆服务任务,则给出风险提示。这样能把一次性的项目代码变成可测试、可审计的产品能力。

图 4:先判断目标范围、结果复用和副作用,再决定其他分支去向。
八、案例实践与迁移路线
上线前至少测试这些场景
- A 驳回时 B、C 都未完成,验证它们是继续、冻结还是取消;
- B 已完成并停在 Join,A 局部驳回后重审,验证 Join 最终只通过一次;
- A 连续驳回两次,验证每轮只创建一个新任务,历史轮次清晰;
- A 整体驳回到 Fork 前,验证旧 B/C 待办与等待令牌全部消失;
- 并行网关内嵌多实例,验证 Execution 集合没有遗漏或扩大;
- 存在边界定时器、消息订阅和异步作业,验证状态变更后无孤儿资源;
- 两个管理员并发执行驳回,验证乐观锁、幂等键和重复提交保护;
- 驳回事务成功而业务补偿失败,验证 Outbox、重试与人工修复通道;
- 流程引擎升级后复跑上述用例,重点关注并行多实例合并;
- 检查待办、已办、流程轨迹和业务单据对同一轮次的展示一致性。
测试断言不能只有“接口返回成功”。还要断言活动任务数、Execution 树、Join 前令牌数、历史删除原因、审批意见有效性、外部副作用次数和最终业务状态。
九、平台落地、测试与选型
标准答案
并行网关中的一个分支驳回,其他分支怎么办?
标准答案是:先由业务定义驳回范围,再按运行时令牌拓扑执行。若只是当前分支补正,移动当前分支的精确 Execution,其他分支继续或在 Join 等待;若已完成结果可以复用但活动任务不能继续,则保留已完成分支并在业务层冻结未完成任务;若任何分支驳回代表本轮整体失败,就必须收集同一 Fork—Join 作用域内的全部执行实例,取消活动任务、收拢等待令牌,再合并到 Fork 之前的单一目标节点。
绝不能只把一个分支退回 Fork 之前而保留其他旧令牌。这样会在重新经过 Fork 时生成重复分支,导致重复待办、错误汇合或流程卡死。并行网关负责同步令牌,不负责判断审批意见是否有效;审批平台还必须同步处理轮次、历史意见、共享变量、业务状态和外部副作用。

浙公网安备 33010602011771号