easy-flows 使用文档
easy-flows 是 warm-flow 工作流引擎的 Spring Boot 二次封装,提供「能力接口 + 自动配置 + SPI 桥接」三层。业务层通过
WorkflowEngine门面调用,桥接层把 warm-flow 原生扩展点(PermissionHandler / GlobalListener / TenantHandler)接到 Matrix 框架接口上。不定义业务 Service / Cmd / Qry / VO——这些属于使用方。
- 1. 引入依赖
- 2. 准备数据库
- 3. 配置
- 4. 核心概念
- 5. WorkflowEngine 编程式 API
- 6. 使用方接入示例
- 7. 与其他组件的衔接
- 8. SPI 扩展点
- 9. 自动配置与顺序
- 10. 注意事项
- 11. 流程设计器集成
- 12. 常见问题
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-dependenciesBOM,与 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 DSL | WorkflowEngine.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-plugin | CurrentUserProvider 扩展点(extends ExtensionPoint),使用方 infrastructure 层实现 |
| easy-store | 可选:StoreDefinitionLoader 从 easy-store ConfigStorage 读取流程 JSON 并部署 |
| easy-rules | ⚠️ 不可行:warm-flow 无条件判断 SPI(ConditionType 只是比较运算符枚举) |
| easy-event-bus | FlowEventListener 桥接任务完成 → 发布 FlowTaskFinishedEvent 领域事件 |
| easy-idgen | warm-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:任务完成 → 发布 FlowTaskFinishedEvent | Spring 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。不实现则该弹框为空,但不影响流程核心功能。