一佳技术

easy-flows 使用文档

easy-flows 是 warm-flow 工作流引擎的 Spring Boot 二次封装,提供「能力接口 + 自动配置 + SPI 桥接」三层。业务层通过 WorkflowEngine 门面调用,桥接层把 warm-flow 原生扩展点(PermissionHandler / GlobalListener / TenantHandler)接到 Matrix 框架接口上。不定义业务 Service / Cmd / Qry / VO——这些属于使用方。


1. 引入依赖

<!-- 核心依赖(必需) -->
<dependency>
    <groupId>io.matrix</groupId>
    <artifactId>easy-flows</artifactId>
    <version>${revision}</version>
</dependency>

<!-- warm-flow 引擎(传递依赖引入) -->
<!-- 自动带进来 warm-flow-mybatis-plus-sb-starter -->

<!-- 可选桥接 -->
<dependency>
    <groupId>io.matrix</groupId>
    <artifactId>easy-store</artifactId>
    <optional>true</optional>
</dependency>

<!-- 可选:流程设计器(前端设计页 + 流程可视化) -->
<dependency>
    <groupId>org.dromara.warm</groupId>
    <artifactId>warm-flow-plugin-ui-sb-web</artifactId>
    <version>${warm-flow.version}</version>
    <optional>true</optional>
</dependency>

设计器为可选能力:warm-flow-plugin-ui-sb-web 在 easy-flows 中标记为 optional,不会强制传给消费方。需要设计器时应用按上面显式引入(版本走 kernel-dependencies BOM,与 warm-flow 引擎一致),即可激活。

前置依赖:kernel-api(Result/PageResult)、kernel-client(User/Pagination)、kernel-common(JsonMapper)、kernel-dal-mp(RepositorySupport)、easy-plugin(SPI 扩展点)。

2. 准备数据库

easy-flows 自身不建表,warm-flow 自带 7 张表。首次集成执行 warm-flow MySQL 全量脚本:

warm-flow 表用途
flow_definition流程定义
flow_node流程节点
flow_skip节点跳转(含条件)
flow_instance流程实例
flow_task待办任务
flow_his_task历史任务(含 approver / message / skipType)
flow_user任务办理人权限
flow_form表单

warm-flow 数据源复用已有的 DataSource,不引入独立数据源配置。

3. 配置

easy-flows 通过 FlowsProperties(前缀 easy.flows)配置,但大部分走默认值即可。关键配置是 CurrentUserProvider 扩展点——不提供时 PermissionBridge 不注册。

# application.yml
easy:
  flows:
    # 使用 CurrentUserProvider 扩展点的 Bean 名称
    # 使用方 infrastructure 层实现并提供
    current-user-provider: null  # 不配置则 PermissionBridge 不注册

# warm-flow 自身配置(标准 warm-flow 前缀)
warm:
  flow:
    # warm-flow 的 keyType 自带 ID 策略;如需统一走 easy-idgen 可后续扩展
    key-type: auto

# warm-flow 数据源(复用 kernel-dal-mp 已有的 DataSource)
spring:
  datasource:
    # ... 已有配置,无需额外添加

可选桥接:流程定义走 easy-store

如果不想把流程定义存在数据库(改 JSON 免重新部署),可以让 easy-flows 从 easy-store 加载:

easy:
  flows:
    # 指定 easy-store 的 ConfigStorage Bean 名称
    # 未配置时不注册 StoreDefinitionLoader
    definition-storage: flowDefinitionStorage

使用方需提供名为 flowDefinitionStorage 的 ConfigStorage<ConfigDefinition> Bean,其中 namespace 对应流程定义存储。

4. 核心概念

easy-flows 是 warm-flow 的薄封装,理解 warm-flow 原生概念即可:

概念warm-flow 原生easy-flows 映射
流程定义flow_definition 表 + JSON DSLWorkflowEngine.deploy(processJson) → 返回 definitionId
流程实例flow_instance 表WorkflowEngine.start(flowCode, businessId, variables)
任务flow_task(待办)/ flow_his_task(历史)WorkflowEngine.complete(taskId, variables, comment)
办理人flow_user 表CurrentUserProvider 扩展点提供

easy-flows 门面命名 WorkflowEngine,避开 warm-flow 原生 FlowEngine。

5. WorkflowEngine 编程式 API

// 注入能力接口
@Autowired
private WorkflowEngine workflowEngine;

// === 流程定义 ===
Result<Long> deploy = workflowEngine.deploy(processJson);          // 部署 JSON 流程定义
Result<Boolean> publish = workflowEngine.publish(definitionId);    // 发布
Result<Boolean> suspend = workflowEngine.suspend(definitionId);   // 停用

