Flowable 驳回到上一节点:普通节点、多实例和并行网关处理
“驳回到上一节点”听起来只有一个目标,真正落到 Flowable 运行时却可能对应三种完全不同的动作:移动一个 Execution、结束整个多实例活动,或者收拢并行网关产生的多条 Execution。若不先识别当前执行树结构,就把所有场景统一写成 moveActivityIdTo,普通流程可能正常,多实例会一次移动多个任务,并行网关则可能留下孤立令牌或制造重复路径。
本文以截至 2026 年 8 月 2 日最新正式版 Flowable 8.0.0 为基准,结合 ChangeActivityStateBuilder、AbstractDynamicStateManager,以及官方 ChangeStateTest、ChangeStateForMultiInstanceTest 和 ChangeStateForGatewaysTest,说明三类场景应该怎样定位上一节点、选择迁移 API、处理剩余任务并验证迁移结果。
一、核心结论与问题边界
一句话回答:先识别执行结构,再决定移动一个还是一组 Execution
可以先记住三个默认策略:
- 普通单实例任务:取当前活动 task 的 executionId,精确移动到目标 activityId;
- 多实例任务:先定义“驳回一个实例”还是“结束整组会签”,再决定移动单个 Execution 或整个多实例活动;
- 并行网关分支:分支内回退只移动本分支,整体退回网关之前则必须收拢全部相关 Execution。
因此,“上一节点”不是一个纯历史查询问题,而是“目标节点解析 + 当前执行树分类 + 作用域迁移策略”的组合问题。
截至 2026 年 8 月,最新 Flowable 代码提供了哪些能力
Flowable 8.0.0 发布于 2026 年 2 月 27 日。与本文直接相关的一项修复是“将并行多实例活动移动到单一活动时的问题”,这再次说明多实例与多 Execution 合并是状态迁移的核心边界,而不是普通 taskId 更新。
ChangeActivityStateBuilder 暴露的主要迁移方法包括:
| API | 运行时含义 | 典型用途 |
|---|---|---|
| moveExecutionToActivityId | 一个 Execution 移到一个活动 | 普通任务、精确分支回退 |
| moveActivityIdTo | 当前 activityId 对应活动迁到目标 | 单活动回退、整组多实例迁移 |
| moveExecutionsToSingleActivityId | 多个 Execution 收拢到一个活动 | 并行分支整体退回或越过汇聚 |
| moveSingleExecutionToActivityIds | 一个 Execution 拆到多个活动 | 明确需要重新建立多条路径 |
| moveActivityIdsToSingleActivityId | 多个 activityId 收拢到一个活动 | 已知多个活动节点整体迁移 |
ChangeActivityStateBuilder 只是通用状态改变接口。Flowable 不知道“上一审批节点”的业务定义,也不会自动决定会签剩余任务和并行分支应该怎样处理。
“上一节点”至少有四种业务口径
同一个按钮可能被不同产品理解为:
- BPMN 模型中当前节点的直接前驱;
- 当前流程实例实际经过的上一用户任务;
- 当前审批人在本分支看到的上一审批人;
- 允许选择的任意已办节点。
直接前驱可以从 BPMN incoming Sequence Flow 解析,但排他网关、循环和条件路径下,它未必实际执行过。上一已办节点可以从历史轨迹寻找,却必须排除网关、服务任务、迁移删除任务和其他并行分支。上一审批人还要结合轮次、会签参与人和业务操作记录。
推荐把“驳回上一节点”定义为:当前运行分支内、同一允许作用域中,最近一次正常到达且具备驳回资格的用户任务节点。若产品要支持“模型直接前驱”或“任意已办节点”,应拆成不同操作入口。
二、关键概念与能力差异
源码为什么证明 activityId 可能对应多个 Execution
Flowable 8.0.0 的 AbstractDynamicStateManager.resolveActiveExecutions 会查询流程实例全部子 Execution,再筛选 currentActivityId 等于指定 activityId 的记录。也就是说,按 activityId 迁移不是天然只命中一个运行实例。
源码随后检查 Execution 是否为 multiInstanceRoot,或是否处于多实例内部;如果是,会按父 Execution 分组构造迁移容器。若不在多实例内,才从匹配结果中取一个活动 Execution。由此可以得到一个重要结论:
- 普通节点中 activityId 通常只有一个活动 Execution;
- 并行多实例中,同一个 activityId 会对应多个任务与子 Execution;
- 嵌套多实例、子流程或网关时,还要按父作用域分组;
- 要精确控制某一任务路径,应优先以当前 task.executionId 定位。
这不是 API 命名差异,而是迁移范围的差异。

