easy-flows 模块设计文档
状态: 设计中 目标引擎: Dromara Warm-Flow 1.8.9(Spring Boot 2.7.x, JDK 17) 模块定位: 框架组件(对 warm-flow 的薄封装),提供「能力接口 + 自动配置 + 扩展点」,不含业务 Service/Cmd/Qry/VO
1. 为什么选 Warm-Flow
| 维度 | Warm-Flow | Activiti/Flowable | 结论 |
|---|---|---|---|
| 表数量 | 7 张 | 25~79 张 | ✅ 极简,维护成本低 |
| 流程定义 | JSON(自研 DSL) | BPMN 2.0 XML | ✅ 无 XML,前端设计器直接生成 JSON |
| ORM 支持 | MyBatis-Plus / MyBatis / JPA | 内部 MyBatis only | ✅ 跟 kernel-dal-mp 无缝对接 |
| JDK / Spring Boot | 8/17/21 · 2.7.x+3.x+4.x | 8/11/17 · 2.x+3.x | ✅ 17 + 2.7.17 匹配 |
| 设计器 | JAR 内嵌 | 需独立部署 | ✅ 可内嵌,符合「可内嵌且轻量」 |
| 审批功能 | 通过/退回/撤销/拿回/跳转/终止/转办/票签/委派/加减签 | 基础 BPMN | ✅ 覆盖 pulse 现有手写审批流 |
2. 架构定位
2.1 框架分层铁律(从 pulse 实际项目反推)
matrix-framework 是 DDD/CQS 框架,pulse(pl-backend)是标准使用方,分层如下:
flowchart TD
subgraph "matrix-framework"
KC["kernel-client<br/>BaseCommand / BaseQuery / BaseDomainEvent<br/>@DomainService / @CommandHandler"]
KD["kernel-dal-mp<br/>RepositorySupport / GenericMapper"]
EC["easy-* 组件<br/>能力接口(RulesExecutor / IdGenerator / MessageSender)"]
end
subgraph "pulse-client"
CMD["Cmd / Qry / Evt<br/>继承 kernel-client 基类"]
end
subgraph "pulse-domain"
DS["DomainService<br/>继承 BaseDomainService"]
end
subgraph "pulse-application"
AS["AppService<br/>继承 BaseAppService"]
end
subgraph "pulse-infrastructure"
MGR["Manager<br/>继承 RepositorySupport"]
end
CMD --> KC
DS --> KC
AS --> KC
MGR --> KD
DS --> MGR
style EC fill:#dff,stroke:#333
style DS fill:#fdd,stroke:#333
style MGR fill:#fdd,stroke:#333
铁律:DomainService / AppService / Manager 都是使用方的类,框架只提供基类 + 注解 + 能力接口。easy-flows 必须遵守——不定义任何业务 Service。
2.2 easy-flows 与现有能力的关系
flowchart TD
EF["easy-flows<br/>FlowEngine 能力接口 + 自动配置 + SPI"]
WF["warm-flow<br/>DefService / InsService / TaskService"]
KMP["kernel-dal-mp<br/>RepositorySupport"]
EF --> WF
WF --> KMP
subgraph "可桥接"
ES["easy-store<br/>流程定义热加载"]
ER["easy-rules<br/>条件表达式"]
end
EF -.->|"可选"| ES
EF -.->|"可选"| ER
subgraph "使用方 pulse-domain"
DS2["ApplyOrderDomainService<br/>注入 FlowEngine 替代手写状态机"]
end
DS2 --> EF
style EF fill:#dff,stroke:#333
style WF fill:#fdd,stroke:#333
关键:pulse 的 ApplyOrderDomainService 现在手写 STATUS_TRANSITIONS 状态机,并已预留 externalFlowId 字段。easy-flows 让这类审批流从手写状态机切换为 warm-flow 引擎,接入点在 DomainService 内部。
3. 模块结构
components/easy-flows/
├── pom.xml # warm-flow-mybatis-plus-sb-starter + kernel-dal-mp + easy-plugin
├── src/main/java/io/matrix/flows/
│ ├── autoconfigure/ # 自动配置(遵循组件 autoconfigure 命名规范)
│ │ ├── FlowsAutoConfiguration.java # 门面 + 三个桥接的条件注册
│ │ └── FlowsProperties.java # easy.flows.* 配置属性
│ ├── engine/ # 能力接口(操作器,非业务 Service)
│ │ ├── WorkflowEngine.java # 工作流引擎门面,返回 Result<T>
│ │ ├── DefaultWorkflowEngine.java # 委托 warm-flow 原生 Service 的默认实现
│ │ └── FlowTask.java # 引擎层任务视图(待办/已办查询结果)
│ ├── bridge/ # 桥接层(warm-flow 原生扩展点 → matrix)
│ │ ├── CurrentUserProvider.java # 当前用户来源扩展点(ExtensionPoint)
│ │ ├── PermissionBridge.java # CurrentUserProvider → PermissionHandler
│ │ ├── FlowEventListener.java # GlobalListener → 领域事件
│ │ └── StoreDefinitionLoader.java # easy-store → 流程定义加载
│ ├── event/
│ │ └── FlowTaskFinishedEvent.java # 任务完成事件(BaseDomainEvent)
│ └── exception/ # 异常适配
│ ├── FlowRuntimeException.java
│ └── FlowExceptionTranslator.java
└── src/test/java/io/matrix/flows/ # 功能验证与用法示例
├── engine/DefaultWorkflowEngineTest.java
└── bridge/{StoreDefinitionLoaderTest, PermissionBridgeTest, FlowEventListenerTest}.java
不造并行 SPI:warm-flow 原生已提供
PermissionHandler/DataFillHandler/TenantHandler/GlobalListener四个扩展点,easy-flows 直接桥接,避免两套扩展机制。原先规划的HandlerExpressionResolver/ConditionExpressionResolver/FlowListenerAdapter/FlowsExtPts已移除。
明确不放:
- ❌
cmd/、qry/、vo/—— 业务命令/查询/视图对象,属于使用方 pulse-client - ❌
service/—— 业务 Service(FlowDefinitionService/FlowInstanceService/FlowTaskService),属于使用方 pulse-domain/application
pom.xml 依赖
<dependencies>
<!-- Warm-Flow(含引擎 + MyBatis-Plus ORM 适配) -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb-starter</artifactId>
<version>1.8.9</version>
</dependency>
<!-- matrix-framework 内部依赖 -->
<dependency>io.matrix:kernel-api</dependency> <!-- Result / PageResult -->
<dependency>io.matrix:kernel-client</dependency> <!-- User / Dept / Pagination 等数据结构 -->
<dependency>io.matrix:kernel-common</dependency> <!-- JsonMapper -->
<dependency>io.matrix:kernel-dal-mp</dependency> <!-- RepositorySupport / GenericMapper -->
<!-- SPI 扩展点(对接 easy-plugin / PF4J) -->
<dependency>io.matrix:easy-plugin</dependency>
<!-- 可选桥接 -->
<dependency>io.matrix:easy-store</dependency> <optional>true</optional>
<dependency>io.matrix:easy-rules</dependency> <optional>true</optional>
<!-- 可选:流程设计器(已核实 warm-flow-plugin-ui-sb-web 版本与引擎对齐 1.8.9) -->
<dependency>org.dromara.warm:warm-flow-plugin-ui-sb-web</dependency> <optional>true</optional>
<dependency>org.springframework.boot:spring-boot-autoconfigure</dependency>
<dependency>org.projectlombok:lombok</dependency> <optional>true</optional>
</dependencies>
设计器(前端)装配:
warm-flow-plugin-ui-sb-web为自包含 Spring Boot starter——WarmFlowUiConfig(WebMvcConfigurer)把/warm-flow-ui/**代理到 jar 内前端资源,WarmFlowController提供/warm-flow/**REST(saveJson/queryDef/handlerType/handlerResult 等),数据复用 warm-flow 的DefService/FormService。easy-flows 不重复封装,仅做 optional 装配 + 放行/token/扩展点引导(见 USAGE §11)。三个面向业务的扩展点(HandlerSelectService/CategoryService/FormPathService)由使用方在自身应用实现。
warm-flow 真实类名(已核实 1.8.9 jar)
| 用途 | 真实全限定名 |
|---|---|
| 引擎门面 | org.dromara.warm.flow.core.FlowEngine |
| 流程定义 | org.dromara.warm.flow.core.service.DefService |
| 流程实例 | org.dromara.warm.flow.core.service.InsService |
| 任务操作 | org.dromara.warm.flow.core.service.TaskService |
| 其它 | NodeService / HisTaskService / ChartService / FormService / SkipService / UserService |
⚠️ warm-flow 自身门面类即为
FlowEngine,故本模块门面改名WorkflowEngine以避免混用。
4. 能力接口设计(WorkflowEngine,替代原「Service + Cmd/Qry」)
4.1 WorkflowEngine 门面
/** 工作流引擎能力门面 —— 对标 easy-rules 的 RulesExecutor / easy-idgen 的 IdGenerator */
public interface WorkflowEngine {
/** 流程定义:部署 / 发布 / 停用(版本等元数据包含在 JSON 内) */
Result<Long> deploy(String processJson);
Result<Boolean> publish(Long definitionId);
Result<Boolean> suspend(Long definitionId);
/** 流程实例:启动 / 撤销 / 终止 */
Result<Long> start(String flowCode, String businessId, Map<String, Object> variables);
Result<Boolean> cancel(Long instanceId, String comment);
Result<Boolean> terminate(Long taskId, String reason);
/** 任务:完成 / 驳回 / 转办 */
Result<Boolean> complete(Long taskId, Map<String, Object> variables, String comment);
Result<Boolean> reject(Long taskId, String targetNodeCode, Map<String, Object> variables, String comment);
Result<Boolean> transfer(Long taskId, String targetHandler, String comment);
/** 查询:我的待办 / 已办 */
PageResult<FlowTask> todo(String handler, int pageNum, int pageSize);
PageResult<FlowTask> done(String handler, int pageNum, int pageSize);
}
- 返回
Result<T>/PageResult<T>(io.matrix.kernel.dto),异常统一FlowRuntimeException - ID 全部用
Long,与 warm-flow 实体(Definition/Instance/Task)一致,避免转换 - 内部委托 warm-flow 的
DefService/InsService/TaskService/HisTaskService/UserService - 命名用
WorkflowEngine(避开 warm-flow 自带的FlowEngine),不是Service
4.1.1 待办 / 已办的实现方式
flow_task 无办理人字段(permissionList / userList 均为 @TableField(exist = false)),
办理人存在 flow_user(processedBy + associated):
| 查询 | 实现 |
|---|---|
todo(handler) | UserService.listByProcessedBys(null, handler, UserType.APPROVAL) → 取 associated(taskId) → TaskService.getByIds → 按 createTime 倒序内存分页 |
done(handler) | HisTaskService.page(probe.setApprover(handler), Page) —— flow_his_task.approver 是真实列,可直接等值分页 |
4.2 使用方接入方式(pulse 视角)
业务方在 DomainService 注入 WorkflowEngine,把审批流转委托给引擎:
@DomainService(domain = "Basic")
public class ApplyOrderDomainService extends BaseDomainService<ApplyOrder, ApplyOrderRepository> {
private final WorkflowEngine workflowEngine; // 注入能力接口,不是注入业务 Service
@CommandHandler(name = "approveApplyOrder", desc = "审批通过申请单")
public ApplyOrder approveApplyOrder(ApproveApplyOrderCmd cmd) {
ApplyOrder order = repository.findBy(cmd.getCode());
order.approve(); // 本地单实体状态(仍可用 easy-fsm)
repository.update(order, cmd);
// 多级审批链交给工作流引擎
workflowEngine.complete(cmd.getTaskId(), Map.of("approved", true), cmd.getComment());
publish(ApplyOrder.class, order.getCode(), new ApplyOrderApprovedEvt());
return order;
}
}
Cmd/Qry/VO 仍由使用方 pulse-client 定义,easy-flows 完全不感知。
5. 扩展点(桥接 warm-flow 原生,不自造 SPI)
5.1 为什么不自造 SPI
warm-flow 原生已提供四个扩展点,easy-flows 直接桥接,避免两套并行的扩展机制:
| warm-flow 原生扩展点 | 职责 | easy-flows 处理 |
|---|---|---|
PermissionHandler | 办理人权限标识 + 办理人唯一标识 | ✅ PermissionBridge:由 CurrentUserProvider 扩展点提供 User 解析 |
GlobalListener | 流程 / 节点 / 任务监听 | ✅ FlowEventListener:任务完成 → 发布 FlowTaskFinishedEvent |
DataFillHandler | 创建人 / 时间等字段填充 | ⛔ 无需桥接:默认实现已通过 PermissionHandler 填充 createBy,与 matrix 语义一致 |
TenantHandler | 多租户隔离 | ✅ kernel-client.User 提供 tenantCode,可由 PermissionBridge 一并注入 |
原先规划的 HandlerExpressionResolver / ConditionExpressionResolver / FlowListenerAdapter /
FlowsExtPts 与上述原生扩展点职责重复,已移除;
warm-flow 的条件判断亦无对外 SPI(ConditionType 仅为比较运算符枚举),故条件表达式桥接 easy-rules 亦不可行。
5.2 注册机制
warm-flow 的 WarmFlow.init() 调用 FlowEngine.initXxxHandler(path),其 initBean 逻辑为:
flowchart TD
A["handlerPath 非空?"] -->|"是"| B["反射实例化(无参构造)"]
A -->|"否"| C["FrameInvoker.getBean(类型)"]
C -->|"从 Spring 容器取 Bean"| D["拿到实例"]
B --> D
D -->|"仍为空"| E["使用默认 supplier"]
因此 easy-flows 只需注册 Spring Bean,warm-flow 自动发现:
@Configuration
@ConditionalOnBean(CurrentUserProvider.class) // 使用方提供扩展实现才启用
@ConditionalOnMissingBean(PermissionHandler.class) // 使用方可自定义覆盖
static class PermissionHandlerConfiguration {
@Bean
public PermissionHandler permissionHandler(CurrentUserProvider userProvider) {
return new PermissionBridge(userProvider);
}
}
依赖方向:easy-flows 作为 DDD 流程 gateway 在制定者 infrastructure 层接入,不反向依赖接入层 (kernel-adapter)。「当前用户从哪来」抽象为
CurrentUserProvider扩展点 (extendsExtensionPoint),由使用方 infrastructure 实现并提供,内部可访问自身的用户上下文。
5.3 办理人桥接的内容
PermissionBridge 从 CurrentUserProvider 提供的 kernel-client.User 取值:
| 返回项 | 来源 | 用途 |
|---|---|---|
getHandler() | User.userCode | 记录流程创建人 / 办理人 |
permissions() | userCode + tenantCode | 节点办理人权限校验 |
5.4 自动配置顺序
FlowsAutoConfiguration 标注 @AutoConfigureAfter({SpringPf4jConfiguration.class, FlowAutoConfig.class}),
确保 warm-flow 自身装配与 PF4J 插件加载完成后再注册门面 Bean。
5.5 与其他组件的衔接
| 衔接点 | 说明 |
|---|---|
| 审批事件 → matrix 事件 | Phase 3:用 warm-flow GlobalListener 桥接为 BaseDomainEvent,对接使用方 @EventHandler |
| ID 生成 → easy-idgen | warm-flow WarmFlow.keyType 自带 ID 策略;如需统一走 IdGenerator 可后续扩展 |
| 事务边界 | 审批操作 + 业务状态流转在同一事务,由使用方 DomainService 的 @Transactional 控制 |
| 与 easy-fsm 分工 | 单实体状态(如 ApplyOrder 的 STATUS_TRANSITIONS)用 easy-fsm;跨节点审批链用 easy-flows |
6. 桥接层
6.1 StoreDefinitionLoader(流程定义 → easy-store)
flowchart LR
DB["DB 模式<br/>warm-flow flow_definition 表"]
STORE["easy-store 模式<br/>ConfigStorage<ConfigDefinition><br/>text = 流程 JSON"]
DB -->|"默认"| D1["简单直接"]
STORE -->|"可选"| D2["改 JSON 免重新部署"]
style STORE fill:#dff
public class StoreDefinitionLoader {
String read(String key); // 只读取流程 JSON
Long deploy(String key); // 读取并部署,返回流程定义 ID
List<Long> deploy(Collection<String> keys);
}
- 通过配置
easy.flows.definition-storage=<bean名>显式指定承载流程定义的ConfigStorageBean;未配置时不注册。应用内可能存在多个ConfigStorage(规则、GraphQL、工作流各自一套), 类型擦除后无法唯一注入,故按 bean 名取用(@ConditionalOnProperty+@ConditionalOnClass) - 不使用
listKeys/loadAll:easy-store 默认实现这两者互相调用会递归,批量部署需显式传入 key 集合
6.2 条件表达式 → easy-rules(不可行)
warm-flow 未提供条件判断的扩展 SPI(ConditionType 只是 eq/gt/like 等比较运算符枚举),
条件求值在引擎内部完成。因此不做 easy-rules 条件桥接,沿用 warm-flow 内置表达式。
7. 数据库表(warm-flow 自带,easy-flows 不建表)
| 表 | 用途 |
|---|---|
flow_definition | 流程定义 |
flow_node | 流程节点 |
flow_skip | 节点跳转(含条件) |
flow_instance | 流程实例 |
flow_task | 待办任务 |
flow_his_task | 历史任务(含 approver / message / skipType) |
flow_user | 任务办理人权限(processedBy + type + associated) |
flow_form | 表单 |
首次集成执行 warm-flow 的 MySQL 全量脚本。
8. 里程碑
Phase 1:骨架 ✅ 已完成
- ✅
easy-flows/pom.xml+components/pom.xmlmodule 注册 + kernel-dependencies BOM(含warm-flow.version) - ✅
FlowsAutoConfiguration(@ConditionalOnClass(FlowEngine)+@AutoConfigureAfter(SpringPf4jConfiguration))+FlowsProperties+spring.factories - ✅
WorkflowEngine接口 +FlowTask+FlowRuntimeException - ✅
mvn compile通过(全 30 模块 BUILD SUCCESS)
Phase 2:能力实现 ✅ 已完成
- ✅
DefaultWorkflowEngine委托 warm-flow 的DefService/InsService/TaskService/HisTaskService/UserService - ✅
FlowExceptionTranslator(底层异常 →FlowRuntimeException) - ✅
PermissionBridge桥接 warm-flowPermissionHandler(CurrentUserProvider扩展点),自动注册为 Spring Bean - ✅ 待办两步查询(
flow_user→flow_task)、已办等值分页(flow_his_task.approver) - ✅
mvn compile通过(全 30 模块 BUILD SUCCESS)
Phase 3:桥接增强 ✅ 已完成
- ✅
FlowEventListener(GlobalListener→FlowTaskFinishedEvent领域事件,经DomainEventGateway) - ✅
StoreDefinitionLoader(easy-store → 流程定义读取/部署) - ⛔
DataFillHandler:默认实现已满足,无需桥接 - ✅
PermissionBridge(CurrentUserProvider扩展点 →PermissionHandler) - ⛔ 条件表达式 → easy-rules:warm-flow 无对应 SPI,不可行
Phase 3.5:功能验证 ✅ 已完成
- ✅
DefaultWorkflowEngineTest(12 例):门面 → warm-flow 原生调用的映射验证(deploy/start/complete/reject/transfer/cancel/terminate/todo/done) - ✅
StoreDefinitionLoaderTest(4 例)、PermissionBridgeTest(3 例)、FlowEventListenerTest(3 例) - ✅
mvn -pl components/easy-flows test22 例全通过(用 JDK 动态代理替换原生 Service,无需数据库)
Phase 4:端到端验证
- 用 pulse 的 ApplyOrder 审批流做接入验证,替换手写
STATUS_TRANSITIONS(需真实 MySQL)
9. 风险与对策
| 风险 | 对策 |
|---|---|
| warm-flow 升级 API 变 | WorkflowEngine 门面隔离,升级只改 DefaultWorkflowEngine 一处 |
| warm-flow 数据源配置与 kernel-dal-mp 冲突 | 复用已有 DataSource,不引入 warm-flow 内置数据源配置 |
| 多租户隔离 | Phase 3 桥接 TenantHandler 到 matrix 租户上下文 |
| 循环依赖 | easy-flows 仅依赖内核数据(kernel-client)与组件(easy-plugin/easy-store),不反向依赖 kernel-adapter。联动只能在应用层(DomainService 注入 WorkflowEngine) |
CurrentUserProvider 缺失 | 使用方未提供扩展实现时,@ConditionalOnBean 不触发,自动降级为无办理人处理器 |
| 待办内存分页 | 单用户待办量有限;若需服务端分页,Phase 3 可扩展自定义查询 |
附录:与旧 easy-flows 的关系
旧 easy-flows 已拆成 easy-fsm + easy-store + easy-rules。本模块是全新模块,无代码继承,但复用拆出的能力:
flowchart TD
OLD["旧 easy-flows"]
OLD -->|"已拆"| FSM["easy-fsm"]
OLD -->|"已拆"| STORE["easy-store"]
OLD -->|"已拆"| RULES["easy-rules"]
NEW["新 easy-flows<br/>warm-flow 封装"]
NEW -.->|"可选桥接"| STORE
NEW -.->|"可选桥接"| RULES
style NEW fill:#dff
style OLD fill:#ddd,text-decoration:line-through