// === 流程实例 ===
Result<Long> instance = workflowEngine.start(
    "requirement-approval",    // flowCode(流程定义标识)
    "REQ-20250901-001",        // businessId(业务关联 ID)
    Map.of("level", "P1")      // 流程变量(条件判断用)
);

Result<Boolean> canceled = workflowEngine.cancel(instanceId, "用户撤销");
Result<Boolean> terminated = workflowEngine.terminate(taskId, "流程终止");

// === 任务操作 ===
Result<Boolean> done = workflowEngine.complete(
    taskId,                   // 待办任务 ID
    Map.of("approved", true), // 提交的表单变量
    "同意"                    // 意见
);

Result<Boolean> rejected = workflowEngine.reject(
    taskId,
    "previous-node",          // 驳回到哪个节点 code
    Map.of("reason", "需要补充材料"),
    "退回补充"
);

Result<Boolean> transferred = workflowEngine.transfer(
    taskId,
    "user-zhangsan",          // 转办给谁(handler 标识)
    "请协助处理"
);

// === 查询 ===
PageResult<FlowTask> todos = workflowEngine.todo("user-zhangsan", 1, 20);
PageResult<FlowTask> dones = workflowEngine.done("user-zhangsan", 1, 20);

返回值:统一 Result<T> / PageResult<T>(kernel-dto 提供);异常统一 FlowRuntimeException。

6. 使用方接入示例

业务方在 DomainService 注入 WorkflowEngine,把审批流转委托给引擎:

@DomainService(domain = "Basic")
public class ApplyOrderDomainService extends BaseDomainService<ApplyOrder, ApplyOrderRepository> {

    private final WorkflowEngine workflowEngine;  // 注入能力接口

    @CommandHandler(name = "approveApplyOrder", desc = "审批通过申请单")
    public ApplyOrder approveApplyOrder(ApproveApplyOrderCmd cmd) {
        ApplyOrder order = repository.findBy(cmd.getCode());

        // 本地单实体状态(仍可用 easy-fsm)
        order.approve();
        repository.update(order, cmd);

        // 多级审批链交给工作流引擎
        workflowEngine.complete(
            cmd.getTaskId(),
            Map.of("approved", true),
            cmd.getComment()
        );

        publish(ApplyOrder.class, order.getCode(), new ApplyOrderApprovedEvt());
        return order;
    }

    @CommandHandler(name = "createApplyOrder", desc = "创建申请单并启动审批")
    public ApplyOrder create(CreateApplyOrderCmd cmd) {
        ApplyOrder order = ApplyOrder.from(cmd);
        repository.save(order, cmd);

        // 启动流程实例,关联业务 ID
        workflowEngine.start(
            "apply-approval",
            order.getCode(),
            Map.of("level", order.getLevel())
        );

        publish(ApplyOrder.class, order.getCode(), new ApplyOrderCreatedEvt());
        return order;
    }
}

职责边界:easy-flows 只提供 WorkflowEngine 能力接口,不定义 CreateApplyOrderCmd / ApplyOrderApprovedEvt / ApplyOrder——这些全是使用方 pulse-client / pulse-domain 的类。

7. 与其他组件的衔接

flowchart TD
    EF["easy-flows<br/>WorkflowEngine 门面"]
    WF["warm-flow<br/>FlowEngine / DefService / TaskService"]
    KMP["kernel-dal-mp<br/>数据源复用"]

    EF --> WF
    WF --> KMP

    subgraph "可选桥接"
        ES["easy-store<br/>StoreDefinitionLoader<br/>流程定义热加载"]
        EP["easy-plugin<br/>SPI 扩展点<br/>CurrentUserProvider"]
        ER["easy-rules<br/>⚠️ 不可行<br/>warm-flow 无条件 SPI"]
    end

    EF -.->|"可选"| ES
    EF -.->|"必需"| EP

    subgraph "运行时桥接"
        PB["PermissionBridge<br/>warm-flow PermissionHandler<br/>← CurrentUserProvider"]
        FL["FlowEventListener<br/>warm-flow GlobalListener<br/>→ FlowTaskFinishedEvent"]
    end

    WF --> PB
    WF --> FL
    PB --> EP
衔接点说明
easy-fsm单实体状态(如 ApplyOrder.STATUS_TRANSITIONS)用 easy-fsm;跨节点审批链用 easy-flows
easy-pluginCurrentUserProvider 扩展点(extends ExtensionPoint),使用方 infrastructure 层实现
easy-store可选:StoreDefinitionLoader 从 easy-store ConfigStorage 读取流程 JSON 并部署
easy-rules⚠️ 不可行:warm-flow 无条件判断 SPI(ConditionType 只是比较运算符枚举)
easy-event-busFlowEventListener 桥接任务完成 → 发布 FlowTaskFinishedEvent 领域事件
easy-idgenwarm-flow WarmFlow.keyType 自带 ID 策略;如需统一走 IdGenerator 可后续扩展