图 1:先检查运行时结构,再决定移动单个 Execution、整个多实例活动或并行分支集合。
普通节点:最稳妥的是按当前 executionId 精确回退
顺序流程 A 申请 → B 部门经理 → C 财务经理,当前 C 需要驳回到 B。普通单实例场景通常只有一个活动任务和一个叶子 Execution,可以使用:
- runtimeService.createChangeActivityStateBuilder()
- .processInstanceId(processInstanceId)
- .moveExecutionToActivityId(currentTask.getExecutionId(), "taskB")
- .changeState()
Flowable 官方 ChangeStateTest 同时验证了 moveActivityIdTo("secondTask", "firstTask") 和 moveExecutionToActivityId(task.getExecutionId(), "firstTask")。迁移后当前活动触发 ACTIVITY_CANCELLED,目标活动触发 ACTIVITY_STARTED,查询到的新任务 taskDefinitionKey 变为 firstTask。
企业实现中更建议保留 processInstanceId 参与业务校验,并以 taskId 查询出的当前 executionId 作为精确迁移对象。驳回完成后,B 上会创建新的任务实例,不会复活此前完成的 B1。
普通节点还要处理边界定时器和事件订阅
如果当前 User Task 挂有 Boundary Timer,迁移离开该节点时不仅要结束任务,还要取消对应 Timer Job;重新进入带定时器的目标节点时,又要创建新的定时作业。Flowable 官方测试明确断言了 JOB_CANCELED、ACTIVITY_CANCELLED、ACTIVITY_STARTED 和 TIMER_SCHEDULED 等事件顺序。
因此不要直接修改 ACT_RU_EXECUTION.ACT_ID_。Change State 的价值不仅是改变活动位置,还包括删除关联任务、作业、事件订阅和子 Execution,再按目标 FlowElement 的行为重新启动。
普通节点迁移后至少验证:当前活动任务只有一条;旧 taskId 已结束;目标 taskId 为新值;目标节点边界定时器已重新调度;业务单据状态与新节点一致。

图 2:普通节点回退移动当前叶子 Execution,旧任务取消,目标节点产生新一轮任务及关联作业。
三、模型架构与运行机制
多实例节点:首先确认驳回发生在节点内还是节点之后
多实例有两个常见位置:
- 当前正在会签,A、B、C 三个人各有一条任务,其中一人点击驳回;
- 会签已经整体结束,后续节点需要驳回并重新启动上一会签节点。
前者必须决定剩余会签任务怎样处理;后者必须决定是否按原参与人快照重新创建整组任务。不能仅看到 taskDefinitionKey 相同,就把一条历史会签任务当成普通上一节点。
多实例 Activity Body 通常包含 multiInstanceRoot、多个子 Execution、nrOfInstances、nrOfActiveInstances、nrOfCompletedInstances 和 loopCounter 等状态。驳回策略必须维护这些计数和作用域关系。
会签进行中:驳回一个人和驳回整组不是一回事
假设并行会签节点有 task-A、task-B、task-C,B 点击“驳回上一节点”。平台通常有三种产品语义:
| 产品语义 | 推荐处理 | 风险提示 |
|---|---|---|
| B 个人退出会签 | 精确处理 B 的 execution | 完成条件和计票规则必须允许 |
| 一人驳回即整组退回 | 结束整个多实例作用域,再回到前置节点 | 其他任务统一取消并保留意见 |
| 先记录反对票,节点完成后决定 | 正常完成 B,按 completionCondition 收敛 | 不立即执行状态迁移 |
中国式 OA 最常见的是“一人驳回即整组退回”。这时不应只移动 B 的 execution,否则 A、C 仍处于活动状态,多实例父执行还在等待计数收敛。平台应把整个会签 activityId 作为迁移范围,或显式收集同一多实例父作用域下的全部活动 Execution,再进入前置节点。
如果产品允许“仅 B 回退”,必须说明 B 回退后是否仍属于原多实例、A/C 结束后谁负责汇聚,以及 B 再次回来如何计算 nrOfCompletedInstances。这通常不是标准会签的自然语义,测试成本很高。
四、核心场景与处理策略
从后续节点驳回到上一会签节点:会重新创建整组实例
流程为申请 → 并行会签 → 总经理,当前总经理驳回到会签节点。此时当前只有总经理的一个 Execution,目标却是多实例 Activity。迁移进入目标节点后,Flowable 会根据目标活动的 MultiInstanceLoopCharacteristics 创建多实例根和内部实例。
Flowable 官方多实例测试显示:从 beforeMultiInstance 移到 parallelTasks 后,会形成一个多实例根和三个并行 Execution,并产生三条 active task;从 parallelTasks1 移动到 parallelTasks2 时,同样会在目标多实例活动形成三个任务。
企业系统必须把参与人集合作为上一轮快照还是重新计算写清楚:
- 原人重审:使用上一轮参与人快照,保证审计可解释;
- 按当前组织重算:适用于人员变动,但要记录人员差异;
- 指定部分人员重审:生成新的参与人集合并标记新轮次。
不要依赖历史 taskId 重建会签人。应在业务表保存 activityId、roundNo、participantSnapshot、decision 和 sourceTaskId。
Flowable 官方测试怎样迁出并行多实例
ChangeStateForMultiInstanceTest 中,一个并行多实例包含三个活动任务。完成其中一个后,仍有两个活动任务;随后调用 moveActivityIdTo("parallelTasks", "nextTask"),测试断言子 Execution 收缩为一个,且只剩 nextTask。
这个行为适合“结束整个会签并进入单一目标”的场景,也说明 activityId 级迁移处理的不是点击按钮的那一个 task,而是该活动作用域中的活动实例集合。Flowable 8.0.0 发布说明中对“并行多实例活动迁到单一活动”的修复,更值得在升级后做专项回归。

