Skip to content

Commit 1adb5e6

Browse files
authored
Merge pull request #144 from codingapi/dev
Dev
2 parents 6bac575 + b393db6 commit 1adb5e6

27 files changed

Lines changed: 703 additions & 34 deletions

docs/components/flow-engine-framework_FlowScriptContext.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ description: Groovy 脚本运行时的 $bind 上下文对象,为脚本提供
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_GatewayContext.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GatewayContext 是框架内部获取流程操作人(IFlowOperator)数据的
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_GroovyScriptBind.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GroovyScriptBind 是脚本编写者在流程 Groovy 脚本中通过 `$bind` 变
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_GroovyScriptRequest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GroovyScriptRequest 是流程 Groovy 脚本中 `request` 参数的实际类型
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_GroovyWorkflowRequest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ GroovyWorkflowRequest 用于工作流级别(非会话级别)的脚本场景
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_IFlowOperator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ IFlowOperator 是流程引擎中对参与流程的用户(审批人、创建者
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_RepositoryHolderContext.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RepositoryHolderContext 是流程引擎运行时的基础设施注册中心。
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

docs/components/flow-engine-framework_ScriptRegistryContext.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ ScriptRegistryContext 是流程引擎中所有默认 Groovy 脚本的注册中
2222
<dependency>
2323
<groupId>com.codingapi.flow</groupId>
2424
<artifactId>flow-engine-framework</artifactId>
25-
<version>0.0.36</version>
25+
<version>0.0.37</version>
2626
</dependency>
2727
```
2828

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# 调整计划: 发起人/审批人指定的人员范围约束
2+
3+
> 编码: 319860 | 日期: 2026-05-20 | 类型: 调整任务 | 来源: 用户需求
4+
5+
## 一、需求与目标
6+
7+
为现有的 **发起人设定(`INITIATOR_SELECT`****审批人设定(`APPROVER_SELECT`** 两种操作人选择能力,增加「可选人员范围」约束:
8+
9+
1. 这两种模式支持配置一个**范围脚本**,复用现有 `OperatorLoadScript`,脚本返回 `List<IFlowOperator>` 作为该节点的「可选人员范围」。
10+
2. 前端在遇到需要设定操作人(发起人指定 / 审批人指定)的节点时,能拿到该节点的候选范围并弹窗,**默认全选**
11+
3.**未设定范围**,则视为可以选择任何人。
12+
4. 提交设定时,后端**主动校验**所选人员是否超出范围,超出则报错提示。
13+
14+
## 二、关键决策(已与用户确认)
15+
16+
| 决策点 | 结论 |
17+
|--------|------|
18+
| 范围脚本承载方式 | **复用** `OperatorLoadStrategy.operatorLoadScript` 字段(不新增字段) |
19+
| 配置了脚本但执行返回空列表 | **等同未配置 = 可选任意人** |
20+
21+
由此得到一条统一规则:
22+
23+
> **可选范围 = 执行 `operatorLoadScript` 的结果列表。结果为空(脚本为 null,或脚本执行返回空)→ 不限范围 → 可选任意人 → 跳过越界校验。**
24+
25+
## 三、现状分析
26+
27+
### 3.1 操作人选择三种模式
28+
29+
`OperatorSelectType``SCRIPT`(默认,脚本直接算出操作人)、`INITIATOR_SELECT`(发起人设定)、`APPROVER_SELECT`(审批人设定)。
30+
31+
### 3.2 现有链路
32+
33+
- `OperatorLoadStrategy``strategy/node/OperatorLoadStrategy.java`
34+
- `SCRIPT` 模式:`loadOperators()` 执行 `operatorLoadScript` 直接得到操作人。
35+
- `INITIATOR/APPROVER` 模式:`loadOperators()` 仅从 `t_flow_operator_assignment` 读已分配的人,**无脚本、无范围概念**
36+
- 扫描需要选人的节点:
37+
- 发起人:`PassAction.run`(StartNode 提交时)→ `InitiatorSelectNodeService.appendNodes`,对未分配的 `INITIATOR_SELECT` 节点产出 `NodeOption`
38+
- 审批人:`PassAction.collectApproverSelectNodes`,对下游未分配的 `APPROVER_SELECT` 节点产出 `NodeOption`
39+
- 产出后通过 `ActionResponse(ResponseType.OPERATOR_SELECT, options)` 返回前端。
40+
- `NodeOption``pojo/response/NodeOption.java`)当前仅含 `id/name/type/display`**不含候选人**
41+
- 保存分配:`FlowCreateService`(发起人)/`FlowActionService`(审批人)遍历 `operatorSelectMap``saveOperatorAssignment(processId, nodeId, operatorIds)`**无范围校验**
42+
- `operatorSelectMap` 值为 `List<Long>`(userId);`IFlowOperator.getUserId()` 返回 `long`,二者可直接比对。
43+
44+
## 四、设计方案
45+
46+
### 4.1 `OperatorLoadStrategy` —— 增加范围脚本与范围计算
47+
48+
1. 工厂方法增加「带脚本」重载(无参重载保留 = 不限范围):
49+
50+
```java
51+
public static OperatorLoadStrategy initiatorSelectStrategy() { ... } // 不限范围
52+
public static OperatorLoadStrategy initiatorSelectStrategy(String rangeScript) { // 带范围
53+
OperatorLoadStrategy s = new OperatorLoadStrategy();
54+
s.selectType = OperatorSelectType.INITIATOR_SELECT;
55+
s.operatorLoadScript = new OperatorLoadScript(rangeScript);
56+
return s;
57+
}
58+
public static OperatorLoadStrategy approverSelectStrategy() { ... } // 不限范围
59+
public static OperatorLoadStrategy approverSelectStrategy(String rangeScript) { ... } // 带范围
60+
```
61+
62+
2. 新增范围计算方法(`loadOperators` 逻辑保持不变):
63+
64+
```java
65+
/** 计算该节点的可选人员范围;返回空表示不限范围(可选任意人) */
66+
public List<IFlowOperator> loadOperatorRange(FlowSession session) {
67+
if (operatorLoadScript == null) {
68+
return List.of();
69+
}
70+
return operatorLoadScript.execute(session); // 返回空亦表示不限
71+
}
72+
```
73+
74+
3. `toMap` / `fromMap` 兼容范围脚本(让 `INITIATOR/APPROVER` 模式也能序列化 `script`):
75+
76+
```java
77+
// toMap:只要存在脚本就写出
78+
if (operatorLoadScript != null) {
79+
map.put("script", operatorLoadScript.getScript());
80+
}
81+
// fromMap:SCRIPT 模式按原逻辑;非 SCRIPT 模式若带 script 则装配为范围脚本
82+
String script = (String) map.get("script");
83+
if (strategy.selectType == OperatorSelectType.SCRIPT) {
84+
strategy.operatorLoadScript = new OperatorLoadScript(script);
85+
} else if (script != null) {
86+
strategy.operatorLoadScript = new OperatorLoadScript(script);
87+
}
88+
```
89+
90+
### 4.2 `NodeStrategyManager` —— 暴露范围计算
91+
92+
```java
93+
public List<IFlowOperator> loadOperatorRange(FlowSession session) {
94+
OperatorLoadStrategy s = getStrategy(OperatorLoadStrategy.class);
95+
return s == null ? List.of() : s.loadOperatorRange(session);
96+
}
97+
```
98+
99+
### 4.3 `NodeOption` —— 携带候选范围返回前端
100+
101+
- 新增字段 `private List<IFlowOperator> operators;`(候选范围;`null`/空 = 不限,前端可选任意人)。
102+
- 新增构造 `NodeOption(IFlowNode node, List<IFlowOperator> operators)`;原 `NodeOption(IFlowNode node)`(手动节点分支选择用)保留。
103+
- 注意:`@AllArgsConstructor` 新增字段后全参构造签名变化,已确认仅 Lombok 生成、无业务代码调用全参构造。
104+
105+
### 4.4 扫描时计算并填充范围
106+
107+
- `InitiatorSelectNodeService.appendNodes`:命中未分配的 `INITIATOR_SELECT` 节点时
108+
109+
```java
110+
FlowSession nodeSession = flowSession.updateSession(node);
111+
List<IFlowOperator> range = node.strategyManager().loadOperatorRange(nodeSession);
112+
operatorSelectNodes.add(new NodeOption(node, range));
113+
```
114+
115+
- `PassAction.collectApproverSelectNodes`:方法增加 `FlowSession flowSession` 入参(递归同步传入),命中未分配的 `APPROVER_SELECT` 节点时
116+
117+
```java
118+
FlowSession nodeSession = flowSession.updateSession(node);
119+
result.add(new NodeOption(node, node.strategyManager().loadOperatorRange(nodeSession)));
120+
```
121+
122+
调用处改为 `collectApproverSelectNodes(flowSession, nextNode, operatorSelectMap, operatorSelectNodes)`
123+
124+
### 4.5 保存前的越界校验(统一封装)
125+
126+
新增框架层服务 `OperatorAssignmentService`(建议置于 `service/impl`),统一「校验 + 保存」,供两个 Service 复用:
127+
128+
```java
129+
public static void validateAndSave(FlowSession baseSession, String processId,
130+
Map<String, List<Long>> operatorSelectMap) {
131+
if (operatorSelectMap == null || operatorSelectMap.isEmpty()) return;
132+
Workflow workflow = baseSession.getWorkflow();
133+
IRepositoryHolder holder = baseSession.getRepositoryHolder();
134+
operatorSelectMap.forEach((nodeId, operatorIds) -> {
135+
IFlowNode node = workflow.getFlowNode(nodeId);
136+
FlowSession nodeSession = baseSession.updateSession(node);
137+
List<IFlowOperator> range = node.strategyManager().loadOperatorRange(nodeSession);
138+
if (range != null && !range.isEmpty()) { // 仅在「有范围」时校验
139+
Set<Long> allow = range.stream().map(IFlowOperator::getUserId).collect(Collectors.toSet());
140+
for (Long id : operatorIds) {
141+
if (!allow.contains(id)) {
142+
throw FlowValidationException.operatorOutOfRange(nodeId);
143+
}
144+
}
145+
}
146+
holder.saveOperatorAssignment(processId, nodeId, operatorIds);
147+
});
148+
}
149+
```
150+
151+
接入点:
152+
153+
- `FlowCreateService.create`:将现有 `saveOperatorAssignment` 循环(第 88–96 行)替换为
154+
`OperatorAssignmentService.validateAndSave(session, flowRecords.get(0).getProcessId(), request.getOperatorSelectMap());`
155+
- `FlowActionService.action`:将现有 `saveOperatorAssignment` 循环(第 87–94 行)下移至 `session` 创建(第 96 行)之后、`flowAction.run(session)`(第 100 行)之前,替换为
156+
`OperatorAssignmentService.validateAndSave(session, flowRecord.getProcessId(), flowAdvice.getOperatorSelectMap());`
157+
`run` 内对下游节点的判断基于 `advice.operatorSelectMap`,不依赖落库顺序;而后续生成下游记录的 `loadOperators` 需在 `run` 前完成落库,故保存放在 `run` 之前即可。)
158+
159+
### 4.6 异常
160+
161+
`FlowValidationException` 新增工厂方法:
162+
163+
```java
164+
public static FlowValidationException operatorOutOfRange(String nodeId) {
165+
return new FlowValidationException("validation.operator.outOfRange",
166+
String.format("Selected operators for node '%s' exceed the allowed range", nodeId));
167+
}
168+
```
169+
170+
## 五、数据与兼容性
171+
172+
- **数据库**:无变更,仍复用 `t_flow_operator_assignment`
173+
- **节点序列化**`OperatorLoadStrategy``script` 字段在 `INITIATOR/APPROVER` 模式下新增写出/读取,旧数据无 `script``operatorLoadScript` 为 null → 不限范围,向后兼容。
174+
- **API**`NodeOption` 新增 `operators` 字段(增量、向后兼容);前端可据此渲染范围并默认全选。
175+
- **工厂方法**:仅新增重载,旧调用不受影响。
176+
177+
## 六、分步实施计划
178+
179+
| 步骤 | 内容 | 验证 |
180+
|------|------|------|
181+
| Step 1 | `OperatorLoadStrategy`(带脚本工厂重载 + `loadOperatorRange` + `toMap/fromMap`)、`NodeStrategyManager.loadOperatorRange``FlowValidationException.operatorOutOfRange` | 编译通过 |
182+
| Step 2 | `NodeOption` 增加 `operators` 字段与构造;`InitiatorSelectNodeService``PassAction.collectApproverSelectNodes` 填充范围 | 编译通过 |
183+
| Step 3 | 新增 `OperatorAssignmentService.validateAndSave`,接入 `FlowCreateService` / `FlowActionService` | 编译通过 |
184+
| Step 4 | 扩展 `OperatorSelectTest` 覆盖范围用例,运行 `./mvnw test -pl flow-engine-framework` | 全量测试通过 |
185+
186+
## 七、测试计划(TDD,扩展 `OperatorSelectTest`
187+
188+
1. 发起人设定 — 带范围 — 选范围内人员 → 流程正常推进。
189+
2. 发起人设定 — 带范围 — 选范围外人员 → 抛 `FlowValidationException`(operatorOutOfRange)。
190+
3. 发起人设定 — 无范围脚本 → 可选任意人(现有行为不破坏)。
191+
4. 发起人设定 — 脚本返回空 → 等同无范围,可选任意人。
192+
5. 审批人设定 — 带范围 — 范围内 / 范围外(同 1、2)。
193+
6. `OPERATOR_SELECT` 响应中 `NodeOption.operators` 返回的范围正确。
194+
7. `OperatorLoadStrategy``toMap → fromMap` 往返保留范围脚本。
195+
196+
## 八、影响面
197+
198+
- 涉及 Maven 模块:`flow-engine-framework`
199+
- 改动文件:`OperatorLoadStrategy``NodeStrategyManager``NodeOption``PassAction``InitiatorSelectNodeService``FlowCreateService``FlowActionService``FlowValidationException`,新增 `OperatorAssignmentService`
200+
- `flow-engine-starter-api``NodeOption` 序列化结构新增 `operators` 字段(前端适配,向后兼容)。
201+
202+
## 九、可选增强(待确认,本计划默认不含)
203+
204+
流程预览接口 `processNodes``FlowProcessNodeService.addFlowNode`)目前对未分配的 `INITIATOR/APPROVER` 节点返回空 `operators`、仅标记 `operatorStrategy`。若希望前端在「发起前预览」阶段即看到各节点候选范围,可在 `ProcessNode` 增加独立字段 `operatorRange` 并用 `loadOperatorRange` 填充。核心需求(提交时弹窗选人)通过 4.4 的 `OPERATOR_SELECT` 响应已可满足,故此项作为可选增强。

flow-engine-example/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>com.codingapi.flow</groupId>
88
<artifactId>flow-engine-parent</artifactId>
9-
<version>0.0.36</version>
9+
<version>0.0.37</version>
1010
<relativePath>../pom.xml</relativePath>
1111
</parent>
1212

0 commit comments

Comments
 (0)