8. SPI 扩展点

warm-flow 原生提供四个扩展点,easy-flows 直接桥接不造并行 SPI:

warm-flow 原生扩展点职责easy-flows 桥接注册方式
PermissionHandler办理人权限 + 办理人唯一标识✅ PermissionBridge:CurrentUserProvider → User 解析Spring Bean(@ConditionalOnBean(CurrentUserProvider.class))
GlobalListener流程/节点/任务监听✅ FlowEventListener:任务完成 → 发布 FlowTaskFinishedEventSpring Bean
DataFillHandler创建人/时间等字段填充⛔ 无需桥接:默认实现已填充 createBy—
TenantHandler多租户隔离⛔ 不独立桥接:随 PermissionHandler 一并处理—

CurrentUserProvider(必需扩展点)

// 使用方 infrastructure 层实现
@Extension(nodes = {"GATEWAY", "USER", "PROVIDER"})
@Component
public class GatewayCurrentUserProvider implements CurrentUserProvider {
    @Override
    public User getCurrentUser() {
        // 从网关上下文 / ThreadLocal / SecurityContextHolder 获取用户
        return UserContextHolder.get();
    }
}

PermissionBridge 自动从 kernel-client.User 取值:

warm-flow 需要User 字段用途
getHandler()userCode记录流程创建人 / 办理人
permissions()userCode + tenantCode节点办理人权限校验

PermissionBridge 注册条件

@Configuration
@ConditionalOnBean(CurrentUserProvider.class)         // 使用方提供扩展实现才启用
@ConditionalOnMissingBean(PermissionHandler.class)    // 使用方可自定义覆盖
static class PermissionHandlerConfiguration {
    @Bean
    public PermissionHandler permissionHandler(CurrentUserProvider userProvider) {
        return new PermissionBridge(userProvider);
    }
}

9. 自动配置与顺序

FlowsAutoConfiguration 标注:

@AutoConfigureAfter({SpringPf4jConfiguration.class, FlowAutoConfig.class})
// 确保 PF4J 插件加载完成 + warm-flow 自身装配
// 再注册 WorkflowEngine / PermissionBridge / FlowEventListener
Bean条件说明
WorkflowEngine (DefaultWorkflowEngine)@ConditionalOnClass(FlowEngine.class)默认门面实现,委托 warm-flow 原生 Service
PermissionBridge (PermissionHandler)@ConditionalOnBean(CurrentUserProvider.class) + @ConditionalOnMissingBean(PermissionHandler.class)可选:使用方提供扩展实现才注册
FlowEventListener (GlobalListener)始终注册桥接任务完成事件
StoreDefinitionLoader@ConditionalOnProperty("easy.flows.definition-storage") + @ConditionalOnBean(ConfigStorage.class)可选:easy-store 桥接

10. 注意事项

项说明
easy-flows 不定义业务类❌ 不放 cmd/qry/vo/service——这些属于使用方 pulse-client/pulse-domain
warm-flow 数据源复用不引入独立 DataSource,复用 kernel-dal-mp 已有的
不反向依赖 kernel-adapter联动只能在应用层(DomainService 注入 WorkflowEngine)
待办查询两步走flow_task 无办理人字段 → 先查 flow_user 拿 taskId → 再查 flow_task(内存分页);已办走 flow_his_task.approver 直接分页
事务边界使用方控制审批操作 + 业务状态流转在同一事务,由使用方 DomainService 的 @Transactional 控制
流程变量 Map 传参条件判断所需上下文通过 start(flowCode, businessId, variables) 的 variables Map 传入
CurrentUserProvider 缺失使用方未提供扩展实现时 PermissionBridge 不注册,自动降级为无办理人处理器
ID 全部 Long与 warm-flow 实体(Definition/Instance/Task)一致,避免转换

11. 流程设计器集成

warm-flow 提供免搬家的可视化设计器,前后端不分离:前端资源打包进 warm-flow-plugin-ui-sb-web jar,后端以静态资源 + REST 接口方式暴露。easy-flows 不重复包装(设计器自包含),只负责放行引导 + 版本协调 + 扩展点说明。

11.1 装配与激活