图 3:会签中的个人退回、整组退回和记票后收敛是三种不同产品语义。
五、数据、规则与状态设计
并行网关:先区分分支内回退和整体退回
流程在并行网关后产生法务、财务两条分支。财务审批人点击驳回时,可以有两种完全不同的目标:
- 退回财务分支内部的上一个节点:只移动财务 execution,法务分支继续;
- 退回并行网关之前的申请节点:必须取消或收拢法务、财务以及可能等待在 Join 的全部相关 execution。
分支内回退可用 moveExecutionToActivityId 精确定位当前分支。整体退回则不能只移动财务 execution 到网关之前,否则法务令牌仍可能继续进入 Join,而申请人重提后网关又会产生新令牌,最终出现重复任务或错误汇聚。
整体退回网关之前:使用多 Execution 收拢语义
整体退回可采用以下处理顺序:

图 4:整体驳回先收拢并行作用域中的全部活动与等待 Execution,再回到 Fork 前的单一节点。
- 根据并发父作用域识别本次并行拆分产生的全部活动 Execution;
- 纳入仍在用户任务上的分支,以及已经等待在汇聚网关上的 Execution;
- 统一校验这些 Execution 属于同一并行作用域;
- 调用 moveExecutionsToSingleActivityId(executionIds, beforeForkActivityId);
- 断言迁移后只存在目标任务和预期数量的子 Execution。
Flowable 官方 ChangeStateForGatewaysTest 有两类直接证据:两条分支活动时,把两个 executionId 收拢到 taskAfter,最终只剩一个 Execution;把多个 Execution 移入 parallelJoin 时,引擎让每个传入 Execution 自然进入汇聚网关,然后继续到 taskAfter。
用于“整体向前越过分支”的测试,同样证明了“多分支整体退回到单一前置节点”应使用一组 Execution 的收拢语义。这是基于官方 API 和测试拓扑作出的工程推论,目标 activityId 需要替换为实际的网关前用户任务。
六、工程实现与系统集成
一条分支已经到 Join,整体驳回该怎样处理
最容易漏掉的状态是:法务已经完成,法务 Execution 正等待在 Parallel Join;财务仍有活动任务。此时待办查询只有财务一条,但执行树仍有两个分支 Execution。
整体驳回不能只查询 active Task 的 executionId。应同时查询并发作用域下的活动 Execution,把财务分支和 Join 等待分支一起纳入迁移集合。Flowable 官方网关测试也覆盖了“一条分支已在 synchronizing gateway,另一条分支仍活动”的组合,并将两条 Execution 一起移动到单一活动。
因此,运行时识别应以 Execution 树为主,待办列表只能作为用户任务入口,不能代表完整令牌集合。
为什么不能随便把一个 Execution 移到 Parallel Join
AbstractDynamicStateManager 源码特别说明:并行网关 Join 需要每个传入 Execution 自然进入网关,因为汇聚逻辑要检查 Execution 数量。当目标 FlowElement 是 Gateway 且迁入多个 Execution 时,源码会为其创建相应子 Execution,让它们分别进入网关。
若只把一条分支直接移到 Join,而其他必达分支已经被取消或根本不存在,Join 可能持续等待;若把网关前的一个普通 Execution 直接移到 Join,又可能绕过 Split 产生错误的到达数。
推荐原则是:
- 分支内驳回,不跨越 Fork 和 Join;
- 整体驳回网关之前,收拢完整并行作用域;
- 移入 Join 时,显式提供所有应该到达的 Execution;
- 从 Join 之后回到网关之前,可以移动当前单 Execution,随后让流程按正常 Sequence Flow 再次经过 Split。
七、安全、性能与治理要求
三类场景的统一决策算法
平台可以在执行驳回前完成五步分类:
- 用 taskId 查询活动任务并取得 executionId、activityId、processInstanceId;
- 向上遍历 Execution 父链,识别 multiInstanceRoot、并发父作用域和子流程边界;
- 结合 BPMN 模型与业务轨迹解析可驳回目标;
- 根据迁移范围选择单 Execution、activityId 集合或 Execution 集合 API;
- 迁移后重新查询执行树、任务、作业和业务投影进行断言。
| 当前结构 | 默认范围 | 推荐 API |
|---|---|---|
| 普通单任务 | 当前叶子 Execution | moveExecutionToActivityId |
| 并行会签整体驳回 | 多实例活动作用域 | moveActivityIdTo 或明确的 Execution 集合 |
| 并行网关分支内回退 | 当前分支 Execution | moveExecutionToActivityId |
| 并行网关整体退回 | 同一并发作用域全部 Execution | moveExecutionsToSingleActivityId |
API 只是最后一步。真正决定正确性的,是前面三步对目标、作用域和执行集合的识别。
事务、幂等和业务状态怎样一起处理
驳回服务应在同一业务事务边界内完成:当前任务活动校验、操作权限校验、幂等键占位、审批操作记录、引擎状态迁移和业务状态更新。若引擎与业务服务跨库,使用 Outbox 与可重试事件保证最终一致。
迁移后不要只检查“出现目标待办”,还应断言:
- 普通节点只有一个目标活动任务;
- 整组会签驳回后没有遗留原会签活动任务;
- 并行整体驳回后没有遗留分支或 Join Execution;
- 目标任务使用新 taskId,并带有新轮次;
- 旧任务结束原因、审批意见和业务操作类型可以对应;
- 定时器、事件订阅和异步作业数量符合目标节点模型。
八、平台落地、测试与选型
低代码平台应该怎样把复杂度挡在按钮之外
云程低代码开发平台这类工作流产品,可以在设计阶段把技术迁移约束转成业务配置:普通审批默认“退回上一已办节点”;会签节点必须配置“一人驳回整组退回”或“记票后结束”;并行网关必须配置“仅本分支”或“整体退回 Fork 前”。

