低代码表单怎么和 Flowable 工作流整合?ForgeAdmin 的 3 种数据模式设计
低代码表单怎么和 Flowable 工作流整合?Forge 的 3 种数据模式设计
低代码搭表单、Flowable 跑审批,这两件事单独做都不难。难的是合在一起:表单数据要不要落业务表?流程变量怎么传?审批中途数据存哪?这篇拆 Forge 的真实实现,看它怎么用 3 种数据模式解决这个整合难题。
做过审批系统的人都知道,低代码表单和工作流引擎之间有一道"缝":
- 低代码表单负责收集数据(请假天数、报销金额、采购明细)
- 工作流引擎负责驱动流程(谁审批、怎么流转、什么时候结束)
但这两边的数据怎么流动,是最容易出问题的地方。
请假审批的表单数据,是只存在流程变量里?还是落到业务表?还是两边都存?不同选择,后续的查询、统计、数据权限完全不同。
Forge Admin 在这块做了一个很清晰的设计:用 3 种数据模式(dataMode)来定义表单数据和流程的关系。这篇就拆这个设计。
一、先看清楚:整合的 4 个核心实体
Forge 的流程和低代码整合,核心是 4 张表的关系。看懂这张图,后面的设计就清楚了:
FlowForm(流程表单定义)
│ formSchema: JSON 格式的表单协议(字段、类型、校验)
│ formType: dynamic(动态表单) / external(外部表单) / builtin(内置表单)
│
▼
FlowEntry(流程入口)
│ modelKey: 绑定哪个流程模型(Flowable BPMN)
│ formKey: 绑定哪个表单(FlowForm)
│ dataMode: 数据模式(PROCESS_ONLY / BUSINESS_OBJECT / HYBRID)
│ objectCode:业务对象编码(落哪张业务表)
│
├──► FlowEntryFieldMapping(字段映射)
│ formField → 表单字段
│ targetField → 业务对象字段
│ flowVariable → 流程变量
│
▼
FlowFormInstance(填报实例快照)
│ schemaSnapshot: 提交时的表单 schema 快照
│ formData: 用户填的数据
│ processInstanceId: 关联的流程实例
│ objectCode + recordId: 关联的业务记录
用一句话说清楚:
- FlowForm 定义"表单长什么样"(低代码表单设计器产出)
- FlowEntry 是"流程入口",把表单和流程模型绑在一起,并决定数据怎么处理
- FlowEntryFieldMapping 定义"表单字段怎么映射到业务表和流程变量"
- FlowFormInstance 是"每次提交的快照",保存当时填的数据和表单结构
二、核心设计:3 种数据模式
整合的难点不在"表单怎么渲染",而在"表单数据提交后怎么处理"。Forge 定义了 3 种模式,对应 3 种业务场景。
模式一:PROCESS_ONLY(纯流程模式)
表单数据只存在流程变量里,不落业务表。
用户填表单 → 表单数据作为流程变量 → 流程流转 → 结束
适用场景:简单的审批,不需要事后查询业务数据。
比如:请假审批。审批通过后,人事在考勤表里手动记录就行,不需要自动落一张"请假记录表"。
优点:简单,不用建业务表,不用写字段映射。
缺点:事后查数据只能查流程历史,不能像查业务表那样分页、筛选、统计。
模式二:BUSINESS_OBJECT(业务对象模式)
表单数据通过字段映射,落到业务表里,流程里不存表单数据。
用户填表单 → 字段映射 → 落业务表 → 流程变量只存关键字段 → 流程流转
适用场景:审批完要当业务数据用的场景。
比如:采购审批。采购单审批通过后,要变成一条采购记录,后续要查询、统计、对账。这种场景必须落业务表。
优点:数据在业务表里,可以正常查询、分页、做数据权限、做统计看板。
缺点:要建业务表、配字段映射、实现业务对象适配器。
模式三:HYBRID(混合模式)
表单数据既落业务表,又留一份快照在流程侧。
用户填表单 → 字段映射 → 落业务表
↘ 同时存一份 FlowFormInstance 快照
流程变量存关键字段 → 流程流转
适用场景:既要业务表查询,又要追溯"提交时表单长什么样"。
比如:合同审批。合同审批通过后要落合同表(BUSINESS_OBJECT),但三个月后可能要查"当时提交审批时填了什么"——如果表单结构改过,纯业务表无法还原当时的填报内容。HYBRID 模式存了一份 schemaSnapshot + formData,可以完整还原。
优点:业务表可查 + 快照可追溯,最完整。
缺点:数据存两份,存储成本略高。
三、源码怎么实现的?
直接看 FlowRuntimeServiceImpl.submitByMode(),这是整合的核心入口:
private FlowStartResultVO submitByMode(FlowEntryRuntimeVO runtime,
List<FlowEntryFieldMapping> mappings,
Map<String, Object> formData,
FlowEntrySubmitDTO dto,
String dataMode) {
FlowEntry entry = runtime.getEntry();
if (BUSINESS_OBJECT.equals(dataMode)) {
return submitBusinessObject(entry, mappings, formData, dto, false);
}
if (HYBRID.equals(dataMode)) {
return submitBusinessObject(entry, mappings, formData, dto, true);
}
return submitProcessOnly(runtime, mappings, formData, dto);
}
就 3 个分支,对应 3 种模式。BUSINESS_OBJECT 和 HYBRID 走同一个方法,区别在 keepSnapshot 参数。
PROCESS_ONLY 怎么处理
纯流程模式最简单:把表单数据塞进流程变量,发起流程,完事。
private FlowStartResultVO submitProcessOnly(...) {
// 1. 构建流程变量
Map<String, Object> variables = buildFlowVariables(formData, mappings, dto);
// 2. 创建表单实例记录(只存基础信息,不存快照)
FlowFormInstance instance = new FlowFormInstance();
instance.setDataMode(PROCESS_ONLY);
instance.setFormData(toJson(formData));
formInstanceMapper.insert(instance);
// 3. 发起流程
String processInstanceId = flowInstanceService.startProcess(
entry.getModelKey(), businessKey, ..., variables, ...);
// 4. 关联回表单实例
formInstanceMapper.updateProcessInstance(instanceId, processInstanceId, "RUNNING");
return result;
}
BUSINESS_OBJECT / HYBRID 怎么处理
这两种模式要落业务表,核心靠 FlowBusinessObjectRuntimeAdapter 这个扩展点:
private FlowStartResultVO submitBusinessObject(FlowEntry entry,
List<FlowEntryFieldMapping> mappings,
Map<String, Object> formData,
FlowEntrySubmitDTO dto,
boolean keepSnapshot) {
// 1. 调用业务侧适配器,把表单数据落成业务记录
BusinessRecordCreateResult record =
businessObjectRuntimeAdapter.createBusinessRecord(entry, mappings, formData);
// 2. 构建流程变量(从字段映射里取)
Map<String, Object> variables = buildFlowVariables(formData, mappings, dto);
// 3. HYBRID 模式:额外存一份快照
if (keepSnapshot) {
FlowFormInstance instance = new FlowFormInstance();
instance.setSchemaSnapshot(runtime.getFormVersion().getFormSchema());
instance.setFieldRegistry(runtime.getFormVersion().getFieldRegistry());
instance.setFormData(toJson(formData));
instance.setDataMode(HYBRID);
instance.setObjectCode(objectCode);
instance.setRecordId(record.getRecordId()); // 关联业务记录
formInstanceMapper.insert(instance);
variables.put("flowFormInstanceId", String.valueOf(formInstanceId));
}
// 4. 发起流程
String processInstanceId = flowInstanceService.startProcess(...);
return result;
}
四、关键解耦:FlowBusinessObjectRuntimeAdapter
注意上面代码里的 businessObjectRuntimeAdapter.createBusinessRecord()。这是整个整合设计里最巧妙的一步。
问题:流程插件要把表单数据落业务表,但"业务表"是业务模块定义的,流程插件不知道你的 biz_purchase 表长什么样。
如果让流程插件直接依赖业务模块,架构就耦合了——流程插件要认识采购表、合同表、报销表……每加一个业务就要改流程插件。
Forge 的解法:流程插件定义一个扩展点接口,由业务侧实现:
public interface FlowBusinessObjectRuntimeAdapter {
BusinessRecordCreateResult createBusinessRecord(
FlowEntry entry, // 流程入口配置
List<FlowEntryFieldMapping> mappings, // 字段映射规则
Map<String, Object> formData // 用户填的表单数据
);
@Data
class BusinessRecordCreateResult {
private String objectCode; // 业务对象编码
private Long recordId; // 业务记录ID
private String businessKey; // 业务键
private Map<String, Object> variables; // 额外流程变量
}
}
源码注释写得很明确:
Flow 插件不直接依赖低代码 generator 插件;需要 BUSINESS_OBJECT/HYBRID 时,由业务侧提供实现,把表单数据映射为业务对象记录。
业务侧怎么实现:
@Component
public class PurchaseFlowAdapter implements FlowBusinessObjectRuntimeAdapter {
@Autowired
private PurchaseMapper purchaseMapper;
@Override
public BusinessRecordCreateResult createBusinessRecord(
FlowEntry entry,
List<FlowEntryFieldMapping> mappings,
Map<String, Object> formData) {
// 1. 按字段映射,把表单数据组装成业务对象
Purchase purchase = new Purchase();
for (FlowEntryFieldMapping mapping : mappings) {
Object value = formData.get(mapping.getFormField());
// 反射或手动 set 到业务对象
setFieldValue(purchase, mapping.getTargetField(), value);
}
// 2. 落业务表
purchaseMapper.insert(purchase);
// 3. 返回业务记录信息
BusinessRecordCreateResult result = new BusinessRecordCreateResult();
result.setObjectCode("purchase");
result.setRecordId(purchase.getId());
result.setBusinessKey("purchase:" + purchase.getId());
return result;
}
}
这样流程插件和业务模块完全解耦:
- 流程插件只管"调适配器、拿记录ID、发起流程"
- 业务模块只管"按映射规则把表单数据落表"
- 字段映射关系在
FlowEntryFieldMapping表里配置,不用写代码
五、字段映射:表单字段怎么变成业务数据和流程变量
FlowEntryFieldMapping 是连接表单和流程/业务表的桥梁:
public class FlowEntryFieldMapping {
private String formField; // 表单字段名
private String targetType; // 映射目标类型:业务字段 / 流程变量
private String targetField; // 目标字段名
private String flowVariable; // 流程变量名
private Integer required; // 是否必填
}
一个表单字段可以同时映射到两个地方:
表单字段 "days"(请假天数)
├── targetType=BUSINESS_FIELD, targetField="leave_days" → 落业务表
└── targetType=FLOW_VARIABLE, flowVariable="days" → 流程变量(条件分支用)
这样设计的好处是:
- 业务表存完整数据(查询、统计、数据权限用)
- 流程变量只存条件判断需要的关键字段(流程分支用,比如
days > 3走老板审批) - 映射关系可配置,不用写代码
六、表单类型:不只是动态表单
FlowForm 支持三种表单类型:
private String formType; // dynamic / external / builtin
| 类型 | 说明 | 适用场景 |
|---|---|---|
dynamic |
动态表单,formSchema 是 JSON 协议,运行时渲染 | 标准审批表单(请假、报销) |
external |
外部表单,指向一个 URL | 已有业务页面接审批 |
builtin |
内置表单,指定前端组件路径 | 定制化程度高的表单 |
dynamic 类型就是低代码表单和流程整合的核心:低代码表单设计器产出的 JSON Schema 存到 formSchema 字段,流程审批时运行时引擎按这个 Schema 渲染表单。
external 类型则给"已有业务页面"留了路:你的采购管理页面已经做好了,只需要在审批时跳过去填,不用重新用低代码搭一遍。
这种设计保证了整合不是强制的——你可以全用低代码动态表单,也可以对接已有页面,不用为了用流程引擎就把所有表单重做。
七、快照机制:为什么 HYBRID 要存两份?
HYBRID 模式下,FlowFormInstance 存了这些字段:
private String schemaSnapshot; // 提交时的表单 Schema 快照
private String fieldRegistry; // 字段目录快照
private String formData; // 用户填的数据
private String dataMode; // HYBRID
private String objectCode; // 业务对象编码
private Long recordId; // 业务记录ID
为什么要存快照?因为表单结构会变。
假设 3 月你用 v1 版表单提交了采购审批,表单有"采购金额"字段。5 月业务方加了"采购用途"字段,表单变成 v2。
如果只存业务表数据,3 月的那条采购记录没有"采购用途"——这没问题。但如果你想在审批历史里还原"3 月提交时表单长什么样",没有快照就做不到。
快照保存的是提交那一刻的表单结构 + 数据。无论表单后续怎么改,历史记录都能完整还原。
这对合规审计很重要——政务、金融场景经常要求"可追溯当时填了什么、表单是什么样的"。
八、完整链路:从填表到审批的 6 步
把整个整合链路串一遍:
1. 管理员在低代码表单设计器里设计表单 → 存 FlowForm(formSchema)
2. 管理员创建 FlowEntry,绑定 modelKey + formKey + dataMode + 字段映射
3. 用户在待办页面点"发起审批" → 运行时加载 FlowEntry + FlowForm
4. 运行时引擎按 formSchema 渲染表单 → 用户填写
5. 提交 → FlowRuntimeServiceImpl.submitEntryForm()
├── PROCESS_ONLY:表单数据 → 流程变量 → 发起流程
├── BUSINESS_OBJECT:表单数据 → 字段映射 → 落业务表 → 发起流程
└── HYBRID:落业务表 + 存快照 → 发起流程
6. 流程结束 → @FlowCallback 回调业务 → 更新业务状态
九、和"纯 Flowable 接入"的关系
上一篇《Spring Boot 接 Flowable:用 3 个注解搭一个请假审批流程》讲的是业务侧怎么用 @FlowStart / @FlowCallback 接流程——那适合"已有业务表,想加审批"的场景。
这篇讲的是低代码表单和流程的深度整合——适合"用低代码搭表单 + 走审批 + 数据落表"的场景。
两者的关系:
| 对比 | 注解化接入(@FlowStart) | 低代码整合(FlowEntry) |
|---|---|---|
| 表单来源 | 业务模块自己的页面 | 低代码表单设计器 |
| 数据存储 | 业务模块自己管 | 由 dataMode 决定 |
| 字段映射 | 不需要(业务方法直接处理) | FlowEntryFieldMapping 配置 |
| 适用场景 | 已有业务系统加审批 | 从零搭审批表单 + 流程 + 落表 |
| 侵入性 | 加注解 | 配置化,几乎无侵入 |
两条路按场景选:已有业务表用注解,从零搭用低代码整合。
十、总结:整合的核心是"数据模式 + 字段映射 + 扩展点"
低代码和 Flowable 的整合,核心不是"表单怎么渲染"(那是低代码的事),而是表单数据提交后怎么和流程、业务表三者协同。
Forge 的设计可以总结为 3 层:
| 层 | 机制 | 解决什么 |
|---|---|---|
| 数据模式 | PROCESS_ONLY / BUSINESS_OBJECT / HYBRID | 表单数据存哪 |
| 字段映射 | FlowEntryFieldMapping | 表单字段怎么变成业务数据和流程变量 |
| 扩展点 | FlowBusinessObjectRuntimeAdapter | 流程插件和业务模块解耦 |
这 3 层做到了:
- 不耦合:流程插件不认识业务表,通过扩展点对接
- 可配置:字段映射在后台配,不改代码
- 可追溯:HYBRID 模式存快照,历史可还原
- 可选择:3 种模式按场景选,不是一刀切
这才是低代码和工作流整合该有的设计——不是把两边硬拼在一起,而是用清晰的数据模式定义边界,用扩展点解耦,用字段映射做桥梁。
源码自取(本文所有代码片段均来自仓库,可对照核验):
- Gitee:https://gitee.com/ForgeLab/forge-admin
- GitHub:https://github.com/yaomindong1996/forge-admin
- 在线演示:http://www.dlforgelab.com:8084/forge/login (admin / 123456)
你们的审批表单和工作流是怎么整合的?表单数据落业务表还是只存流程变量?评论区聊聊。
更多推荐



所有评论(0)