Spring Boot 表单挂 Flowable 审批:设计器绑模型后,业务侧用注解把单子跑起来
上一篇热帖讲的是:用 3 个注解在 Spring Boot 里接 Flowable,搭一条请假审批 (掘金原文,约 18 赞 / 44 藏)。很多人跟完之后卡在下一问------
低代码或应用中心里,表单页已经拖好了,审批节点也画完了。业务侧还要不要在 Service 里手写
RuntimeService.startProcessInstanceByKey?页面和流程模型到底怎么挂在一起,提交之后单子才会出现在待办里?
这篇只补这一段闭环:设计器侧挂模型 → 业务侧把单子跑起来 。方案载体仍是 Forge Admin;标题用 Spring Boot / Flowable / 表单等通用词,方便检索。文中路径相对仓库 forge-project,推断处标「按源码推演」,发布前建议在自己环境用一条请假或采购流复验。
查重:与已发的「审批公共节点合并」「超时终态 ENTITY_DELETED」,以及改期不发的「运行态 designPreview 卡死」都不是同一角度。本篇是热帖续篇,不重复讲 BPMN 节点合并或超时终态。
TL;DR
- 痛点:表单/页面做完,业务 Service 仍直接依赖 Flowable API,引擎一拆库就全改。
- 设计器侧 :业务对象绑定
flowModelKey+startMode(手动按钮 / 触发器 / 两者),节点列表从已发布模型读 userTask。 - 业务侧三件套 :类上
@FlowBind,方法上@FlowStart(或显式FlowClient.startProcess),回调上@FlowCallback。 - 取舍 :需要立刻回写
processInstanceId到业务表时,仓库请假/采购示例走显式FlowClient;不需要回写可用@FlowStart切面。 - 低代码路径 :
BusinessFlowService.startFlow读绑定配置再 RPC 发起,类上@FlowBind(modelKey = "*")承接回调。
一、痛点:表单做完,还在手写 RuntimeService
你大概已经经历过这两种「半成品」:
- 只做了表单 :字段、校验、列表页都有了,提交按钮后面空着,或直接
insert业务表,审批靠人肉群消息。 - 硬接了引擎 :Service 里
@Autowired RuntimeService,到处startProcessInstanceByKey,流程一独立部署,业务模块跟着改编译依赖。
典型「搅在一起」的写法(反例,不要照抄):
java
// 业务 Service 直接依赖引擎 API
Map<String, Object> vars = new HashMap<>();
vars.put("days", leave.getDays());
ProcessInstance pi = runtimeService.startProcessInstanceByKey(
"leave_process", "leave:" + leave.getId(), vars);
leave.setProcessInstanceId(pi.getId());
问题有三:
- 业务代码侵入流程引擎 API,引擎独立部署后到处改。
- 表单页/业务对象和
modelKey没有统一绑定入口,每个模块硬编码字符串。 - 流程结束回写业务状态靠散落的监听器,请假、采购、报销各写一套,难复用。
Forge Admin 的拆法是:流程服务独立(forge-flow)+ 业务侧只认 FlowClient 与注解;低代码侧再把「哪个业务对象绑哪条流程」落到绑定配置,而不是散落在前端常量里。
二、架构一图:设计器挂模型 × 业务侧发起
先建立全景,再往下贴代码就不会懵。
text
┌──────────────────────────────┐ ┌────────────────────────────┐
│ 应用中心 / 表单设计器(UI) │ │ 流程服务 forge-flow │
│ │ │ │
│ 选已发布流程模型 flowModelKey │ 发布 │ Flowable BPMN / 任务流转 │
│ startMode: MANUAL|TRIGGER │ ───────►│ /start/{modelKey} │
│ 节点权限从 userTask 解析 │ │ 事件 → Redis/Webhook │
└──────────────┬───────────────┘ └─────────────▲──────────────┘
│ 绑定配置 │ HTTP
▼ │
┌──────────────────────────────┐ ┌─────────────┴──────────────┐
│ 业务服务(admin / business) │ │ forge-flow-client │
│ │ RPC │ FlowClient │
│ @FlowBind(modelKey=...) │ ───────►│ FlowStartAspect │
│ @FlowStart / startProcess │ │ FlowEventSubscriber │
│ @FlowCallback 回写状态 │◄────────│ Pub/Sub 路由回调 │
└──────────────────────────────┘ └────────────────────────────┘