引入 warm-flow-plugin-ui-sb-web(见 §1)后即完成装配,无需额外配置:

  • 静态代理:WarmFlowUiConfig(WebMvcConfigurer)把 /warm-flow-ui/** 映射到 jar 内 classpath:/META-INF/resources/warm-flow-ui/
  • REST 接口:WarmFlowController(/warm-flow/**):saveJson / queryDef / queryFlowChart / handlerType / handlerResult / publishedForm / getFormContent / saveFormContent 等
  • 数据源复用 warm-flow 引擎的 DefService / FormService 等,easy-flows 已注册

FlowAutoConfig / FlowsAutoConfiguration 与设计器的装配顺序由 @AutoConfigureAfter 保证,无需干预。

11.2 放行差异(Web 安全 / 网关)

部署方式放行位置放行路径
单体业务后端安全框架/warm-flow-ui/**、/warm-flow/**
微服务网关白名单/warm-flow-ui/**、/warm-flow/**,且网关需配置指向流程服务的路由

例如 spring-security:

.authorizeHttpRequests(requests -> requests
    .antMatchers("/warm-flow-ui/**", "/warm-flow/**").permitAll()
    .anyRequest().authenticated())

11.3 前端页面引入(iframe)

<iframe src="${base}/warm-flow-ui/index.html"
        ?id=${definitionId}             <!-- 流程定义 ID;不传则新增流程 -->
        &onlyDesignShow=${onlyDesignShow} <!-- true:仅设计器,不显示基础信息 -->
        &disabled=${disabled}           <!-- true:只读不可编辑 -->
        &theme=theme-dark              <!-- 主题:theme-light / theme-dark -->
        width="100%" height="100%"/>

页面通过 window.message 事件广播,监听 method === "close" 处理保存后关闭/刷新。

11.4 共享后端权限(token)

security 只保留 /warm-flow-ui/** 白名单放行(去掉 /warm-flow/**),业务 token 通过 URL 参数透传:

/warm-flow-ui/index.html?...&Authorization=${token}

并在 yml 声明 token 头名,设计器会把 token set 到请求头:

warm-flow:
  token-name: Authorization   # 多个 token 用逗号分隔

11.5 三个业务扩展点(设计器数据来源)

以下接口由使用方在自身应用实现并注册为 Spring Bean,easy-flows 不提供默认实现(无业务通用的用户/角色/部门模型)。它们定义在 warm-flow-plugin-ui-core:

扩展点职责由工作流设计器哪个能力使用
HandlerSelectService任务节点可办理人选择:getHandlerType() 返回 tabs(用户/角色/部门…),getHandlerSelect(HandlerQuery) 返回办理人列表节点「办理人」设置弹框
CategoryService流程基础信息类别下拉数据设计器顶部基础信息归类
FormPathService动态/自定义表单路径节点表单绑定

业务数据通过 HandlerFunDto(普通列表)+ TreeFunDto(树,如部门树)包装返回。

不提供实现时,对应弹框为空列表,不影响流程设计/流转核心功能。

12. 常见问题

Q: easy-flows 和 easy-fsm 怎么配合? A: 分工明确——单实体状态(ApplyOrder 的审批状态机)用 easy-fsm 的 FsmBuilder 声明式定义,DomainService 里 checkTransition/fireTransition;跨节点审批链(多级审批流)用 easy-flows 的 WorkflowEngine。两者可以共存于同一个 DomainService。

Q: 必须提供 CurrentUserProvider 吗? A: 不是必需。不提供时 PermissionBridge 不注册,warm-flow 用默认 PermissionHandler(不做权限校验)。但生产环境一定要提供,否则节点办理人权限会失效。

Q: 流程定义 JSON 格式是什么? A: warm-flow 自研 DSL(非 BPMN XML)。格式示例见 warm-flow 文档:节点定义 + 条件表达式 + 跳转规则。前端设计器直接生成 JSON。

Q: 条件表达式怎么写? A: warm-flow 内置表达式引擎,支持 eq/gt/lt/like 等比较运算符,基于流程变量 Map 求值。⚠️ 不做 easy-rules 桥接(warm-flow 无条件 SPI)。

Q: 启动流程实例的 businessId 是什么? A: 使用方的业务主键(如申请单号 REQ-20250901-001),warm-flow 内部关联到 flow_instance.businessId。

Q: 怎么给流程加自定义监听器? A: 实现 warm-flow 的 GlobalListener 接口并注册为 Spring Bean。easy-flows 已注册一个 FlowEventListener 桥接到 FlowTaskFinishedEvent,你可以再加自己的。

Q: warm-flow 升级 API 变了怎么办? A: 只改 DefaultWorkflowEngine 一个类(门面隔离),其他代码不动。

Q: 怎么打开流程设计器? A: 先引入 warm-flow-plugin-ui-sb-web(见 §1),放行 /warm-flow-ui/**、/warm-flow/**,然后前端 iframe 打开 /warm-flow-ui/index.html?id=xxx(详见 §11)。打开空白多为安全拦截未放行,或 spring-web 上下文缺失。

Q: 设计器的办理人下拉是空的? A: 需在自身应用实现 HandlerSelectService(返回用户/角色/部门的 tabs 及列表),见 §11.5。不实现则该弹框为空,但不影响流程核心功能。