Skip to content

Commit 5c766e4

Browse files
authored
Merge pull request #134 from jaquanli/main
解决流程节点接口返回数据问题
2 parents 08f1689 + e7f1202 commit 5c766e4

5 files changed

Lines changed: 432 additions & 2 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# processNodes 接口返回数据异常
2+
3+
> 编码: 107562 | 日期: 2026-05-14 | 严重度: S2 | 状态: 待确认
4+
5+
## 现象描述
6+
7+
processNodes 接口返回的数据并没有按照一个确定的流程路径来返回,而是返回了所有的流程节点。当遇到条件分支时,应该默认走其他情况,但目前由于可能包含多条条件分支,所以都默认走其他情况导致返回数据不符合预期。
8+
9+
## 关键信息
10+
11+
- **可能关联的模块**
12+
- flow-engine-starter-api(REST API 层)- 提供 `/api/cmd/record/processNodes` 接口
13+
- flow-engine-framework(节点引擎)- 包含节点执行、分支过滤逻辑
14+
- FlowProcessNodeService(处理流程节点信息的服务)
15+
16+
- **受影响的功能**
17+
- 流程节点信息获取
18+
- 条件分支节点的路径过滤
19+
- processNodes 接口的数据返回结果
20+
21+
- **初步判断**
22+
问题可能出现在节点的 `filterBranches()` 方法或 FlowProcessNodeService 的分支过滤逻辑中。当遇到条件分支时,应该根据条件脚本执行结果选择匹配的分支,但当前实现可能没有正确处理多条件分支的过滤,导致返回了所有节点而不是遵循特定的流程路径。
23+
24+
## 期望行为
25+
26+
processNodes 接口应该返回从当前节点开始、按照确定的流程路径(基于条件分支的默认分支或其他逻辑)逐步推进的节点列表,而不是返回所有可能的节点。当遇到条件分支时,应该:
27+
28+
1. 根据条件分支的条件脚本执行结果选择匹配的分支
29+
2. 如果条件不匹配,则默认走其他(else)分支
30+
3. 最终返回按照这一确定路径所包含的所有节点
31+
32+
## 备注
33+
34+
{待人工补充}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# processNodes 接口分支节点排序异常
2+
3+
> 编码: 116393 | 日期: 2026-05-14 | 严重度: S2 | 状态: 待确认
4+
5+
## 现象描述
6+
7+
`processNodes` 接口返回的节点列表中,并行分支节点(`ParallelBranchNode`)、人工分支节点(`ManualBranchNode`)、包容分支节点(`InclusiveBranchNode` / `InclusiveElseBranchNode`)等分支类节点出现在了结束节点(`EndNode`)之后,未按流程图中的节点定义顺序排序,导致前端展示的流程路径不直观。
8+
9+
## 关键信息
10+
11+
- **可能关联的模块**
12+
- flow-engine-starter-api(REST API 层)- 提供 `/api/cmd/record/processNodes` 接口
13+
- flow-engine-framework(节点引擎)- `FlowProcessNodeService``NextNodeLoader`
14+
15+
- **可能关联的节点类型**
16+
- `ParallelNode` / `ParallelBranchNode`(并行分支)
17+
- `ManualNode` / `ManualBranchNode`(人工分支)
18+
- `InclusiveNode` / `InclusiveBranchNode` / `InclusiveElseBranchNode`(包容分支)
19+
20+
- **受影响的功能**
21+
- `processNodes` 接口的节点返回顺序
22+
- 流程预览中分支节点位置展示
23+
24+
- **初步判断**
25+
问题可能在 `FlowProcessNodeService.NextNodeLoader.fetchNextNode()``flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java:170-192`)的递归遍历逻辑中。当前实现是深度优先递归,将每个节点 add 到一个共享 `nodeList`,分支控制节点的子分支可能在递归回溯阶段才被加入,导致顺序晚于 End 节点。需要确认递归顺序与节点 `order` 字段的关系,或在遍历完成后按节点定义顺序重新排序输出。
26+
27+
## 期望行为
28+
29+
`processNodes` 接口返回的节点列表应按照流程图中的节点定义顺序排序(即按 Start → 各业务节点 → 各分支控制节点 → End 的顺序),分支节点应出现在它实际所在的流程位置,而不是 End 节点之后。
30+
31+
## 备注
32+
33+
{待人工补充}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# 修复计划: processNodes 接口返回数据异常
2+
3+
> 编码: 107562 | 日期: 2026-05-14 | 类型: Bug 修复 | 来源: bugfix | 基于: docs/bugs/107562-processNodes-节点路径错误.md
4+
5+
## 根因分析
6+
7+
问题位于 `FlowProcessNodeService.NextNodeLoader.fetchNextNode()``flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java:170-192`),关键代码在第 189-190 行:
8+
9+
```java
10+
List<IFlowNode> nextNodes = workflow.nextNodes(flowNode);
11+
this.fetchNextNode(flowSession.updateSession(flowNode), nextNodes);
12+
```
13+
14+
`workflow.nextNodes(flowNode)``Workflow.java:346`)只负责从节点定义中获取所有下一节点,不做条件过滤。运行时分支选择是由 `FlowSession.matchNextNodes()``FlowSession.java:286-293`)通过调用 `nextNode.filterBranches(nodeList, this)` 来完成的。
15+
16+
`NextNodeLoader.fetchNextNode()``processNodes` 接口的预览路径,**完全没有调用 `filterBranches()`**:当 `flowNode``ConditionNode` / `InclusiveNode` 时,`workflow.nextNodes()` 直接返回该控制节点下的**所有分支节点**(多个 `ConditionBranchNode` + 一个 `ConditionElseBranchNode`),随后递归全部遍历,导致接口返回了所有可能路径的节点集合。
17+
18+
期望行为是:**调用 `filterBranches()` 让分支节点自身按条件脚本决策**——
19+
- 若 if 分支的条件脚本能依据表单参数返回 `true`,则走匹配的 if 分支
20+
- 若 if 全部不匹配(或表单数据缺失导致脚本异常),则 fallback 到 else 分支
21+
22+
这一行为在 `ConditionBranchNode.filterBranches``ConditionBranchNode.java:54-67`)中已经实现:它按 `node.handle(flowSession)` 收集匹配的分支并取 order 最小的;而 `ConditionElseBranchNode.handle``ConditionElseBranchNode.java:41-43`)恒返回 `true`,天然兜底。问题只在于 `processNodes` 路径根本没去调它。
23+
24+
证据:
25+
- `FlowProcessNodeService.java:170-192` 递归未做分支过滤
26+
- `FlowSession.matchNextNodes``FlowSession.java:286-293`)是正确调用 `filterBranches()` 的对照实现
27+
- 前一次提交 `a9ffb2b refactor(024982): processNodes 数据返回修复` 只处理了历史节点合并,未处理分支选择问题
28+
29+
## 影响面评估
30+
31+
- 直接修改的 Maven 模块: `flow-engine-framework`
32+
- 可能回归的场景:
33+
- 含条件分支(`ConditionNode`)的流程预览
34+
- 含包容分支(`InclusiveNode`)的流程预览
35+
- 已运行流程的"未来节点"预览(包含分支跨越的情况)
36+
- 普通无分支流程(应当无任何变化)
37+
- 是否需要数据迁移/配置变更: 否
38+
39+
## 新增文件
40+
41+
| 文件路径 | 用途说明 |
42+
|----------|----------|
43+
| `flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowProcessNodeBranchTest.java` | 针对条件分支与包容分支默认走 else 的回归用例 |
44+
45+
## 修改文件
46+
47+
| 文件路径 | 修改内容 |
48+
|----------|----------|
49+
| `flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java` |`NextNodeLoader.fetchNextNode()` 中获取 `nextNodes` 之后,对条件 / 包容分支节点集合调用 `filterBranches(nextNodes, flowSession)` 进行决策;条件脚本执行异常(表单数据不完整等)时降级到 else 分支。运行时分支选择逻辑无变化 |
50+
51+
## 关键代码修改
52+
53+
`NextNodeLoader.fetchNextNode()` 内将原本:
54+
55+
```java
56+
List<IFlowNode> nextNodes = workflow.nextNodes(flowNode);
57+
this.fetchNextNode(flowSession.updateSession(flowNode), nextNodes);
58+
```
59+
60+
改为先调用新增私有方法 `selectBranchNodes(...)` 过滤之后再递归:
61+
62+
```java
63+
List<IFlowNode> nextNodes = workflow.nextNodes(flowNode);
64+
List<IFlowNode> selected = selectBranchNodes(nextNodes, flowSession.updateSession(flowNode));
65+
this.fetchNextNode(flowSession.updateSession(flowNode), selected);
66+
```
67+
68+
新增方法的设计要点:
69+
70+
```java
71+
private List<IFlowNode> selectBranchNodes(List<IFlowNode> nextNodes, FlowSession flowSession) {
72+
if (nextNodes == null || nextNodes.isEmpty()) {
73+
return nextNodes;
74+
}
75+
IFlowNode first = nextNodes.get(0);
76+
String type = first.getType();
77+
boolean isBranchControl =
78+
ConditionBranchNode.NODE_TYPE.equals(type)
79+
|| ConditionElseBranchNode.NODE_TYPE.equals(type)
80+
|| InclusiveBranchNode.NODE_TYPE.equals(type)
81+
|| InclusiveElseBranchNode.NODE_TYPE.equals(type);
82+
if (!isBranchControl) {
83+
return nextNodes;
84+
}
85+
try {
86+
// 优先按 if 条件脚本匹配;脚本天然 fallback else(else.handle() 恒为 true)
87+
List<IFlowNode> matched = first.filterBranches(nextNodes, flowSession);
88+
if (matched != null && !matched.isEmpty()) {
89+
return matched;
90+
}
91+
} catch (Exception ignored) {
92+
// 表单数据缺失导致条件脚本异常 —— 降级到 else
93+
}
94+
List<IFlowNode> elseOnly = nextNodes.stream()
95+
.filter(n -> ConditionElseBranchNode.NODE_TYPE.equals(n.getType())
96+
|| InclusiveElseBranchNode.NODE_TYPE.equals(n.getType()))
97+
.toList();
98+
return elseOnly.isEmpty() ? nextNodes : elseOnly;
99+
}
100+
```
101+
102+
要点说明:
103+
1. **复用 `filterBranches()` 现有逻辑**,与运行时(`FlowSession.matchNextNodes`)行为一致:若表单参数能让 if 条件脚本返回 `true`,则走匹配的 if 分支;若 if 不匹配,由 `ConditionElseBranchNode.handle()``true` 的特性自然走 else
104+
2. **异常降级**:预览阶段表单数据可能为空 / 缺字段,条件脚本访问未定义字段会抛 `groovy.lang.MissingPropertyException` 等;通过 try-catch 将异常降级为"走 else"
105+
3. **兜底**:当 `filterBranches()` 返回空且无 else 分支时,保留原始列表(兼容历史数据,不让分支节点凭空消失)
106+
4. 不影响 `ParallelNode`(并行节点)、`ManualNode``RouterNode` —— 它们各自的 `filterBranches()` 行为不变,由现有递归路径处理
107+
108+
## 回归验证
109+
110+
| 验证项 | 说明 |
111+
|--------|------|
112+
| 表单数据不足走 else | 构造含 `ConditionNode` 的流程,预览时不提供条件字段,验证返回的是 else 分支路径 |
113+
| 表单数据匹配 if 走 if | 构造含 `ConditionNode` 的流程,提供能让 if 条件脚本返回 `true` 的表单参数,验证返回 if 分支路径 |
114+
| 多条件分支场景 | 构造含两个嵌套 `ConditionNode` 的流程,验证按各自表单条件分别走 if 或 else,最终路径唯一 |
115+
| 包容分支场景 | 构造含 `InclusiveNode` 的流程,验证按条件匹配(无数据时走 `InclusiveElseBranchNode`|
116+
| 无分支场景 | 普通直线流程(Start → Approval → End),验证 `processNodes` 行为无变化 |
117+
| 关联用例 | 运行 `flow-engine-framework` 既有测试(`FlowDetailServiceTest` 等),保证未引入回归 |
118+
| 新增用例 | 新建 `FlowProcessNodeBranchTest` 覆盖上述四类场景 |
119+
| 编译验证 | `mvn -pl flow-engine-framework -am compile` |
120+
121+
具体验证命令:
122+
123+
```bash
124+
mvn -pl flow-engine-framework -am compile
125+
mvn -pl flow-engine-framework test -Dtest=FlowProcessNodeBranchTest
126+
mvn -pl flow-engine-framework test
127+
```
128+
129+
## 执行顺序
130+
131+
1. 创建 git worktree(按项目规范,所有开发必须在 worktree 中进行)
132+
2. 修改 `flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java`
133+
- 引入 `ConditionBranchNode` / `ConditionElseBranchNode` / `InclusiveBranchNode` / `InclusiveElseBranchNode` 的 import
134+
- 新增私有方法 `selectDefaultBranchNodes(List<IFlowNode>)`
135+
- 修改 `NextNodeLoader.fetchNextNode()` 在递归前调用过滤方法
136+
3. 创建 `flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowProcessNodeBranchTest.java` 覆盖五类场景:
137+
- 表单缺失条件字段 → 走 else 分支
138+
- 表单提供匹配条件 → 走对应 if 分支
139+
- 多嵌套条件分支 → 按各自数据决策
140+
- 包容分支按表单匹配 / 缺失走 else
141+
- 无分支流程不受影响
142+
4. 执行 `mvn -pl flow-engine-framework -am compile` 编译验证
143+
5. 执行 `mvn -pl flow-engine-framework test` 完整回归
144+
6. 人工审核后提交 PR(目标分支:dev)
145+
146+
## 注意事项
147+
148+
- 严格遵循 CLAUDE.md 中的开发规范:本变更必须在 git worktree 中进行,PR 目标为 `dev` 分支
149+
- 不修改 `FlowSession.matchNextNodes()` 等运行时路径,避免影响实际审批流转
150+
- 修复仅作用于预览路径(`FlowProcessNodeService`),运行时分支选择仍由 `filterBranches()``FlowActionService` 等动作触发时使用
151+
152+
## 变更说明
153+
154+
- 2026-05-14: 根据用户澄清调整 —— 条件分支决策不应一刀切走 else,而应在 if 条件参数有值且匹配时走 if,仅在数据缺失 / 不匹配时 fallback 到 else。实现方式从"强制 else"改为"调用 `filterBranches()` + 异常降级 else",与运行时 `FlowSession.matchNextNodes` 行为一致。

0 commit comments

Comments
 (0)