一佳技术

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-FlowActiviti/Flowable结论
表数量7 张25~79 张✅ 极简,维护成本低
流程定义JSON(自研 DSL)BPMN 2.0 XML✅ 无 XML,前端设计器直接生成 JSON
ORM 支持MyBatis-Plus / MyBatis / JPA内部 MyBatis only✅ 跟 kernel-dal-mp 无缝对接
JDK / Spring Boot8/17/21 · 2.7.x+3.x+4.x8/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 扩展点 (extends ExtensionPoint),由使用方 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-idgenwarm-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&lt;ConfigDefinition&gt;<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名> 显式指定承载流程定义的 ConfigStorage Bean;未配置时不注册。应用内可能存在多个 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.xml module 注册 + 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-flow PermissionHandler(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 test 22 例全通过(用 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