Camunda Modeler实战:5分钟搞定审批流程设计(附Java回调代码)

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 拖拽与连接核心节点

首先,从左侧面板将元素拖到画布上:

  1. 保留自带的“Start Event”。
  2. 拖入一个 “User Task”,放在开始事件右侧。双击它,将名称改为“经理审批”。
  3. 拖入一个 “Exclusive Gateway”(排他网关),放在“经理审批”右侧。
  4. 拖入两个 “End Event”,放在网关的右下方和右上方。
  5. 再拖入一个 “User Task”,放在网关右侧、其中一个结束事件的上方,命名为“申请驳回”。

接下来,用鼠标从元素的边缘拖出箭头,连接它们:

  • Start Event经理审批
  • 经理审批Exclusive Gateway
  • Exclusive GatewayEnd Event (同意) //这条线代表同意分支
  • Exclusive Gateway申请驳回 //这条线代表拒绝分支
  • 申请驳回End Event (拒绝)

你的画布现在应该类似这样:

[Start] --> [经理审批] --> <网关>
                             |
                             |--(同意)--> [End]
                             |
                             |--(拒绝)--> [申请驳回] --> [End]

2.2 配置审批人与条件表达式

关键步骤来了,我们需要告诉Camunda:“经理审批”这个任务由谁处理,以及网关如何做决策。

配置审批人: 点击“经理审批”用户任务,右侧属性面板会展开。找到“Assignee”这一项。这里你可以直接填写一个静态的用户ID,比如 zhangsan。但在实际项目中,审批人往往是动态的,比如根据申请部门决定。这时,我们可以使用 EL表达式 从流程变量中获取。例如,填入 ${applicantDeptManager},引擎会在任务创建时,自动查找名为“applicantDeptManager”的流程变量,并将其值作为任务处理人。

配置网关条件: 点击从网关指向“同意”分支的箭头(顺序流),在右侧属性面板找到“Condition”部分。

  1. 将“Type”下拉菜单选为 “Expression”
  2. 在“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”(服务任务)。

  1. 拖入一个“Service Task”,将其放置在“经理审批”和“排他网关”之间,命名为“审批后处理”。
  2. 选中这个服务任务,在右侧属性面板的“Implementation”部分,将“Type”选为“Delegate Expression”。
  3. 在“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 Groupsdept_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_TASKACT_RU_VARIABLE 表往往能快速找到根源。

5.4 常见问题排查

  1. 任务找不到或无法完成:检查任务的 AssigneeCandidate Groups 是否与当前操作用户匹配。检查流程实例是否已挂起或结束。
  2. 网关条件不生效:确保条件表达式中引用的变量已正确设置,且类型匹配。使用Cockpit查看流程实例的变量值。
  3. JavaDelegate未执行:首先确认服务任务的 Delegate Expression 是否与Spring容器中的Bean名称一致。检查应用日志是否有异常抛出。确保该类已被Spring组件扫描到。
  4. 流程变量为null:变量作用域要搞清楚。在任务完成API中设置的变量,默认是任务局部变量还是流程变量?通常需要使用 withVariablesInReturn() 或明确指定作用域。

流程引擎的引入,初期会带来一定的学习成本,但一旦跑通,对于流程频繁变更的业务来说,其带来的灵活性和可维护性提升是巨大的。从画出一个简单的审批流,到结合业务实现动态路由、会签或签、超时处理,每一步的扩展都有清晰的路径。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值