对应源码落点:
| 层级 | 真实路径 |
|---|---|
| 注解定义 | forge-server/forge-flow/forge-flow-client/src/main/java/com/mdframe/forge/flow/client/annotation/(FlowBind / FlowStart / FlowCallback) |
| 发起切面 | .../helper/FlowStartAspect.java |
| 自动配置 | .../helper/FlowClientHelperAutoConfiguration.java(有 AspectJ + FlowClient 才注册切面) |
| 设计器绑定 UI | forge-admin-ui/src/views/app-center/components/ApplicationFlowBindingBar.vue、designer/BusinessFlowBindingPanel.vue |
| 节点策略 | .../ApplicationFlowNodePanel.vue(按 flowModelKey 拉 userTask) |
| 低代码发起 | forge-plugin-generator/.../businessapp/BusinessFlowService.java 的 startFlow(...) |
| 请假示例 | forge-admin-server/.../leave/service/impl/LeaveRequestServiceImpl.java |
| 采购示例 | forge-business-core/.../purchase/service/impl/SamplePurchaseOrderServiceImpl.java |
服务器入口也能对上:FlowInstanceController 提供 POST /start/{modelKey},客户端最终都是打到这条链,而不是业务进程内嵌 Flowable。
三、设计器侧:页面/业务对象挂上流程模型
打开应用中心业务对象的流程绑定。新入口在应用工作台「业务流程」;旧的 BusinessFlowBindingPanel 会提示「配置已整合到业务流程画布」,并提供跳转按钮------跟做时以新入口为准。
核心字段就两个:
- 流程模型 :下拉已发布模型,值为
modelKey(如leave_process)。 - 谁来发起 / 启动方式 :
MANUAL(用户点按钮)、TRIGGER(触发器自动)、BOTH。
还可以配 流程标题模板 (如 ${opportunityName}-流程)和 业务字段 → 流程变量 映射;未配置时系统会按字段编码同名补齐(面板文案与 resetVariableMappings 行为一致)。
前端绑定栏源码(节选):
vue
<!-- ApplicationFlowBindingBar.vue -->
<n-select
v-model:value="form.flowModelKey"
:options="flowModelOptions"
placeholder="选择已发布的审批流程"
/>
<n-select
v-model:value="form.startMode"
:options="startModeOptions"
:disabled="!form.flowModelKey"
/>
未选模型时提示文案是:「尚未绑定流程模型;绑定后节点配置将自动读取审批节点。」节点策略面板 ApplicationFlowNodePanel 会 watch flowModelKey,去拉该模型的 userTask;解析失败则回退手填节点标识,并提示确认模型已发布且包含用户任务。
保存后写入业务对象的 FLOW 绑定(flowModelKey、titleTemplate、startMode、variableMapping...),编解码在 BusinessFlowBindingCodec。
按源码推演:绑定保存走
saveBusinessFlowBinding(objectCode, payload);低代码正式发起时BusinessFlowService.startFlow会selectBindingByTypeAndCode(..., "FLOW"),缺绑定或缺少flowModelKey直接抛「未配置流程绑定 / 缺少 flowModelKey」。所以「先绑模型再点提交」是硬前提,不是文档建议。
拖审批节点仍在流程设计器里完成;本篇不展开 BPMN 与公共节点合并。你只要记住一句:页面挂的是已发布的 modelKey,不是设计器里未发布的草稿画布。
四、业务侧竖切:三注解把单子跑起来
4.1 类上绑定模型:@FlowBind
java
// forge-flow-client/.../annotation/FlowBind.java 文档示例
@FlowBind(modelKey = "leave_process", businessType = "leave")
@Service
public class LeaveService { ... }
仓库请假实现同样挂了这行(LeaveRequestServiceImpl)。低代码通用回调服务则是:
java
@FlowBind(modelKey = "*", businessType = "lowcode-business")
public class BusinessFlowService { ... }
modelKey = "*" 表示按事件里的 processDefKey / businessKey 动态匹配,适合「一个服务承接多种业务对象」。采购示例则绑死具体常量:@FlowBind(modelKey = SamplePurchaseOrderService.MODEL_KEY, businessType = ...)。
4.2 方法成功后自动发起:@FlowStart
注解把 SpEL 约定写在源码注释里(FlowStart.java):
java
@FlowStart(
modelKey = "leave_process", // 可省略,取 @FlowBind.modelKey
businessKeySpEl = "'leave:' + #leave.id",
titleSpEl = "#leave.name + ' 的请假申请'",
userIdSpEl = "#leave.applicantId",
userNameSpEl = "#leave.applicantName",
variablesSpEl = "{'days': #leave.days}"
)
public LeaveApply submit(LeaveApply leave) { ... }
切面 FlowStartAspect 在方法 proceed() 成功后解析 SpEL,调用 flowClient.startProcess(...);businessKeySpEl 为空会直接跳过并发 warn。引入 forge-flow-client、容器里有 FlowClient、类路径有 AspectJ 时,由 FlowClientHelperAutoConfiguration 注册该切面。
仓库里的真实取舍(跟做必看): 请假与采购示例目前没有 在 submit 方法上贴 @FlowStart,而是显式调用 FlowClient.startProcess。请假代码注释写得很直白------
显式发起流程并回写
processInstanceId(@FlowStart切面无法回写业务表字段)
java
// LeaveRequestServiceImpl.submitLeave(真实代码)
FlowResult<String> startResult = flowClient.startProcess(
PROCESS_KEY, businessKey, BUSINESS_TYPE, title,
Collections.emptyMap(),
leave.getApplyUserId(), leave.getApplyUserName(),
leave.getApplyDeptId(), leave.getApplyDeptName());
if (!startResult.isSuccess() || !StringUtils.hasText(startResult.getData())) {
throw new BusinessException(...);
}
leave.setProcessInstanceId(startResult.getData());
updateById(leave);
采购单 SamplePurchaseOrderServiceImpl.submit 同理:先组装变量(金额、会签人、部门负责人等),再 RPC 启动,成功后写 processInstanceId 并 markInProcess。注释还强调「流程启动(远程 RPC)在事务外执行,避免长事务占用 DB 连接」------这是生产向细节,纯 @FlowStart 默认在业务方法返回后触发,细控事务边界时反而显式调用更清晰。
跟做决策表:
| 场景 | 推荐 |
|---|---|
| 演示/简单单据,不需要把 instanceId 落业务表 | @FlowStart 零侵入 |
要落 process_instance_id,或要卡启动失败回滚语义 |
显式 FlowClient.startProcess(与仓库示例一致) |
| 低代码按钮 / 触发器 | 走绑定 + BusinessFlowService.startFlow |
两种写法都依赖同一条 HTTP 发起链,都不要再注入 RuntimeService。
4.3 结束与节点事件:@FlowCallback
java
@FlowCallback(on = FlowCallback.ON_COMPLETED)
public void onLeaveApproved(FlowEventContext ctx) {
LeaveRequest leave = getByBusinessKey(ctx.getBusinessKey());
if (leave != null) {
leave.setStatus(LeaveRequestStatus.APPROVED.getCode());
leave.setApproveComment(ctx.getLastComment());
updateById(leave);
}
}
事件常量:PROCESS_COMPLETED / PROCESS_REJECTED / PROCESS_CANCELED,以及任务级 TASK_CREATED / TASK_COMPLETED / TASK_ASSIGNED。请假示例拆成三个方法;采购示例一个方法里 on = { ... } 多事件,再按 context.getEvent() 分支。分发靠 FlowEventSubscriber(默认订阅 Redis 频道 flow:event:all,也可 Webhook)。
若回调对不上,先查两件事:类上是否有匹配的 @FlowBind;forge.flow.client.redis-subscribe 是否为 true(自动配置默认 true)。
4.4 低代码:绑定配置驱动的发起
java
// BusinessFlowService.startFlow(真实逻辑摘要)
AiBusinessBinding flowBinding = bindingMapper.selectBindingByTypeAndCode(
tenantId, "OBJECT", objectCode, "FLOW");
String flowModelKey = resolveFlowModelKey(bindingConfig);
String businessKey = objectCode + ":" + recordId;
FlowResult<String> result = flowClient.startProcess(
flowModelKey, businessKey, title,
flowVariables, String.valueOf(userId), userName, null, null);
跟做可以压成四步:
- 在流程设计器发布模型,记下
modelKey。 - 在业务对象绑定该
modelKey,选好startMode,保存。 - 代码优先模块:
@FlowBind+(@FlowStart或显式startProcess)+@FlowCallback。 - 低代码模块:保存业务记录后点「提交审批」或走触发器,后台进
startFlow。
文档技能 code-first-workflow.md 也建议:仅 DRAFT 可提交、启动成功后再置 IN_PROCESS、回调状态迁移要幂等------和采购示例一致。
五、对照表 & 自测清单
注解 / 入口对照
| 能力 | 位置 | 作用 | 仓库证据 |
|---|---|---|---|
@FlowBind |
类 | 声明 modelKey / businessType | Leave、Purchase、BusinessFlowService |
@FlowStart |
方法 | 成功后自动 startProcess |
注解 + FlowStartAspect(业务示例多改显式调用) |
@FlowCallback |
方法 | 收流程/任务事件回写业务 | Leave 三回调;Purchase 合并回调 |
| 设计器绑定 | UI + Codec | flowModelKey / startMode / 变量映射 |
BindingBar / BindingPanel / BindingCodec |
| 低代码发起 | Service | 读绑定后 RPC | BusinessFlowService.startFlow |
自测清单(建议收藏)
- 流程模型已发布 ,绑定下拉里能选到,且
modelKey与@FlowBind/ 绑定 JSON 一致。 -
startMode与入口一致:按钮发起用MANUAL/BOTH;只靠触发器不要指望列表按钮出现。 - 业务键全局唯一(请假
LEAVE_.../ 低代码objectCode:recordId/ 采购按定义拼接)。 - 若业务表有
process_instance_id,用显式FlowClient回写;不要假设@FlowStart会改实体字段。 - Redis 或 Webhook 已通:通过/驳回后业务状态有变;日志里能看到回调命中对应
@FlowBindBean。 - 业务 Service 未 直接注入
RuntimeService/TaskService。 - 启动失败时有明确业务异常(请假/采购都校验
FlowResult.isSuccess()),避免「表有数据、待办没有」。

六、小结 & CTA
表单挂审批不是再写一遍 Flowable 教程,而是两段对接:
- 设计器 :业务对象 ↔ 已发布
modelKey(再配启动方式与变量); - 业务侧 :
forge-flow-client三注解(或显式FlowClient)把单子跑起来、把状态收回来。
上一篇解决「注解怎么写」;这篇解决「页面怎么挂、单子怎么真的进待办」。Forge Admin 开源仓库与演示:
- Gitee:gitee.com/ForgeLab/fo...
- GitHub:github.com/yaomindong1...
- 文档:www.dlforgelab.com:8084/forge-docs/
- 演示:www.dlforgelab.com:8084/forge/login...
评论区想看下一篇哪条?例如「触发器自动发起的变量映射踩坑」或「任务表单字段权限如何跟节点走」------留言出题,少踩一个坑。
(系列:上篇「3 个注解搭请假」→ 本篇「表单挂模型 + 发起闭环」。这里只谈 Spring Boot + Flowable + 表单绑定的跟做路径。)