JeecgBoot 低代码平台自带两个流程模块——"协同工作"和"Flowable 流程审批"。不少团队上线后第一反应是:这俩不都是审批吗,干嘛要两个?随便选一个不就行了?结果要么把临时协作硬塞进 BPMN 流程图,审批节点画得比业务还复杂;要么拿协同工作跑正式制度流程,三个月后流程记录一团乱,审计时谁也说不清审批链路。
两个模块的设计哲学完全不同,混用代价不小。下面把它们的本质差异拆开,再给一个选型决策的实操方法。
临时拉群 vs 正式流程:底层逻辑不一样
协同工作的设计隐喻是"临时拉群办事"——有人突然需要一件事走个审批,当场选人、当场发起、当场走完。没有预定义的流程图,发起人自己决定下一步给谁审批,审批人也可以继续往下指派。核心特征:
- 无固定流程模板,发起时才确定路径
- 审批人由发起人或当前处理人动态指定
- 流程结束即归档,不强调长期复用
- 适合低频、一次性、路径不确定的场景
Flowable 流程审批的设计隐喻是"制度流程"——流程图提前画好,每个节点绑定了角色或条件,发起人只填业务数据,路径由引擎按规则自动推进。核心特征:
- 流程图(BPMN)必须预先设计并部署
- 节点审批人由流程定义中的表达式或角色绑定决定
- 支持条件分支、并行网关、子流程等复杂路由
- 适合高频、制度性、路径确定的场景
一句话总结:协同工作是"人指路",Flowable 是"图指路"。
典型误用场景与后果
| 误用方式 | 后果 |
|---|---|
| 用 Flowable 跑一次性临时审批 | 每次都要先画流程图再部署,开发成本远超业务价值;流程模板堆积,维护负担重 |
| 用协同工作跑正式制度流程(如报销、合同审批) | 每次审批链路不同,无法保证合规路径;历史记录碎片化,审计时无法追溯"谁在哪个节点批了什么" |
| 两个模块混用同一个业务表单 | 数据分散在两套流程引擎的表中,统计报表拼不出来;用户不知道该从哪个入口发起 |
最典型的坑:一个报销流程,前半段用协同工作(因为"灵活"),后半段接入 Flowable(因为"要走财务制度节点")。结果是流程断在两个引擎的边界上,状态同步全靠手工补数据。
选型决策:一张图 + 一个检查清单
选型不需要纠结,问自己三个问题:
- 这个流程的路径是否每次都一样? → 是 → Flowable;否 → 协同工作
- 是否需要审计追溯"必须经过哪些节点"? → 是 → Flowable;否 → 协同工作
- 流程是否会被反复发起(高频复用)? → 是 → Flowable;否 → 协同工作
三个问题中有两个指向同一个答案,就用那个。如果 2:1 拿不准,默认选 Flowable——制度性流程的合规风险比临时流程的灵活性代价更严重。
下面用一个实际项目中的配置片段,展示如何在 JeecgBoot 中为同一个业务表单分别接入两种流程,并通过前端路由区分入口。
实操示例:同一表单,双入口分流
假设你有一个"出差申请"表单 biz_trip,制度性出差走 Flowable,临时性出差(比如紧急现场支援)走协同工作。核心思路是:表单数据只存一份,流程入口在前端分流,后端通过不同 Service 调用不同引擎。
1. 表单设计——只做一份
在 JeecgBoot 的 Online 表单设计中创建 biz_trip 表单,字段包括申请人、目的地、日期、事由、预算等。不要在表单层面区分流程类型,流程类型是运行时决策。
2. 后端 Service 分流
@Service
public class BizTripProcessService {
@Autowired
private FlowableProcessService flowableService; // Flowable 流程服务
@Autowired
private CollaborativeWorkService collaborativeService; // 协同工作服务
/**
* 发起出差审批——根据流程类型分流到不同引擎
* @param tripId 出差申请ID
* @param processType "flowable" 或 "collaborative"
* @param nextAssignee 协同工作模式下,下一步审批人账号
*/
public void startProcess(String tripId, String processType, String nextAssignee) {
if ("flowable".equals(processType)) {
// Flowable:流程图已预部署,按定义key启动
// 流程定义中已绑定各节点角色,不需要手动指定审批人
flowableService.startProcessByKey("biz_trip_flowable", tripId);
} else if ("collaborative".equals(processType)) {
// 协同工作:无预定义流程,发起时指定第一个审批人
collaborativeService.startAdhocProcess(
"biz_trip", tripId, nextAssignee
);
} else {
throw new IllegalArgumentException("未知的流程类型: " + processType);
}
}
}
关键点:
- Flowable 入口只需要 processDefinitionKey 和 businessKey,审批人由流程图中的表达式决定。
- 协同工作入口必须传入 nextAssignee,因为路径是发起人当场决定的。
3. Flowable 流程定义——出差制度流程
以下是出差制度流程的 BPMN XML(简化版),部署到 Flowable 后即可被上面的 Service 调用:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
targetNamespace="jeecgboot">
<process id="biz_trip_flowable" name="出差申请-制度流程">
<startEvent id="start" name="发起出差申请"/>
<!-- 部门经理审批:绑定角色表达式 -->
<userTask id="deptManagerApproval" name="部门经理审批">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>role:dept_manager</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<!-- 条件分支:预算超过5000走财务总监 -->
<exclusiveGateway id="budgetGateway" name="预算判断"/>
<userTask id="financeDirectorApproval" name="财务总监审批">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>role:finance_director</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<endEvent id="end" name="审批完成"/>
<!-- 连线 -->
<sequenceFlow sourceRef="start" targetRef="deptManagerApproval"/>
<sequenceFlow sourceRef="deptManagerApproval" targetRef="budgetGateway"/>
<sequenceFlow sourceRef="budgetGateway" targetRef="financeDirectorApproval">
<conditionExpression>${budget > 5000}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="budgetGateway" targetRef="end">
<conditionExpression>${budget <= 5000}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="financeDirectorApproval" targetRef="end"/>
</process>
</definitions>
部署方式:在 JeecgBoot 的流程设计器中导入此 XML,或通过 Flowable 的 REST API 部署:
# 通过 Flowable REST API 部署流程定义
curl -X POST "http://localhost:8080/flowable/repository/deployments" \
-H "Content-Type: multipart/form-data" \
-F "file=@biz_trip_flowable.bpmn20.xml"
4. 前端入口分流
在 JeecgBoot 的表单详情页中,根据业务场景提供两个按钮:
<template>
<a-form-item label="流程类型">
<a-radio-group v-model="processType">
<a-radio value="flowable">制度性出差(固定审批链路)</a-radio>
<a-radio value="collaborative">临时出差(自选审批人)</a-radio>
</a-radio-group>
</a-form-item>
<!-- 协同工作模式下,显示审批人选择框 -->
<a-form-item v-if="processType === 'collaborative'" label="下一步审批人">
<j-select-user-by-dept v-model="nextAssignee" />
</a-form-item>
<a-button type="primary" @click="handleSubmit">提交审批</a-button>
</template>
<script setup>
import { ref } from 'vue';
import { postAction } from '@/api/manage';
const processType = ref('flowable');
const nextAssignee = ref('');
function handleSubmit() {
postAction('/bizTrip/process/start', {
tripId: formState.id,
processType: processType.value,
nextAssignee: nextAssignee.value, // Flowable模式下此字段忽略
}).then(res => {
if (res.success) {
message.success('审批已发起');
}
});
}
</script>
这样用户在提交时就能明确选择流程路径,后端自动分流到对应引擎,数据始终只存一份。
上线前的检查清单
在正式启用双流程之前,逐项确认:
- [ ] 每个业务表单只接入一个流程引擎作为主流程,辅助性通知/催办可以用协同工作,但主审批链路不要跨引擎。
- [ ] Flowable 流程图已部署并验证——至少跑一遍完整路径的测试发起,确认节点角色绑定和条件分支生效。
- [ ] 协同工作的审批记录有归档机制——临时流程也要留存"谁批了什么",不能审批完就散。
- [ ] 报表和统计只从主流程引擎取数据——避免两个引擎的数据拼在一起导致口径不一致。
- [ ] 用户培训区分两个入口——前端按钮文案必须让普通用户一眼看出"制度流程"和"临时协作"的区别,不要用技术术语。
两个引擎不是冗余,是针对两种截然不同的协作模式。选对了,流程跑得顺、审计查得清;选错了,要么开发成本浪费在画一次性流程图上,要么合规风险藏在碎片化的审批记录里。别再混着用了。