Camunda Modeler实战:5分钟搞定审批流程设计(附Java回调代码)
如果你正在为OA系统里那些繁琐的报销、请假、采购审批流程头疼,觉得每次都要写一堆重复的业务代码太费时,那今天这篇文章就是为你准备的。Camunda这个开源工作流引擎,配合它的可视化设计器Modeler,能让你像搭积木一样快速构建出可执行的审批流程。很多开发者第一次接触时,可能会被BPMN那些复杂的符号吓到,但其实核心的审批场景,用到的元素就那么几个。我经历过从手动硬编码状态机到引入工作流引擎的整个过程,最大的感受就是:把流程逻辑从代码里抽离出来,用图形化的方式管理和迭代,后期的维护成本和灵活性完全是两个维度。
这篇文章不会讲太多枯燥的理论,我们就聚焦一个最典型的场景:一个带条件分支的审批流。比如,金额超过5000的采购单需要总监审批,以下的经理审批即可。我会手把手带你用Camunda Modeler在五分钟内画出这个流程图,并配上可以直接粘贴复用的Java回调代码。无论你是想快速验证原型,还是为现有系统引入流程自动化能力,这套组合拳都能让你立刻上手。
1. 环境准备与核心概念速览
在开始画图之前,我们得先把“舞台”搭好。Camunda Modeler是一个独立的桌面应用,不需要连接任何服务就能进行流程设计,这为本地开发和调试提供了极大的便利。你可以直接从Camunda官网下载对应你操作系统(Windows、macOS、Linux)的版本,安装过程就是简单的下一步。
安装完成后打开,你会看到一个清爽的界面。中间是画布,左侧是元素面板,右侧是属性配置区。对于审批流程,我们最需要关注的BPMN元素其实只有四类:
- 事件(Events):流程的开始与结束。我们通常用一个空心的圆圈表示开始事件,一个粗边圆圈表示结束事件。
- 活动(Activities):流程中需要执行的工作。用户任务(User Task) 是最关键的,它代表需要人工审批的环节,在图上显示为一个圆角矩形,左上角带一个人形图标。
- 网关(Gateways):控制流程的分支与合并。排他网关(Exclusive Gateway),长得像菱形,是审批中最常用的,它根据条件表达式决定流程走向哪条分支。
- 顺序流(Sequence Flows):连接以上元素的箭头,表示执行的顺序。
理解这几个元素,你就能构建出80%的审批场景。下面这个表格快速对比了它们的核心作用:
| 元素类型 | BPMN符号 | 在审批流程中的角色 | 关键属性 |
|---|---|---|---|
| 开始事件 | ⭘ (空心圆) | 流程的触发起点 | 通常无需特殊配置 |
| 用户任务 | ▢ (圆角矩形,带人头图标) | 等待审批人处理的任务节点 | Assignee(指定处理人), Candidate Users/Groups(候选用户/组) |
| 排他网关 | ◇ (菱形) | 根据审批结果(如“同意/拒绝”)决定下一步 | 条件配置在其流出的顺序流上 |
| 顺序流 | → (箭头) | 连接节点,定义流转路径 | Condition Expression(条件表达式) |
| 结束事件 | ⭘ (粗边圆) | 流程或分支的终点 | 通常无需特殊配置 |
提示:刚开始设计时,不必追求流程图的“美学”,先把主干逻辑跑通。复杂的多级审批、会签或签,都是在这些基础元素上组合演变而来的。
有了这些基本认识,我们就可以动手创建一个新流程了。在Modeler中点击“File” -> “New” -> “BPMN Diagram”,一个包含开始和结束事件的空白画布就准备好了。
2. 五分钟绘制带分支的审批流程图
现在,我们以“采购申请审批”为例,目标是设计一个流程:提交申请后,由经理审批;如果经理同意,流程结束;如果拒绝,则流转到一个“申请驳回”的节点,通知申请人。
2.1 拖拽与连接核心节点
首先,从左侧面板将元素拖到画布上:
- 保留自带的“Start Event”。
- 拖入一个 “User Task”,放在开始事件右侧。双击它,将名称改为“经理审批”。
- 拖入一个 “Exclusive Gateway”(排他网关),放在“经理审批”右侧。
- 拖入两个 “End Event”,放在网关的右下方和右上方。
- 再拖入一个 “User Task”,放在网关右侧、其中一个结束事件的上方,命名为“申请驳回”。
接下来,用鼠标从元素的边缘拖出箭头,连接它们:
Start Event→经理审批经理审批→Exclusive GatewayExclusive Gateway→End Event (同意)//这条线代表同意分支Exclusive Gateway→申请驳回//这条线代表拒绝分支申请驳回→End Event (拒绝)
你的画布现在应该类似这样:
[Start] --> [经理审批] --> <网关>
|
|--(同意)--> [End]
|
|--(拒绝)--> [申请驳回] --> [End]
2.2 配置审批人与条件表达式
关键步骤来了,我们需要告诉Camunda:“经理审批”这个任务由谁处理,以及网关如何做决策。
配置审批人:
点击“经理审批”用户任务,右侧属性面板会展开。找到“Assignee”这一项。这里你可以直接填写一个静态的用户ID,比如 zhangsan。但在实际项目中,审批人往往是动态的,比如根据申请部门决定。这时,我们可以使用 EL表达式 从流程变量中获取。例如,填入 ${applicantDeptManager},引擎会在任务创建时,自动查找名为“applicantDeptManager”的流程变量,并将其值作为任务处理人。
配置网关条件: 点击从网关指向“同意”分支的箭头(顺序流),在右侧属性面板找到“Condition”部分。
- 将“Type”下拉菜单选为 “Expression”。
- 在“Condition Expression”输入框中,填入
${approve == true}。这里的approve就是我们预设的一个流程变量,类型为布尔值(Boolean),表示审批结果。
接着,点击从网关指向“申请驳回”的箭头,用同样的方法设置条件为 ${approve == false} 或者更简洁的 ${!approve}。
注意:条件表达式支持丰富的逻辑,例如你可以设置
${amount > 5000}来判断金额,实现动态路由。表达式中使用的变量,都需要在流程启动或执行过程中被设置。
2.3 部署流程定义
设计完成后,点击左下角的火箭图标(Deploy)进行部署。Modeler会提示你连接到一个Camunda引擎。如果你本地已经通过Spring Boot等方式启动了Camunda(通常默认端口是8080),输入地址 http://localhost:8080/engine-rest 即可。部署成功后,这个流程定义就发布到了引擎中,可以被应用程序调用了。
至此,一个具备基本分支逻辑的审批流程图就在五分钟内完成了。但这只是“骨架”,要让流程真正“动”起来,并执行一些业务逻辑,我们还需要代码。
3. 集成Java服务:让流程拥有“大脑”
图形化设计定义了流程的路径和规则,而每个节点具体要做什么事,则需要通过Java代码(或其他方式)来实现。Camunda通过“委托”(Delegate)机制,将流程节点与我们的业务代码解耦。
3.1 实现JavaDelegate接口
最常见的集成方式是实现 JavaDelegate 接口。我们创建一个服务类,在审批完成后(或者任何需要自动处理的节点)执行一些业务逻辑,比如发送通知、更新业务状态。
package com.yourcompany.workflow.delegate;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("postApproveService") // 赋予一个Bean名称,用于在流程中引用
public class PostApproveServiceDelegate implements JavaDelegate {
private static final Logger LOG = LoggerFactory.getLogger(PostApproveServiceDelegate.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
// 1. 获取流程变量
Boolean isApproved = (Boolean) execution.getVariable("approve");
String applicant = (String) execution.getVariable("applicantName");
String processInstanceId = execution.getProcessInstanceId();
LOG.info("流程实例 [{}] 的审批已完成。申请人:{}, 审批结果:{}",
processInstanceId, applicant, isApproved ? "通过" : "拒绝");
// 2. 根据审批结果执行不同的业务逻辑
if (Boolean.TRUE.equals(isApproved)) {
// 审批通过:更新订单状态为“已批准”,可能触发后续操作
updateOrderStatus(processInstanceId, "APPROVED");
LOG.info("已处理审批通过后的业务逻辑。");
} else {
// 审批拒绝:发送驳回通知给申请人
sendRejectionNotification(applicant, "您的申请已被驳回,请查收邮件。");
LOG.info("已发送驳回通知。");
}
// 3. 可以设置新的流程变量,供后续节点使用
execution.setVariable("processCompleteTime", new Date());
}
private void updateOrderStatus(String orderId, String status) {
// 这里调用你的业务服务,更新数据库等
// orderService.updateStatus(orderId, status);
}
private void sendRejectionNotification(String toUser, String message) {
// 这里集成邮件、消息推送等服务
// notificationService.sendEmail(toUser, "申请驳回通知", message);
}
}
这段代码做了几件事:获取流程上下文中的变量、根据变量执行业务逻辑、记录日志、甚至可以设置新的变量。DelegateExecution 对象是核心,它提供了访问和操作当前流程实例全部信息的能力。
3.2 在流程中绑定服务任务
光有代码不行,我们得告诉流程在哪个节点去调用它。回到Camunda Modeler,在“经理审批”用户任务和结束事件之间,我们可以插入一个 “Service Task”(服务任务)。
- 拖入一个“Service Task”,将其放置在“经理审批”和“排他网关”之间,命名为“审批后处理”。
- 选中这个服务任务,在右侧属性面板的“Implementation”部分,将“Type”选为“Delegate Expression”。
- 在“Delegate Expression”输入框中,填入
${postApproveService}。这个值必须与我们Java代码中@Component注解里定义的Bean名称完全一致。
这样,当“经理审批”任务被人完成(点击同意或拒绝)后,流程引擎会自动调用我们编写的 PostApproveServiceDelegate.execute() 方法。这种设计实现了业务逻辑与流程控制的完美分离。
4. 启动与测试:让流程真正跑起来
流程部署了,代码也写好了,最后一步就是启动一个流程实例并进行测试。这里我们通过编写一个简单的REST API控制器来模拟。
4.1 通过API启动流程实例
在Spring Boot应用中,我们可以注入Camunda的 RuntimeService 来启动流程。
package com.yourcompany.workflow.controller;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ProcessStartController {
@Autowired
private RuntimeService runtimeService;
@PostMapping("/api/process/purchase/start")
public Map<String, Object> startPurchaseProcess(@RequestBody StartRequest request) {
// 构建流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("applicantName", request.getApplicantName());
variables.put("applicantDeptManager", request.getDeptManagerId()); // 动态指定审批人
variables.put("amount", request.getAmount());
// approve变量初始为null,由审批人完成任务时设置
// 通过流程定义的Key来启动实例
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"PurchaseApprovalProcess", // 这是在Modeler中设置的流程ID
variables
);
Map<String, Object> response = new HashMap<>();
response.put("processInstanceId", instance.getId());
response.put("processDefinitionId", instance.getProcessDefinitionId());
response.put("message", "采购审批流程已启动");
return response;
}
// 简单的请求体
public static class StartRequest {
private String applicantName;
private String deptManagerId;
private Double amount;
// getters and setters...
}
}
启动流程时,我们传入了申请人、审批人、金额等变量。注意,approve 变量此时还未设置,它将在审批任务被完成时由前端或另一个接口传入。
4.2 查询与完成任务
流程启动后,“经理审批”任务会生成。审批人(或系统)需要查询待办任务并完成它。
# 1. 查询待办任务 (通常由任务列表接口完成)
# 假设我们通过管理后台或另一个API获取到任务ID为 "task-123"
# 2. 完成任务并设置审批结果
curl -X POST http://localhost:8080/engine-rest/task/task-123/complete \
-H "Content-Type: application/json" \
-d '{
"variables": {
"approve": {
"value": true,
"type": "Boolean"
}
}
}'
这个REST调用会完成ID为“task-123”的任务,并将流程变量 approve 设置为 true。引擎随后会沿着我们设置的条件表达式路径(${approve == true}),将流程推进到“同意”分支,并触发我们绑定的服务任务 PostApproveServiceDelegate。
4.3 监控与调试
Camunda自带了一个功能强大的Web管理界面——Cockpit。启动应用后,访问 http://localhost:8080/camunda/app/cockpit/,用默认账号(如demo/demo)登录。在这里你可以:
- 查看流程实例:实时看到流程运行到了哪个节点。
- 检查流程变量:确认每一步设置的变量值是否正确。
- 查看历史记录:审计所有流程活动的日志。
- 手动干预:在开发测试时,可以手动修改变量或跳转节点,这对于调试复杂流程异常有用。
我习惯在开发复杂流程时,一边跑测试用例,一边开着Cockpit页面观察流程的推进和变量变化,很多逻辑问题都能直观地定位。
5. 进阶技巧与避坑指南
掌握了基础操作后,我们可以让流程设计得更健壮、更智能。这里分享几个实战中高频用到的技巧和容易踩的坑。
5.1 动态任务分配与候选人组
静态指定 Assignee 很不灵活。更常见的做法是使用 候选人组(Candidate Groups)。比如,将所有部门经理加入一个“dept_managers”组。在用户任务的属性中,不填 Assignee,而是填写 Candidate Groups 为 dept_managers。这样,该组内的任何用户都能在任务列表中看到并“认领”这个任务,认领后该用户自动成为 Assignee。这实现了任务的抢占式分配。
// 在启动流程时,动态计算并设置候选人组
variables.put("approvalGroup", "dept_managers"); // 或者根据业务规则计算出的组名
然后在Modeler中,将用户任务的 Candidate Groups 设置为 ${approvalGroup}。
5.2 使用监听器(Listener)实现横切关注点
除了服务任务,监听器(Listener) 是另一种更轻量、更灵活的集成方式。它可以在任务或事件的特定生命周期点(如创建时、完成时)触发一段逻辑。
- 执行监听器(Execution Listener):附加在流程活动(任务、网关)或顺序流上。例如,在流程开始时,自动初始化一些系统变量。
- 任务监听器(Task Listener):附加在用户任务上,事件更丰富,如
create(任务创建)、assignment(任务分配)、complete(任务完成)。
注意:监听器适合执行与核心业务逻辑无关的“辅助”操作,如发送通知、记录审计日志、更新统计信息。如果把复杂的业务逻辑放在监听器里,会降低流程的可读性和可维护性。
5.3 流程版本管理与数据库表结构
当你修改流程图并重新部署时,Camunda不会覆盖旧的流程定义,而是会创建一个新版本。旧版本启动的流程实例会继续按照原定义执行,新启动的实例则会使用新版本。这实现了流程的无中断升级。
了解Camunda的数据库表结构对排查问题很有帮助。核心表主要分几类:
ACT_RE_*(REpository): 存储静态的部署信息,如流程定义。ACT_RU_*(RUntime): 存储运行时的数据,如正在执行的任务、流程变量。流程实例结束后,相关记录会被删除。ACT_HI_*(HIstory): 存储历史数据,所有运行过的实例、任务、变量变更都有记录。ACT_ID_*(IDentity): 存储用户、组等身份信息。
当发现任务卡住或变量不对时,直接查询 ACT_RU_TASK 和 ACT_RU_VARIABLE 表往往能快速找到根源。
5.4 常见问题排查
- 任务找不到或无法完成:检查任务的
Assignee或Candidate Groups是否与当前操作用户匹配。检查流程实例是否已挂起或结束。 - 网关条件不生效:确保条件表达式中引用的变量已正确设置,且类型匹配。使用Cockpit查看流程实例的变量值。
- JavaDelegate未执行:首先确认服务任务的
Delegate Expression是否与Spring容器中的Bean名称一致。检查应用日志是否有异常抛出。确保该类已被Spring组件扫描到。 - 流程变量为null:变量作用域要搞清楚。在任务完成API中设置的变量,默认是任务局部变量还是流程变量?通常需要使用
withVariablesInReturn()或明确指定作用域。
流程引擎的引入,初期会带来一定的学习成本,但一旦跑通,对于流程频繁变更的业务来说,其带来的灵活性和可维护性提升是巨大的。从画出一个简单的审批流,到结合业务实现动态路由、会签或签、超时处理,每一步的扩展都有清晰的路径。
&spm=1001.2101.3001.5002&articleId=153769163&d=1&t=3&u=62166f59b28e4a96a924ec4585f7cd54)
2165

被折叠的 条评论
为什么被折叠?



