feat: dispatch NamedAction and NamedCondition by key

Hosts register one bean per action/condition; Spring wires the dispatcher when no facade is defined, and REST catalog projects from those beans.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0264408
2026-09-20 11:07:57 +08:00
co-authored by Cursor
parent e4e2c6ed9b
commit d743939964
16 changed files with 570 additions and 26 deletions
+1
View File
@@ -19,6 +19,7 @@
- 到期升级 `processDue` / `TASK_ESCALATED`
- 管理员/系统取消 `cancel` / `CANCELLED`
- 可选 REST(autoconfigure 条件装配)+ 目录 SPI `OrdoCatalog`
- `NamedAction` / `NamedCondition` 注册与官方 key 分发;默认 Catalog 从具名 Bean 投影
## 开发计划(确定要做)
+21 -14
View File
@@ -76,12 +76,16 @@ ordo:
| Bean | 默认 |
|---|---|
| `AssigneeResolver` | 候选人字符串即办理人 |
| `RoutingCondition` | `ref` 边默认恒 true;无 `when` 的边不经过该 Bean |
| `ActionHandler` | 空操作 |
| `NamedCondition` | 可有多个;无 `RoutingCondition` 门面且列表非空时装配 `DispatchingRoutingCondition` |
| `NamedAction` | 可有多个;无 `ActionHandler` 门面且列表非空时装配 `DispatchingActionHandler` |
| `RoutingCondition` | 无 `NamedCondition` 时 `ref` 边恒 true;有则按 key 分发,未知 `ref` 为 false |
| `ActionHandler` | 无 `NamedAction` 时空操作;有则按 key 分发,未知 key 抛 `IllegalArgumentException` |
| `OrdoEventListener` | 可有多个,提交后按 Spring 顺序调用 |
| `OrdoCatalog` | 空列表(仅 REST 打开时装配) |
| `OrdoCatalog` | REST 打开时:从 `NamedCondition` / `NamedAction` 投影;皆空则为空列表。`assignees` 仍空 |
未提供自定义 Bean 时,ACTION 步骤仍会推进流程,但 handler 什么都不做。
未提供 `NamedAction` 且未覆盖 `ActionHandler` 时,ACTION 步骤仍会推进流程,但 handler 什么都不做。不要同时提供门面 Bean 与对应 `Named*`(门面优先,具名 Bean 不参与运行时)。
内存引擎把 `DispatchingRoutingCondition.of(...)` / `DispatchingActionHandler.of(...)` 传入 `InMemoryOrdoEngine` 即可。
### 2.3 可选 REST
@@ -294,9 +298,9 @@ v1:至少 2 条分支;禁止套娃 PARALLEL;join 固定 ALL;任一分支
无条件边是默认分支;`priority` 只在同类边之间比较(多条条件边之间,或多条无条件边之间)。
`RoutingCondition.matches` 看到 `args` 与 `ProcessRuntime`(含业务 `context`、发起人、定义与当前步)。看不到任务意见。上下文在 `start` 时写入,运行中引擎不会改 context。未知 `ref` 由宿主返回 `false`,该边不匹配。内置谓词仍只读 `ProcessContext` 变量。
`RoutingCondition.matches` 看到 `args` 与 `ProcessRuntime`(含业务 `context`、发起人、定义与当前步)。看不到任务意见。上下文在 `start` 时写入,运行中引擎不会改 context。内置谓词仍只读 `ProcessContext` 变量。
引擎只注入**一个** `RoutingCondition`。Spring 下多个该类型 Bean 会冲突。宿主用一个门面按 `ref` 分发;不要指望引擎按定义拆 bean。
引擎仍只注入**一个** `RoutingCondition`。Spring 下注册多个 `NamedCondition` Bean(`key()` 对应 `when.ref`);无门面且列表非空时装配官方分发器。未知 `ref` 为 false,该边不匹配。重复 `key` 启动失败。也可继续提供单个 `RoutingCondition` 门面自行分发(不要与 `NamedCondition` 混用)。无 `NamedCondition` 时默认仍是恒 true。
## 7. ACTION 步骤
@@ -307,22 +311,25 @@ v1:至少 2 条分支;禁止套娃 PARALLEL;join 固定 ALL;任一分支
3. 事务提交后调用 `ActionHandler.execute(actionKey, runtime)`。
4. 成功 → `SUCCESS` + 事件 `ACTION_SUCCEEDED`;失败 → `FAILED`(`errorMessage`)+ `ACTION_FAILED` + 日志 WARNING。
**失败不回滚已提交的审批,不阻塞后续步骤,引擎不做重试。** 宿主用 `queryActionExecutions` 或 listener 自行补发。
**失败不回滚已提交的审批,不阻塞后续步骤,引擎不做重试。** 宿主用 `queryActionExecutions` 或 listener 自行补发。分发器遇到未知 `actionKey` 会抛 `IllegalArgumentException`,记为该次 ACTION `FAILED`。
```java
@Component
public class MailActions implements ActionHandler {
public class LeaveApprovedMail implements NamedAction {
@Override
public void execute(String actionKey, ProcessRuntime runtime) {
switch (actionKey) {
case "leave-submitted-mail" -> { /* ... */ }
case "leave-approved-mail" -> { /* ... */ }
default -> throw new IllegalStateException("unknown action: " + actionKey);
}
public String key() {
return "leave-approved-mail";
}
@Override
public void execute(ProcessRuntime runtime) {
/* ... */
}
}
```
仍可提供单个 `ActionHandler` 按 key 自行分发;不要与 `NamedAction` 混用。
## 8. 审计与监听
`queryHistory(instanceId, page)`:该实例事件,**发生时间升序**。