发布校验器应检查跨作用域目标、Parallel Join 令牌完整性、多实例参与人快照和完成条件。运行时页面只展示模型与权限允许的目标,不能让前端任意提交 activityId。
这样业务人员看到的仍是一个清晰按钮,复杂的 Execution 集合计算、作用域迁移和迁移后断言由平台统一完成。
上线前必须覆盖的测试矩阵
- 普通 C→B 回退,验证旧任务取消、新任务创建和事件顺序;
- 带 Boundary Timer 的任务离开和重新进入,验证作业取消与重建;
- 会签三人中一人驳回整组,验证其他两条任务全部结束;
- 会签完成后从后续节点回到会签,验证参与人快照和新轮次;
- 并行多实例完成一人后整体迁出,验证只剩单一目标任务;
- 并行网关分支内回退,验证另一分支不受影响;
- 两条分支都活动时整体退回 Fork 前,验证 Execution 收拢;
- 一条分支已到 Join、一条仍活动时整体退回,验证等待令牌也被处理;
- 从 Join 后节点退回 Fork 前,再次提交后验证分支只创建一次;
- 重复驳回请求、任务并发完成和乐观锁冲突均保持幂等;
- Flowable 8 升级后回归并行多实例迁到单一活动;
- 迁移失败时业务状态、审批操作记录和待办投影可以恢复。

浙公网安备 33010602011771号