
本文以 Forge Admin 开源框架为例,深入剖析如何基于 Flowable 7 构建一套"声明式接入、事件驱动同步、双设计器前端"的企业级工作流平台。从三模块解耦架构到
@FlowStart注解式发流,从 Redis Pub/Sub 事件总线到 AI 生成 BPMN,带你理解每一个设计决策背后的工程考量。
📌 仓库地址
- Gitee :gitee.com/ForgeLab/fo...
- GitHub :github.com/yaomindong1...
欢迎 Star ⭐ 支持!项目持续更新中。
一、为什么不是"开箱即用"的 Flowable?
Flowable 自带的 Spring Boot Starter 能让你 5 分钟跑通一个流程,但当真正落地企业级场景时,你会遇到一连串问题:
- 业务代码与流程引擎深度耦合 --- 每次发起流程都要写一堆
runtimeService.startProcessInstanceByKey(...),业务层充斥着流程 API - 状态同步全靠手写 --- 流程审批通过/驳回后,业务表状态怎么更新?写定时任务轮询?还是在每个审批接口里硬编码?
- 多租户隔离 --- Flowable 原生多租户机制和业务框架的多租户拦截器如何协同?
- 前端设计器 --- bpmn-js 原生面板对业务人员来说门槛太高,能不能做成钉钉审批那种拖拽式?
- 流程定义和业务表单绑定 --- 一个流程模型对应哪张业务表?表单字段在哪些节点可编辑?
Forge 框架的工作流模块正是为了解决这些问题而设计的。下面我们逐层拆解。
二、三模块解耦架构
Forge 将工作流拆分为三个 Maven 模块,遵循"引擎 / 插件 / SDK"的分离原则:
scss
┌─────────────────────────────────────────────────────────┐
│ 业务模块 (forge-business) │
│ │
│ @FlowBind @FlowStart @FlowCallback │
│ │ │ │ │
│ ┌────▼───────────▼───────────▼────┐ │
│ │ forge-flow-client (SDK) │ │
│ │ · FlowClient (HTTP 门面) │ │
│ │ · FlowStartAspect (AOP 自动发流) │ │
│ │ · FlowEventSubscriber (事件分发) │ │
│ └────┬───────────────────────┬─────┘ │
│ │ HTTP/RestTemplate │ Redis Pub/Sub │
│ ┌────▼───────────────────────▼─────┐ │
│ │ forge-plugin-flow (引擎插件) │ │
│ │ · FlowInstanceServiceImpl │ │
│ │ · FlowTaskServiceImpl │ │
│ │ · FlowTaskEventListener │ │
│ │ · Flowable 7.0 引擎 │ │
│ └──────────────────────────────────┘ │
│ │
│ forge-flow-server (独立微服务, 可选部署) │
└─────────────────────────────────────────────────────────┘
| 模块 | 定位 | Flowable 依赖 |
|---|---|---|
forge-flow-client |
业务侧 SDK,提供注解 + HTTP 门面 + 事件订阅 | ❌ 无 |
forge-plugin-flow |
引擎核心,封装 Flowable 全部能力 | ✅ 7.0.1 |
forge-flow-server |
独立微服务入口,薄 REST 层 | ✅ |
关键设计 :业务模块只依赖 forge-flow-client,完全不引入 Flowable 的任何类。业务代码通过注解声明流程绑定,通过 HTTP 调用引擎服务,通过 Redis 接收事件回调。这意味着------你可以把流程引擎拆成独立微服务部署,业务模块完全无感知。
三、声明式注解:让业务代码"零侵入"接入流程
这是整个设计中最核心的部分。三个注解,解决"绑定、发起、回调"三个核心问题。
3.1 @FlowBind --- 类级别绑定
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FlowBind {
String modelKey(); // 绑定流程模型 key
String businessType() default "";
}
标注在 Service 类上,声明"这个 Service 管理的业务走哪个流程模型"。后续的 @FlowStart 可以省略 modelKey,FlowEventSubscriber 也靠它来路由回调事件。
3.2 @FlowStart --- 方法级别自动发起
java
@Target(ElementType.METHOD)
public @interface FlowStart {
String modelKey() default ""; // 默认取 @FlowBind 的值
String businessKeySpEl(); // 必填,SpEL 表达式
String titleSpEl() default "";
String userIdSpEl() default "";
String variablesSpEl() default ""; // 必须返回 Map<String,Object>
boolean skipOnError() default true;
}
设计亮点:用 SpEL 表达式从方法参数和返回值中提取流程变量,业务方法完全不用关心流程 API。
AOP 切面 FlowStartAspect 的核心逻辑:
java
@Around("@annotation(flowStart)")
public Object around(ProceedingJoinPoint pjp, FlowStart flowStart) throws Throwable {
Object result;
try {
result = pjp.proceed(); // 先执行业务方法
} catch (Throwable ex) {
if (!flowStart.skipOnError()) tryStart(pjp, flowStart, null);
throw ex;
}
tryStart(pjp, flowStart, result); // 业务成功后自动发起流程
return result;
}
SpEL 上下文支持:#paramName(按参数名)、#p0/#p1(按位置)、#result(返回值),甚至可以通过 BeanResolver 引用 Spring Bean。
使用示例:
ini
@FlowStart(
businessKeySpEl = "'leave:' + #leave.id",
titleSpEl = "#leave.name + ' 的请假申请'",
userIdSpEl = "#leave.applicantId",
variablesSpEl = "{'days': #leave.days, 'type': #leave.leaveType}"
)
public Leave createLeave(Leave leave) {
leaveMapper.insert(leave);
return leave; // 返回值供 SpEL 使用
}
业务方法只管存数据,流程自动发起------这就是"零侵入"的含义。
3.3 @FlowCallback --- 事件回调
scss
@FlowCallback(on = {
FlowCallback.ON_COMPLETED, // 流程通过
FlowCallback.ON_REJECTED, // 流程驳回
FlowCallback.ON_CANCELED, // 流程撤销
FlowCallback.ON_TASK_CREATED, // 任务创建
FlowCallback.ON_TASK_COMPLETED, // 任务完成
FlowCallback.ON_TASK_ASSIGNED // 任务分配
})
public void handleFlowEvent(FlowEventContext context) {
switch (context.getEvent()) {
case FlowCallback.ON_COMPLETED -> markApproved(context.getBusinessKey());
case FlowCallback.ON_REJECTED -> markRejected(context.getBusinessKey(), context.getComment());
case FlowCallback.ON_CANCELED -> markCanceled(context.getBusinessKey());
}
}
FlowEventContext 携带了完整的上下文信息:businessKey、processInstanceId、taskName、assigneeId、comment、variables 等。
事件传递链路:
sql
Flowable 引擎事件
↓
FlowTaskEventListener (引擎侧监听器)
↓
Redis Pub/Sub 频道: flow:event:all
↓
FlowEventSubscriber (业务侧订阅器)
↓
匹配 @FlowBind modelKey → 调用 @FlowCallback 方法
整个链路通过 Redis Pub/Sub 解耦,业务侧和引擎侧可以部署在不同进程。
四、引擎层核心设计:从发流到审批的完整链路
4.1 流程发起 - FlowInstanceServiceImpl
发流看似简单(一行 runtimeService.startProcessInstanceByKey),但企业级场景下要处理一堆边界问题:
scss
// 1. 幂等锁:同一 businessKey 不能重复发起
ReentrantLock lock = acquireFlowStartLock(tenantId, businessKey, 5);
// 2. 先写业务关联表,再启动流程
// 原因:TASK_CREATED 事件监听器需要查到业务上下文
flowBusinessMapper.insert(flowBusiness);
// 3. 自动注入流程变量
variables.put("initiator", startUserId);
variables.put("initiatorLeader", orgService.getLeaderUserIdByLevel(...));
variables.put("regionCode", SessionHelper.getRegionCode());
variables.put("startUserOrgIds", orgService.getUserOrgIds(userId));
// 4. 按需部署:如果没有流程定义,自动从 FlowModel 部署 BPMN
deployModelOnDemand(modelKey);
// 5. 启动流程实例
processInstance = runtimeService.startProcessInstanceByKey(modelKey, businessKey, vars);
五个关键工程决策:
| 决策 | 原因 |
|---|---|
| 幂等锁 + DB 唯一键 | 防止并发请求重复发流,5s 超时 |
| 先写业务表再启流程 | TASK_CREATED 在 startProcessInstanceByKey 同步触发,监听器要能查到上下文 |
| 自动变量注入 | 发起人、直属领导、组织、区划等变量自动填充,BPMN 网关条件可直接用 |
| 按需部署 | 业务侧不需要手动部署流程定义,首次发起自动部署 |
| 锁随事务释放 | 用 TransactionSynchronizationManager 确保锁在事务提交后才释放 |
4.2 任务审批 - FlowTaskServiceImpl
审批的核心不仅是"完成任务",还包括权限校验、变量注入、状态同步:
scss
public void approve(taskId, userId, comment, signature, variables) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 节点级权限校验:这个节点允许审批吗?
validateTaskAction(task, ACTION_APPROVE, comment, signature);
// 校验必填变量
validateRequiredVariables(task, variables);
// 记录审批意见
taskService.addComment(taskId, task.getProcessInstanceId(), comment);
// 注入网关判断变量
completeTask(task, mergeActionVariables(variables, true));
// mergeActionVariables 会设置:
// approved = true
// approvalResult = "approve"
// 网关条件直接读这两个变量做分支
// 自动跳过重复审批人
autoApproveRepeatedTasks(task.getProcessInstanceId());
}
自动跳过重复审批人是个很实用的功能:当流程经过多个节点,而某些节点的审批人是同一个人时,系统会自动帮 ta 审批通过,避免"自己审自己"。支持两种模式:
firstOnly- 同一实例中,一个人只在第一次出现时审批consecutive- 连续节点为同一人时自动跳过
4.3 驳回与退回
Forge 实现了三种"往回走"的操作,语义各不同:
| 操作 | 发起者 | 语义 | 实现 |
|---|---|---|---|
| 驳回 (reject) | 审批人 | 不同意,流程结束 | complete + approvalResult=reject |
| 退回 (returnTask) | 审批人 | 退到指定节点重审 | createChangeActivityStateBuilder |
| 撤回 (withdraw) | 发起人 | 发起人主动撤回 | 受 allowSubmitterWithdraw 控制 |
退回操作使用 Flowable 7 的状态迁移 API:
scss
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveActivityIdsToSingleActivityId(currentActivityIds, targetActivityId)
.changeState();
五、事件驱动同步:FlowTaskEventListener
这是引擎侧的"同步枢纽",实现了 FlowableEventListener,监听所有引擎事件并同步到业务表:
TASK_CREATED → 在 sys_flow_task 创建待办记录 + 发站内信
TASK_COMPLETED → 更新任务状态 + 检测驳回
TASK_ASSIGNED → 更新受理人
PROCESS_COMPLETED → 读取 approvalResult 变量 → 更新 sys_flow_business 状态
PROCESS_CANCELLED → 标记业务为已取消
设计要点:
- 异步发布 - 事件发布用独立线程池
flowEventExecutor(core 2 / max 8 / queue 200),不阻塞引擎线程 - 幂等注册 - 在
ApplicationReadyEvent时注册监听器,带防重复注册保护 - 站内信去重 -
MessageService.sendIfAbsent确保同一任务不会重复发通知 - 抄送触发 -
TASK_CREATED时根据节点配置的角色解析抄送人,触发FlowCcService.sendCc
scss
// FlowAutoConfiguration 中的关键配置
@Bean
public TaskExecutor flowEventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("flow-event-");
return executor;
}
六、前端双设计器:专业 BPMN + 钉钉风格
Forge 前端实现了两套流程设计器,根据模型类型自动切换:
6.1 专业 BPMN 设计器(基于 bpmn-js)
bash
components/bpmn/
├── FlowModeler.vue # 核心设计器
├── CustomRenderer.js # 自定义 SVG 渲染
├── flowable-moddle.json # Flowable 命名空间扩展
├── NodePropertiesPanel.vue # 属性面板
├── AutoLayout.js # dagre 自动布局
└── InteractiveProcessDiagram.vue # 运行时流程图
深度定制 :CustomRenderer.js 重写了所有节点的 SVG 渲染,用渐变色 + 图标替代 bpmn-js 默认样式:
css
const COLORS = {
start: { from: '#34d399', to: '#10b981' }, // 绿
end: { from: '#f87171', to: '#ef4444' }, // 红
userTask: { from: '#60a5fa', to: '#3b82f6' }, // 蓝
serviceTask: { from: '#a78bfa', to: '#8b5cf6' }, // 紫
}
Flowable 属性扩展 :通过 flowable-moddle.json 给 bpmn-js 注入 Flowable 专属属性,让设计器能直接编辑 assignee、candidateUsers、formKey、allowApprove、requireSignature 等属性。
6.2 钉钉风格设计器(从零实现)
bash
components/flow-designer/
├── DingFlowDesigner.vue # 主组件,API 与 FlowModeler 1:1 兼容
├── canvas/ # 画布、节点渲染、边线、布局引擎
├── nodes/ # 审批人/发起/结束/分支/服务/抄送节点
├── panel/ # 各节点配置面板
├── converter/ # JSON ↔ BPMN XML 双向转换
└── viewer/ # 只读运行时视图
这套设计器的核心是 JSON 树 ↔ BPMN XML 的双向转换器:
arduino
// converter/json-to-bpmn.js - 将钉钉风格 JSON 树转为 BPMN XML
// converter/bpmn-to-json.js - 将 BPMN XML 转为钉钉风格 JSON 树
两套设计器对外接口完全一致:
xml
<!-- design.vue 根据模型类型自动切换 -->
<DingFlowDesigner v-if="isApprovalDesigner" :xml="bpmnXml" @change="handleBpmnChange" />
<FlowModeler v-else :xml="bpmnXml" @change="handleBpmnChange" />
业务人员用钉钉风格拖拽设计,专业开发者用 BPMN 设计器精细控制--同一份数据,两种编辑体验。
七、实战:采购审批完整示例
Forge 内置了一个采购审批示例(forge-business 模块),完整演示了从发流到回调的全链路。
7.1 绑定流程模型
less
@Slf4j
@Service
@RequiredArgsConstructor
@FlowBind(modelKey = "purchase_approval", businessType = "purchase")
public class SamplePurchaseOrderServiceImpl
extends ServiceImpl<SamplePurchaseOrderMapper, SamplePurchaseOrder>
implements SamplePurchaseOrderService {
public static final String MODEL_KEY = "purchase_approval";
public static final String BUSINESS_TYPE = "purchase";
private final FlowClient flowClient;
// ...
}
7.2 发起流程
ini
public void submit(SamplePurchaseOrderSubmitDTO dto) {
// 1. 保存业务数据
SamplePurchaseOrder entity = savePurchaseOrder(dto);
// 2. 构建流程变量
String businessKey = "purchase:" + entity.getId();
Map<String, Object> variables = new HashMap<>();
variables.put("recordId", entity.getId());
variables.put("orderNo", entity.getOrderNo());
variables.put("amount", entity.getAmount());
variables.put("deptLeaderId", orgService.getLeaderUserId(entity.getCreateBy()));
variables.put("countersignUsers", engineeringManagerIds);
// 3. 调用 FlowClient 发起流程
FlowResult<String> result = flowClient.startProcess(
MODEL_KEY, businessKey, BUSINESS_TYPE,
flowTitle, variables,
userId, userName, deptId, null);
// 4. 回写流程实例 ID
entity.setBusinessKey(businessKey);
entity.setProcessInstanceId(result.getData());
markInProcess(entity);
}
7.3 接收回调
scss
@FlowCallback(on = {
FlowCallback.ON_TASK_CREATED,
FlowCallback.ON_TASK_COMPLETED,
FlowCallback.ON_COMPLETED,
FlowCallback.ON_REJECTED,
FlowCallback.ON_CANCELED
})
@Transactional(rollbackFor = Exception.class)
public void handleFlowEvent(FlowEventContext context) {
String businessKey = context.getBusinessKey();
String event = context.getEvent();
switch (event) {
case FlowCallback.ON_TASK_CREATED ->
// 进入"申请人修改"节点时,标记为待修改
handleTaskCreated(context);
case FlowCallback.ON_TASK_COMPLETED ->
// 检测是否被驳回
handleTaskCompleted(context);
case FlowCallback.ON_COMPLETED ->
// 流程通过,标记为已审批
markApproved(businessKey);
case FlowCallback.ON_REJECTED ->
// 驳回,记录原因
markRejected(businessKey, context.getComment());
case FlowCallback.ON_CANCELED ->
// 撤销
markCanceled(businessKey);
}
}
7.4 流程模型自举
最巧妙的是 ensureFlowModel() 方法--业务模块可以自己保证流程模型存在:
scss
private void ensureFlowModel() {
// 1. 用代码构建 BPMN XML
String bpmnXml = SamplePurchaseOrderFlowBpmn.build();
// 2. 检查模型是否存在
FlowModelVO model = flowClient.getModelByKey(MODEL_KEY);
if (model == null) {
// 3. 不存在则创建
flowClient.createModel(modelRequest);
}
// 4. 未部署则自动部署
if (!isDeployed(model)) {
flowClient.deployModel(model.getId());
}
}
这意味着业务模块可以独立交付--不需要运维人员手动在管理界面建流程模型,首次启动时自动完成。
7.5 状态机流转
scss
DRAFT(草稿)
↓ submit
IN_PROCESS(审批中)
↓ ON_TASK_CREATED (进入修改节点)
NEED_MODIFY(待修改)
↓ resubmit
IN_PROCESS(审批中)
↓ ON_COMPLETED ↓ ON_REJECTED ↓ ON_CANCELED
APPROVED(已通过) REJECTED(已驳回) CANCELED(已撤销)
八、AI 驱动 BPMN 生成
Forge 集成了 AI 能力,可以根据自然语言描述自动生成 BPMN 流程定义:
less
// FlowBpmnGenerateService - AI 生成 BPMN
public SseEmitter streamGenerate(FlowBpmnGenerateRequest request) {
SseEmitter emitter = new SseEmitter(120_000L);
// 构建富上下文 Prompt
String prompt = buildPrompt(request,
assigneeTypes, // 审批人类型
formTypes, // 表单类型
multiInstanceTypes, // 会签类型
completionConditions, // 完成条件
timeoutActions, // 超时动作
enabledSpelTemplates, // SpEL 模板
enabledForms, // 可用表单
currentNodeConfigs // 当前节点配置
);
// 调用 AI 客户端,流式返回
aiClient.streamChat(prompt, agentCode("flow_bpmn_builder"))
.onProgress(chunk -> emitter.send(SseEmitter.event().name("chunk").data(chunk)))
.onComplete(result -> emitter.send(SseEmitter.event().name("complete").data(result)))
.onError(err -> emitter.send(SseEmitter.event().name("error").data(err.getMessage())));
return emitter;
}
前端通过 SSE(Server-Sent Events)实时接收生成进度:
javascript
// flow-generator.js
const response = await fetch('/api/flow/ai-generator/stream-generate', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify(data),
signal: controller.signal // 支持中断
});
const reader = response.body.getReader();
// 解析 event: progress | chunk | complete | error
Prompt 工程:不是简单地把用户描述扔给大模型,而是注入了完整的平台上下文(可用审批人类型、表单字段、会签规则等),让 AI 生成的 BPMN 直接可用,不需要二次调整。
九、核心设计总结
| 问题 | Forge 的解法 |
|---|---|
| 业务代码耦合流程 API | @FlowStart AOP + SpEL,零侵入发流 |
| 流程状态同步 | FlowTaskEventListener → Redis Pub/Sub → @FlowCallback |
| 多租户隔离 | 引擎侧事件携带 tenantId,回调时切换租户上下文 |
| 流程模型管理 | @FlowBind 绑定 + ensureFlowModel() 自举 |
| 前端设计器门槛 | 双设计器:bpmn-js(专业)+ 钉钉风格(业务友好) |
| 重复审批 | autoApproveRepeatedTasks 自动跳过 |
| 流程定义部署 | 按需部署,首次发起自动 deploy |
| 驳回/退回/撤回 | 三种语义分离,各自独立实现 |
| AI 辅助设计 | 富上下文 Prompt + SSE 流式生成 |
架构分层回顾
less
业务层 @FlowBind / @FlowStart / @FlowCallback ← 声明式,零 Flowable 依赖
SDK层 FlowClient / FlowStartAspect / FlowEventSubscriber ← HTTP + Redis
引擎层 FlowInstanceService / FlowTaskService / EventListener ← Flowable 7
前端层 FlowModeler / DingFlowDesigner ← 双设计器,同一份数据
AI层 FlowBpmnGenerateService ← 自然语言 → BPMN
十、写在最后
企业级工作流从来不是"引入一个引擎"就能解决的问题。Flowable 提供了强大的流程执行能力,但在它和业务系统之间,还需要一整套抽象层来处理声明式接入、事件同步、多租户、设计器适配等问题。
Forge 框架的工作流模块试图给出一份参考实现--不一定是唯一答案,但每一个设计决策都有明确的工程动机。如果你也在做类似的技术选型,希望本文能给你一些启发。
如果这篇文章对你有帮助,欢迎到仓库点个 Star ⭐
- Gitee :gitee.com/ForgeLab/fo...
- GitHub :github.com/yaomindong1...
后续会继续分享:
- Flowable 多租户深度实践
- 动态表单与流程节点字段权限控制
- 钉钉风格设计器的 JSON ↔ BPMN 转换算法
- Flowable 性能优化与历史数据归档
敬请关注,我们下篇见! 🚀