Article

工作流 Camunda

更新于:2026-07-15

第一章:Camunda 概述与核心概念

1.1 什么是 Camunda?

概念名称说明注意事项
Camunda Platform开源的流程自动化平台,用于建模、执行和监控业务流程(BPM)和决策流程(DMN)。支持 BPMN 2.0、DMN 1.3 和 CMMN 1.1 标准。Camunda 不仅是流程引擎,更是一套完整的开发与运维工具链,适用于微服务架构。
开源协议Camunda Platform 社区版基于 Apache License 2.0 开源,企业版提供额外功能和商业支持。若用于商业项目,需注意社区版与企业版的功能差异,如集群高可用、外部任务监控等。
核心能力支持流程定义、流程执行、任务管理、历史数据追踪、决策自动化(DMN)、REST API 集成等。强调”嵌入式”设计,可作为库集成到 Java 应用中,而非独立部署的黑盒系统。
适用场景金融审批流、订单处理、IT 运维自动化、医疗流程管理、工作流引擎替代等。适合需要高度定制化、与代码深度集成的流程自动化场景,而非纯低代码拖拽平台。
流程驱动架构Camunda 倡导以流程为中心的系统设计,将业务逻辑与流程控制分离,提升可维护性和可视化程度。在微服务中常作为”流程协调者”(Orchestrator),通过异步任务解耦服务。

1.2 BPMN 2.0 核心概念介绍

概念名称说明注意事项
BPMN 2.0(Business Process Model and Notation)业务流程建模与标注的标准规范,由 OMG 组织制定。使用图形化符号描述业务流程的执行逻辑。是 Camunda 流程定义的基础,所有 .bpmn 文件均遵循此标准。
流程(Process)一组有序执行的活动(Activities)和事件(Events),表示一个完整的业务流程。分为”可执行流程”和”协作流程”。Camunda 只执行”可执行流程”,协作流程用于跨组织建模,不可直接运行。
事件(Event)表示流程中发生的事情,如开始、结束、中断等。分为开始事件、中间事件、结束事件。事件用圆圈表示,类型通过图标区分(如时钟=定时,闪电=错误)。
活动(Activity)流程中的工作单元,如用户任务、服务任务、子流程等。用圆角矩形表示。活动是流程执行的核心,决定”做什么”。
网关(Gateway)控制流程分支与合并的逻辑节点,如排他网关、并行网关、包容网关等。用菱形表示。网关不执行工作,仅控制流程走向,需正确配置条件表达式。
流(Sequence Flow)连接流程元素的有向线,表示执行顺序。用实线箭头表示。只有在网关或事件后才可分叉,普通任务后不能直接多出流向。
池(Pool)与泳道(Lane)池表示参与流程的独立实体(如部门、系统),泳道在池内划分职责区域。多池图用于跨组织流程建模,单池通常用于系统内部流程。
扩展属性(Extension Elements)BPMN 标准允许通过扩展属性添加自定义配置,如 camunda:delegateExpressioncamunda:formKeyCamunda 特有配置需使用 camunda 命名空间,否则不会生效。

1.3 Camunda 架构组成(引擎、REST API、Web Apps)

组件名称说明注意事项
流程引擎(Process Engine)Camunda 的核心,负责解析 BPMN 流程定义、执行流程实例、管理任务、处理事件等。基于 Java 实现,可嵌入 Spring Boot 应用。引擎是无状态的,状态保存在数据库中,支持集群部署。
数据库(Database)存储流程定义、流程实例、任务、变量、历史数据等。支持 H2、MySQL、PostgreSQL、Oracle、SQL Server 等。生产环境必须使用生产级数据库,避免使用 H2。
REST API提供 HTTP 接口,用于远程操作流程引擎,如启动流程、查询任务、完成任务等。默认路径 /engine-rest可跨语言调用,适合前端、移动端或非 Java 系统集成。
Web 应用套件(Web Apps)包括 Cockpit(流程监控)、Tasklist(任务处理)、Admin(用户管理)三个 Web 界面。可独立部署或与引擎共部署,适合运维和业务人员使用。
外部任务(External Task)允许将服务任务交由外部 Worker(如 Python、Node.js 服务)执行,实现跨语言集成。通过轮询机制拉取任务,需合理配置重试和超时策略。
Connectors内置的轻量级集成组件,用于调用 HTTP、Kafka、Email 等服务,无需编写 Java 代码。通过 BPMN 元素配置,适合简单集成场景。
决策引擎(DMN)支持 DMN 1.3 标准,用于建模和执行业务规则决策表。可独立调用或与流程结合使用。常用于审批规则、定价策略等场景。

1.4 Camunda 与其他流程引擎的对比

对比项CamundaActivitiFlowableJBPM备注
开源背景由 Activiti 核心团队分裂后创建,专注企业级流程自动化。Alfresco 公司发起,早期流行,后发展缓慢。从 Activiti 分支出,功能丰富,社区活跃。Red Hat 推出,集成于 JBoss 生态,强调规则与复杂流程。Camunda 和 Flowable 技术同源,均源自 Activiti 5。
核心标准支持BPMN 2.0、DMN 1.3、CMMN 1.1BPMN 2.0(DMN 支持较弱)BPMN 2.0、DMN 1.3、CMMN 1.1BPMN 2.0、DMN、规则引擎(Drools)深度集成Camunda 对 DMN 和 CMMN 支持最成熟。
架构设计轻量级、嵌入式、适合微服务可嵌入,但企业版功能更强类似 Camunda,支持多种部署模式重量级,依赖 JBoss,适合传统企业Camunda 更适合云原生和 Spring 生态。
Spring Boot 集成官方提供 camunda-bpm-spring-boot-starter,集成极简。有社区支持,但官方支持较弱。提供 flowable-spring-boot-starter,集成良好。支持 Spring,但配置较复杂。Camunda 的 Spring Boot 集成体验最佳。
Web 管理界面Cockpit(监控)、Tasklist(任务)、Admin(权限),功能完整。Activiti App 提供类似功能,但更新慢。Flowable UI 提供建模与任务处理。Business Central 提供全流程管理,功能强大但复杂。Camunda 的 Web 工具简洁实用,适合开发者和运维。
社区与文档文档清晰,社区活跃,官方教程丰富。社区逐渐萎缩,文档陈旧。文档良好,社区活跃。文档专业,但学习曲线陡峭。Camunda 文档被广泛认为是最友好的。
企业支持提供企业版,支持集群、高级监控、SLA 保障。Alfresco 提供商业支持。Digital AI 提供企业版支持。Red Hat 提供商业支持。企业项目建议评估商业支持需求。

第二章:环境搭建与快速入门

2.1 安装 Camunda Platform(独立版)

方法/操作语法/命令用途示例注意事项
下载 Camunda Run访问 Camunda Download 页面,选择 Camunda Platform Run获取独立可运行的 Camunda 发行版,无需应用服务器。camunda-platform-run-7.22.0.zip建议选择最新稳定版(如 7.22+),Run 版适合快速体验和开发测试。
解压安装包unzip camunda-platform-run-*.zip 或使用解压工具解压运行环境到本地目录。解压至 D:\camunda\run~/camunda/run路径避免中文和空格。
启动 Camunda Run进入 camunda-platform-run-* 目录,执行 ./start-camunda.bat(Windows)或 ./start-camunda.sh(Linux/macOS)启动内嵌的 Spring Boot 服务,自动运行 Camunda 引擎。控制台输出 Started CamundaApplication in 8.5 seconds 表示成功首次启动会自动初始化数据库(H2),后续启动会复用。
访问 Web Apps浏览器打开 http://localhost:8080查看 Camunda 提供的 Web 应用入口页。页面显示 “Camunda BPM Run” 和三个链接:Cockpit, Tasklist, Admin默认端口为 8080,可通过 application.yml 修改。
访问 Tasklisthttp://localhost:8080/camunda/app/tasklist/default/登录并处理用户任务。默认用户:demo / demodemo 用户拥有所有权限,生产环境需创建自定义用户。
访问 Cockpithttp://localhost:8080/camunda/app/cockpit/default/查看流程定义、实例、历史数据等。可查看部署的流程图和运行状态用于监控和调试流程执行。
配置数据库(可选)修改 configuration/root/application.yml将默认 H2 数据库替换为 MySQL/PostgreSQL 等生产级数据库。需提前创建数据库,并导入 Camunda MySQL DDL 脚本(位于 sql/ 目录)修改后需重启服务,确保数据库驱动在 lib/ 目录。
camunda:
  bpm:
    database:
      type: persistent
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/camunda
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

2.2 集成 Camunda 到 Spring Boot 项目

方法/操作语法/命令用途示例注意事项
创建 Spring Boot 项目使用 Spring Initializr初始化标准 Spring Boot 工程。选择 Java 17+、Spring Boot 3.x、Web、Lombok 等推荐使用 Maven 或 Gradle 构建。
添加 Camunda Starter 依赖(Maven)引入 Camunda Spring Boot 启动器,自动配置流程引擎。版本需与 Camunda Platform 一致使用 spring-boot-starter-jpaspring-boot-starter-web 作为基础。
<dependency>
  <groupId>org.camunda.bpm.springboot</groupId>
  <artifactId>camunda-bpm-spring-boot-starter</artifactId>
  <version>7.22.0</version>
</dependency>

| 启用流程引擎 | 在主应用类上添加 @EnableProcessApplication | 标识该应用为 Camunda 流程应用,启用流程引擎自动装配。 | @EnableProcessApplication 是必需的 | 若使用 DMN 或 CMMN,可添加 @EnableDecisionEngine 等。 |

@SpringBootApplication
@EnableProcessApplication
public class MyApplication { ... }

| 配置 application.yml | — | 配置流程引擎行为,如历史级别、作业执行等。 | history-level: full 记录所有历史数据,适合开发 | 生产环境建议使用 auditactivity 以提升性能。 |

camunda:
  bpm:
    job-execution-enabled: true
    history-level: full
    database:
      type: strong

| 自定义流程引擎配置类 | — | 完全控制流程引擎配置,适用于复杂场景。 | 可设置事务管理器、作业执行器、历史级别等 | 若使用 camunda-bpm-spring-boot-starter,通常无需手动配置。 |

@Configuration
public class CamundaConfig {
  @Bean
  public SpringProcessEngineConfiguration processEngineConfiguration(DataSource dataSource) {
    SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
    config.setDataSource(dataSource);
    config.setTransactionManager(...);
    config.setDatabaseSchemaUpdate("true");
    config.setJobExecutorActivate(true);
    return config;
  }
}

| 流程定义自动部署 | 将 .bpmn 文件放入 src/main/resources | 启动时自动扫描并部署流程定义。 | 文件名如 process1.bpmn | 支持 BPMN、DMN、Form 文件自动部署。 |

2.3 部署第一个 BPMN 流程定义

方法/操作语法/命令用途示例注意事项
创建 BPMN 文件使用 Camunda Modeler 或其他工具创建 .bpmn 文件定义业务流程的图形化模型。示例流程:开始 → 用户任务 → 结束文件必须符合 BPMN 2.0 标准。
放置流程文件my-process.bpmn 放入 src/main/resources触发自动部署机制。路径可嵌套,如 bpmn/order-process.bpmn文件名建议语义化,避免空格。
使用 RepositoryService 部署(编程方式)手动控制流程部署,适用于动态部署场景。可部署多个资源,支持 ZIP 包部署部署后生成 Deployment 和 ProcessDefinition 记录。
@Autowired
private RepositoryService repositoryService;

public void deployProcess() {
  Deployment deployment = repositoryService
    .createDeployment()
    .addClasspathResource("my-process.bpmn")
    .name("订单审批流程")
    .deploy();
  System.out.println("部署ID: " + deployment.getId());
}

| 查看部署结果 | 访问 Cockpit → Deployments | 验证流程是否成功部署。 | 显示部署名称、时间、包含的流程定义 | 若部署失败,查看日志中的 BPMN 解析错误。 | | 查询流程定义 | — | 获取已部署的流程定义列表。 | 可按 Key、Name、Version 等条件查询 | processDefinitionKey 是流程文件中 id 属性。 |

List<ProcessDefinition> defs = repositoryService
  .createProcessDefinitionQuery()
  .latestVersion()
  .list();

| 挂起/激活流程定义 | — | 控制流程是否可启动新实例。 | 挂起后无法启动新实例,但已有实例继续运行 | 用于灰度发布或紧急下线。 |

repositoryService.suspendProcessDefinitionByKey("myProcess");
repositoryService.activateProcessDefinitionByKey("myProcess");

2.4 启动流程实例并查看任务

方法/操作语法/命令用途示例注意事项
使用 RuntimeService 启动流程通过流程定义 Key 启动一个新流程实例,并传入变量。myProcess 是 BPMN 文件中 <process id="myProcess">若流程有启动表单,也可在此传入表单数据。
@Autowired
private RuntimeService runtimeService;

public void startProcess() {
  Map<String, Object> variables = new HashMap<>();
  variables.put("applicant", "张三");
  variables.put("amount", 5000);

  ProcessInstance instance = runtimeService
    .startProcessInstanceByKey("myProcess", variables);

  System.out.println("流程实例ID: " + instance.getId());
  System.out.println("流程定义ID: " + instance.getProcessDefinitionId());
}

| 通过 REST API 启动流程 | POST /engine-rest/process-definition/key/{key}/start | 从外部系统启动流程,适合前后端分离架构。 | 使用 Postman 或 curl 测试 | 需确保 REST API 已启用且网络可达。 |

{ "variables": { "amount": { "value": 5000 } } }

| 查询用户任务 | — | 获取指定用户(如 kermit)的待办任务列表。 | 可按候选人、候选组、流程实例 ID 等条件查询 | 任务状态为”未完成”时才可查询到。 |

@Autowired
private TaskService taskService;

List<Task> tasks = taskService
  .createTaskQuery()
  .taskAssignee("kermit")
  .list();

| 查看 Tasklist 中的任务 | 登录 http://localhost:8080/camunda/app/tasklist/ | 通过 Web 界面查看和处理任务。 | demo 用户登录后可见分配给自己的任务 | 任务标题、表单数据会自动显示。 | | 完成用户任务 | — | 完成当前任务,并传递输出变量给后续流程。 | taskService.claim("taskId", "kermit") 可先领取任务 | 若任务有表单,需先提交表单数据。 |

Map<String, Object> taskVariables = new HashMap<>();
taskVariables.put("approved", true);

taskService.complete("taskId", taskVariables);

| 查看流程实例状态 | 访问 Cockpit → Processes → 选择流程定义 → Instances | 图形化查看流程实例的当前执行路径。 | 高亮显示当前活动节点 | 可查看变量、历史活动、日志等。 |


第三章:BPMN 2.0 流程设计基础

3.1 开始事件(Start Event)与结束事件(End Event)

BPMN 元素配置方式 / XML 示例用途示例说明注意事项
无开始事件(None Start Event)<startEvent id="start" name="开始" />流程的入口点,不依赖外部触发,流程启动即激活。最常用类型,适用于用户主动发起的流程(如”提交申请”)。一个流程只能有一个无开始事件。
定时开始事件(Timer Start Event)按照指定时间周期自动启动流程实例。每天上午9点启动一次(ISO 8601 格式)。仅在流程定义激活状态下生效;需确保 Job Executor 正常运行。
<startEvent id="timerStart">
  <timerEventDefinition>
    <timeCycle>R/2025-10-18T09:00:00+08:00/P1D</timeCycle>
  </timerEventDefinition>
</startEvent>

| 消息开始事件(Message Start Event) | — | 接收外部消息后启动流程。 | 当系统发送名为 NewOrderMessage 的消息时,触发流程。 | 消息名需全局唯一;常用于跨系统集成。 |

<startEvent id="msgStart">
  <messageEventDefinition messageRef="NewOrderMessage" />
</startEvent>

| 错误开始事件(Error Start Event) | — | 用于事件子流程,捕获父流程抛出的错误。 | 不能作为主流程的开始事件,仅用于事件子流程。 | errorRef 需与错误边界事件或抛出错误匹配。 |

<startEvent id="errorStart">
  <errorEventDefinition errorRef="myError" />
</startEvent>

| 无结束事件(None End Event) | <endEvent id="end" name="结束" /> | 表示流程正常终止,所有执行路径到达此节点即结束。 | 流程执行成功完成。 | 可有多个结束事件,表示不同成功路径。 | | 错误结束事件(Error End Event) | — | 抛出一个错误,由上游的错误边界事件捕获。 | 用于服务任务校验失败时抛出特定错误。 | 不会终止整个流程,而是触发异常处理路径。 |

<endEvent id="errorEnd">
  <errorEventDefinition errorRef="validationFailed" />
</endEvent>

| 终止结束事件(Terminate End Event) | — | 立即终止当前流程实例及其所有分支。 | 在并行网关后使用,强制结束所有路径。 | 使用需谨慎,可能导致数据不一致。 |

<endEvent id="terminateEnd">
  <terminateEventDefinition />
</endEvent>

3.2 用户任务(User Task)与任务分配

分配方式配置方式 / XML 示例用途示例说明注意事项
直接分配(Assignee)<userTask id="task1" name="审批任务" camunda:assignee="kermit" />将任务直接分配给指定用户。kermit 用户登录 Tasklist 后可在”我的任务”中看到。用户需在 Camunda Identity 中存在。
候选用户(Candidate Users)<userTask id="task2" camunda:candidateUsers="kermit,gonzo" />多个用户均可领取并处理该任务。任一候选用户可”领取”任务,领取后变为 assignee。适合小组协作场景。
候选组(Candidate Groups)<userTask id="task3" camunda:candidateGroups="management,sales" />将任务分配给一组用户,组内成员可领取。需提前在 Admin 中创建组并分配用户。更适合角色化任务分配(如”财务组”)。
使用表达式动态分配<userTask id="task4" camunda:assignee="${initiator}" camunda:candidateGroups="${department}Managers" />根据流程变量动态决定任务分配。initiator 是流程启动时传入的变量。表达式在任务创建时求值,支持 UEL。
任务表单(Form Key)<userTask id="task5" camunda:formKey="app:components:approvalForm" />关联外部表单(如 Angular、React 组件)或内嵌表单。在 Tasklist 中显示自定义表单界面。app: 表示 Camunda Tasklist 的前端组件。
任务监听器(Task Listener)在任务生命周期事件(create、assignee、complete)时执行 Java 逻辑。可用于发送任务通知邮件。实现 TaskListener 接口。
<userTask id="task6">
  <extensionElements>
    <camunda:taskListener event="create" class="com.example.TaskCreateListener" />
  </extensionElements>
</userTask>

3.3 排他网关(Exclusive Gateway)与流程分支

配置项配置方式 / XML 示例用途示例说明注意事项
排他网关定义<exclusiveGateway id="decision" name="金额判断" />根据条件表达式选择唯一一条流出路径。类似编程中的 if-else 结构。所有流出路径必须有 conditionExpression 或默认路径。
条件表达式(UEL)定义路径执行条件,使用 Unified EL 语法。当流程变量 amount 大于 10000 时走此路径。表达式返回 truefalse,第一个为 true 的路径被选中。
<sequenceFlow id="toHigh" sourceRef="decision" targetRef="highApproval">
  <conditionExpression xsi:type="tFormalExpression">
    ${amount > 10000}
  </conditionExpression>
</sequenceFlow>

| 默认路径(Default Flow) | — | 当所有条件都不满足时,执行的默认路径。 | default 属性指向 sequenceFlow 的 id。 |

<exclusiveGateway id="decision" default="toLow" />
<sequenceFlow id="toLow" sourceRef="..." targetRef="lowApproval" />

| 合并路径 | <exclusiveGateway id="merge" /> | 多条分支在此合并,继续后续流程。 | 不需要条件,仅用于合并执行流。 | 排他网关既可分支也可合并,但语义必须清晰。 |

3.4 并行网关(Parallel Gateway)控制并发

配置项配置方式 / XML 示例用途示例说明注意事项
并行分支网关<parallelGateway id="fork" />将流程同时拆分为多个并行执行路径。两个服务任务同时执行。所有流出路径无条件执行。
并行合并网关<parallelGateway id="join" />等待所有并发分支全部完成后,再继续后续流程。所有分支到达 join 后,流程继续。必须与 fork 成对使用,否则可能导致死锁。
并行路径示例实现两个任务并行执行。如”发送邮件”和”生成报告”可并行。并行任务共享流程变量,注意并发修改问题。
<sequenceFlow sourceRef="fork" targetRef="taskA" />
<sequenceFlow sourceRef="fork" targetRef="taskB" />
<sequenceFlow sourceRef="taskA" targetRef="join" />
<sequenceFlow sourceRef="taskB" targetRef="join" />

3.5 服务任务(Service Task)执行后台逻辑

实现方式配置方式 / XML 示例用途示例说明注意事项
Java Delegate<serviceTask id="javaTask" camunda:class="com.example.ApprovalService" />执行 Java 类中的业务逻辑,实现 JavaDelegate 接口。最常用方式,适合复杂业务逻辑。
public class ApprovalService implements JavaDelegate {
  public void execute(DelegateExecution ex) {
    String user = (String) ex.getVariable("applicant");
    ex.setVariable("result", "approved");
  }
}

| 表达式(Expression) | <serviceTask id="exprTask" camunda:expression="${emailService.sendEmail(applicant)}" camunda:resultVariable="emailResult" /> | 调用 Spring Bean 的方法。 | emailService 是 Spring 容器中的 Bean。 | 方法返回值可存入变量。 | | 委托表达式(Delegate Expression) | <serviceTask id="delTask" camunda:delegateExpression="${approvalDelegate}" /> | 动态引用 Spring Bean,更灵活。 | approvalDelegate 是 Bean 名,可动态切换实现。 | 支持策略模式。 | | 异步执行 | <serviceTask id="asyncTask" camunda:class="com.example.LongRunningTask" camunda:asyncBefore="true" /> | 将任务放入 Job Executor 队列异步执行,避免阻塞主流程。 | 适合耗时操作(如调用外部 API)。 | 需启用 Job Executor;异常处理需配置重试策略。 | | 外部任务(External Task) | <serviceTask id="extTask" camunda:type="external" camunda:topic="creditScoreCheck" /> | 将任务发布到外部 Worker 处理,支持跨语言。 | Python Worker 订阅 creditScoreCheck 主题并处理。 | 实现系统解耦,适合微服务架构。 |

3.6 脚本任务(Script Task)运行内联脚本

配置项配置方式 / XML 示例用途示例说明注意事项
脚本任务定义<scriptTask id="scriptTask" name="计算税费" scriptFormat="groovy" resource="calculateTax.groovy" />执行内联或外部脚本,支持 Groovy、JavaScript、Python(需引擎支持)。适合简单计算或变量处理。生产环境慎用,难以调试和维护。
内联脚本直接在 BPMN 中编写脚本逻辑。Groovy 脚本访问 execution 变量操作流程变量。executionDelegateExecution 对象。
<scriptTask id="inlineScript">
  <script><![CDATA[
    def tax = amount * 0.1;
    execution.setVariable("tax", tax);
  ]]></script>
</scriptTask>

| 脚本语言选择 | scriptFormat="groovy""javascript""python"(需 Jython) | 指定脚本语言。 | Groovy 性能最好,与 Java 无缝集成。 | JavaScript 在 Nashorn 引擎中运行(JDK 15+ 已弃用)。 | | 结果变量 | <scriptTask ... camunda:resultVariable="scriptResult"> | 将脚本最后一行表达式的值存入指定变量。 | return "success" → 存入 scriptResult | 类似函数返回值。 |


第四章:流程变量与表达式

4.1 流程变量(Process Variables)的作用域与生命周期

概念说明作用域生命周期注意事项
流程变量(Process Variable)在流程执行过程中存储和传递数据的键值对,用于控制流程逻辑、任务分配、条件判断等。全局作用域:默认在整个流程实例中可见。从创建开始,到流程实例结束时自动销毁(历史级别 ≥ activity 时可查询)。变量名区分大小写,建议使用小写字母和下划线(如 order_amount)。
局部变量(Local Variable)仅在特定执行路径(Execution)或任务(Task)中有效的变量。局部作用域:仅在当前执行上下文(如子流程、并行分支)中有效。创建于当前 Execution,该 Execution 结束时销毁。使用 setVariableLocal() 设置,getVariable() 仍可读取,但外部无法访问。
变量继承子执行(如子流程、并行分支)默认继承父流程的变量。子 Execution 继承父 Execution 的所有变量。继承的变量在子 Execution 中可读可写;修改后是否影响父级取决于是否为局部变量。若在子流程中使用 setVariableLocal("x", v),则不影响父流程的 x。
变量覆盖在子作用域中设置同名变量,会覆盖继承的值。局部优先:getVariable("x") 优先返回本地值。覆盖仅在局部作用域内有效,不影响父级变量。易引发逻辑错误,建议避免在子流程中覆盖关键全局变量。
变量删除可显式删除变量。删除后,getVariable("key") 返回 null删除后不可恢复,即使父级存在同名变量也不会自动继承。使用 removeVariable("key")removeVariableLocal("key")

4.2 使用 UEL 表达式(Unified EL)

表达式类型语法用途示例注意事项
Value Expression(值表达式)${expression}在流程执行时求值,常用于条件判断、任务分配。网关条件:${amount > 1000};任务分配:camunda:assignee="${initiator}"在任务创建、网关决策等运行时求值。
Method Expression(方法表达式)${bean.method(arg)}调用 Spring Bean 的方法,传递参数。camunda:expression="${emailService.send(to, subject)}"方法可返回值,用于设置结果变量。
访问流程变量${variableName}直接引用流程变量。${customerName}, ${order.total}支持嵌套属性访问(如 POJO 的 getter)。
访问 DelegateExecution${execution}在表达式中访问执行上下文对象。${execution.processInstanceId}executionDelegateExecution 实例,可用于日志或调试。
访问 Task${task}在任务相关表达式中访问任务对象。${task.assignee}, ${task.name}仅在任务监听器、表单等任务上下文中可用。
逻辑运算符and, or, not, ==, !=, >, <构建复杂条件。${amount > 1000 and department == 'IT'}优先使用 and/or/not 而非 &&/`
空值检查${variable != null}防止空指针异常。${customer != null and customer.age > 18}EL 中 null 安全,但复杂表达式仍需检查。

4.3 在任务中访问和修改流程变量

方法语法(Java)用途代码示例注意事项
获取变量execution.getVariable("key") / task.getVariable("key")从执行上下文或任务中读取变量值。String user = (String) execution.getVariable("applicant"); / Integer amount = task.getVariable("amount", Integer.class);建议使用泛型方法 getVariable(String, Class<T>) 避免类型转换异常。
设置变量execution.setVariable("key", value) / task.setVariable("key", value)修改或创建全局变量。execution.setVariable("approved", true); / task.setVariable("reviewTime", new Date());会覆盖所有作用域中的同名变量。
设置局部变量execution.setVariableLocal("key", value) / task.setVariableLocal("key", value)仅在当前 Execution 或 Task 中设置变量。execution.setVariableLocal("tempResult", result);不影响父流程或其他并行分支的变量。
删除变量execution.removeVariable("key") / task.removeVariable("key")删除全局变量。execution.removeVariable("tempData");删除后,getVariable 返回 null
删除局部变量execution.removeVariableLocal("key") / task.removeVariableLocal("key")仅删除本地作用域的变量。execution.removeVariableLocal("scratch");即使父级有同名变量,删除局部变量不影响父级。
批量操作变量execution.getVariables() / execution.setVariables(map)获取或设置多个变量。Map<String, Object> vars = execution.getVariables(); vars.put("status", "processed"); execution.setVariables(vars);适合批量传递数据,但注意性能开销。

4.4 变量类型与序列化机制

变量类型存储方式序列化机制示例注意事项
基本类型(String, Integer, Boolean 等)直接存储在 ACT_RU_VARIABLE 表的 TEXT_LONG_ 字段。无需序列化,直接转换为数据库字段。"John", 1000, true性能最好,推荐优先使用。
Date存储为 TIMESTAMP 类型。自动转换为数据库时间戳。new Date()时区问题需注意,建议统一使用 UTC。
Serializable 对象存储在 BYTEARRAY_ID_ 字段,指向 ACT_GE_BYTEARRAY 表。使用 Java 原生序列化(ObjectOutputStream)。必须实现 Serializable 接口;类路径必须一致;反序列化安全风险。
public class Order implements Serializable {
  private static final long serialVersionUID = 1L;
  // fields
}

| Jackson Object(JSON) | 存储为 JSON 字符串在 TEXT_ 字段。 | 使用 Jackson 库序列化为 JSON。 | 配置 objectMapper 后自动处理 POJO。 | 需注册 JacksonObjectValueType;跨语言友好;推荐替代 Serializable。 | | 自定义类型转换器 | 通过 ValueSerializer 接口自定义序列化逻辑。 | 开发者控制序列化/反序列化过程。 | 实现 ValueSerializer<T> 并注册到引擎。 | 适用于加密、压缩、特殊格式等场景。 | | 文件/大对象 | 建议存储路径或 ID,而非直接存内容。 | 不推荐将大文件作为变量。 | 变量存 fileId="123",文件存文件系统或对象存储。 | 避免变量过大影响数据库性能和流程执行速度。 | | null 值 | TEXT_ 字段为 NULL。 | 直接表示空值。 | setVariable("note", null); | 查询时注意空值判断。 |

提示:

  • 生产环境建议使用 Jackson JSON 序列化替代 Java 原生序列化,避免类版本不兼容问题。
  • 避免在变量中存储大对象或敏感信息(如密码)。
  • 合理使用局部变量可减少全局状态污染。

第五章:Java 服务与外部集成

5.1 实现 JavaDelegate 接口编写服务任务逻辑

方法语法(Java)用途代码示例注意事项
executepublic void execute(DelegateExecution execution) throws Exception实现服务任务的核心逻辑,由流程引擎在任务执行时调用。方法必须为 public,参数为 DelegateExecution,不可重载。
public class ApprovalDelegate implements JavaDelegate {
  @Override
  public void execute(DelegateExecution execution) {
    String applicant = (String) execution.getVariable("applicant");
    boolean approved = businessRuleService.check(applicant);
    execution.setVariable("approved", approved);
  }
}

| 获取流程变量 | execution.getVariable("key") / execution.getVariable("key", Type.class) | 读取当前上下文中的变量。 | Double amount = execution.getVariable("amount", Double.class); | 建议使用泛型方法避免类型转换异常。 | | 设置流程变量 | execution.setVariable("key", value) | 写入变量,供后续流程使用。 | execution.setVariable("status", "approved"); | 变量作用域为整个流程实例。 | | 设置局部变量 | execution.setVariableLocal("key", value) | 仅在当前执行路径中有效。 | execution.setVariableLocal("tempResult", result); | 不影响父流程或其他分支。 | | 获取流程实例信息 | execution.getProcessInstanceId() / execution.getProcessDefinitionId() / execution.getCurrentActivityId() | 获取执行上下文元数据。 | log.info("Executing task for process: " + execution.getProcessInstanceId()); | 用于日志、监控或条件判断。 | | 抛出异常 | throw new BpmnError("errorCode") / throw new RuntimeException("msg") | 触发错误事件或中断流程。 | — | BpmnError 可被边界错误事件捕获;RuntimeException 导致任务失败并进入 Job 重试机制。 |

if (user == null) {
  throw new BpmnError("USER_NOT_FOUND");
}

5.2 使用 ExecutionListener 和 TaskListener

监听器类型触发事件配置方式(BPMN XML)用途示例注意事项
ExecutionListenerstart, end, take可应用于 processserviceTaskuserTask 等元素监听流程、活动(如服务任务)的执行事件。在流程开始时初始化变量或记录日志。
<process ...>
  <extensionElements>
    <camunda:executionListener
      event="start"
      class="com.example.ProcessStartListener" />
  </extensionElements>
</process>

| TaskListener | create, assignment, complete, delete | 应用于 userTask 元素 | 监听用户任务的生命周期事件。 | 任务创建时发送邮件通知负责人。 | assignment 事件在任务被领取或分配时触发。 |

<userTask id="reviewTask">
  <extensionElements>
    <camunda:taskListener
      event="create"
      class="com.example.TaskCreateNotifier" />
  </extensionElements>
</userTask>

| 使用表达式配置监听器 | 支持 class、delegateExpression、expression | — | 使用 Spring Bean 或 EL 表达式,避免编写 Java 类。 | 更灵活,适合简单逻辑。 | expression 中可使用 executiontask 等上下文对象。 |

<camunda:taskListener
  event="complete"
  expression="${notificationService.notifyCompleted(task)}" />

| 监听器执行上下文 | DelegateExecution(ExecutionListener) / DelegateTask(TaskListener) | 提供事件相关的执行或任务对象。 | 可读写变量、获取流程实例信息。 |

public void notify(DelegateTask task) {
  String assignee = task.getAssignee();
  String name = task.getName();
}

5.3 异步连续(Async Continuation)与异步任务

配置项配置方式(BPMN XML)用途示例注意事项
异步前(Async Before)<serviceTask id="longTask" camunda:class="com.example.LongRunningTask" camunda:asyncBefore="true" />将当前任务之前的流程执行异步化,任务由 Job Executor 执行。适合耗时任务,避免阻塞主线程。任务提交为 Job,状态为 created,由 Job Executor 拉取执行。
异步后(Async After)<serviceTask id="task" camunda:class="com.example.Task" camunda:asyncAfter="true" />将当前任务之后的流程继续(即后续 Sequence Flow)异步执行。任务同步执行,但后续流程由 Job Executor 触发。适用于任务执行快,但后续流程复杂或需延迟执行。
异步连续(Async Continuation)同时设置 asyncBefore="true"exclusive="false"实现非独占式异步,允许多个 Job 并发执行。提高吞吐量,避免 Job Executor 队列阻塞。exclusive="false" 表示 Job 非独占,可并行处理。
异常重试配置配置任务失败后的重试策略。R3/PT5M 表示失败后重试 3 次,每次间隔 5 分钟。若未配置,使用全局默认值(通常为 R3/PT10M)。
<serviceTask ...>
  <extensionElements>
    <camunda:failedJobRetryTimeCycle>
      R3/PT5M
    </camunda:failedJobRetryTimeCycle>
  </extensionElements>
</serviceTask>

| 查询异步任务状态 | — | 查看待执行或失败的异步任务。 | 用于监控和手动重试。 | managementService 用于管理 Job、变量、数据库等。 |

List<Job> jobs = managementService.createJobQuery()
  .processInstanceId("...")
  .list();

5.4 外部任务(External Task)与 Worker 模式

配置/方法语法/代码用途示例注意事项
BPMN 中定义外部任务<serviceTask id="creditCheck" camunda:type="external" camunda:topic="creditScoreCheck" camunda:asyncBefore="true" />声明一个由外部 Worker 处理的任务。topic 是 Worker 订阅的主题名称。必须启用 Job Executor。
创建 External Task Worker(Java)Worker 主动拉取任务并处理。使用 fetchAndLock 获取并锁定任务,防止并发处理。lockDuration(ms)决定任务被锁定的时间,需大于处理时间。
@Component
public class CreditWorker {

  @PostConstruct
  public void subscribe() {
    ExternalTaskService externalTasks = processEngine.getExternalTaskService();

    externalTasks.fetchAndLock(10, "creditWorker")
      .topic("creditScoreCheck", 10000)
      .variable("applicantId")
      .execute((handler, task) -> {
        String applicantId = (String) task.getVariable("applicantId");
        int score = creditAgency.check(applicantId);

        Map<String, Object> result = new HashMap<>();
        result.put("creditScore", score);
        handler.complete(result);
      });
  }
}

| 完成外部任务 | handler.complete(variables) | 处理成功,继续流程。 | 传递结果变量给流程。 | 可为空 handler.complete()。 | | 失败处理 | handler.handleFailure("msg", "details", 3, 5000) | 处理失败,触发重试。 | retries=3, retryTimeout=5000ms | 若重试耗尽,任务进入 failed 状态。 | | 错误处理 | handler.handleError("CUSTOM_ERROR") | 抛出业务错误,由流程中的错误边界事件捕获。 | 实现流程级异常处理。 | errorCode 需与边界事件配置匹配。 | | Worker 轮询机制 | fetchAndLock(...).execute(...) 在循环中调用 | 持续监听任务队列。 | 可使用 ScheduledExecutorService 定期拉取。 | 生产环境建议使用专用 Worker 服务(如 camunda-external-task-client)。 |

5.5 REST 服务调用(使用 Connectors 或 Feign)

方法配置方式/代码用途示例注意事项
HTTP Connector(BPMN)无需写代码,通过 BPMN 配置调用 REST API。适合简单、固定的外部调用。需启用 connectors 模块;支持 JSON 转换。
<serviceTask id="callApi"
  camunda:type="connector">
  <extensionElements>
    <camunda:connector>
      <camunda:inputOutput>
        <camunda:inputParameter name="url">https://api.example.com/users</camunda:inputParameter>
        <camunda:inputParameter name="method">POST</camunda:inputParameter>
        <camunda:inputParameter name="headers">
          <map>
            <entry key="Content-Type" value="application/json" />
          </map>
        </camunda:inputParameter>
        <camunda:inputParameter name="body">${requestBody}</camunda:inputParameter>
      </camunda:inputOutput>
      <camunda:connectorId>http-connector</camunda:connectorId>
    </camunda:connector>
  </extensionElements>
</serviceTask>

| 使用 Feign Client(Java) | — | 在 Java 代码中声明式调用 REST API。 | 类型安全,支持 Spring Cloud 生态。 | 需添加 spring-cloud-starter-openfeign 依赖。 |

@FeignClient(name = "user-service", url = "https://api.example.com")
public interface UserClient {
  @PostMapping("/users")
  User createUser(@RequestBody User user);
}

// 在 JavaDelegate 中注入使用
@Autowired
private UserClient userClient;

public void execute(DelegateExecution ex) {
  User user = ex.getVariable("user", User.class);
  User saved = userClient.createUser(user);
  ex.setVariable("savedUser", saved);
}

| 使用 WebClient(推荐) | — | 响应式非阻塞 HTTP 客户端,性能更好。 | 适用于高并发场景。 | 需处理异步结果(Mono/Flux)。 |

@Autowired
private WebClient webClient;

public Mono<User> createUser(User user) {
  return webClient.post()
    .uri("/users")
    .bodyValue(user)
    .retrieve()
    .bodyToMono(User.class);
}

| 处理响应与错误 | 在 Feign 或 WebClient 中使用 try-catch 或 onErrorResume | 捕获网络异常、4xx/5xx 错误。 | 记录日志、设置默认值或抛出 BpmnError。 | 避免因外部服务不可用导致流程长时间阻塞。 | | 超时配置(Feign) | — | 防止外部调用无限等待。 | 根据服务 SLA 合理设置。 | 超时会抛出 FeignException。 |

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000

第六章:用户任务与任务管理

6.1 任务分配:直接分配、候选用户与候选组

分配方式BPMN 配置(XML)Java API 设置用途示例说明注意事项
直接分配(Assignee)<userTask id="task1" camunda:assignee="kermit" />taskService.setAssignee("taskId", "kermit"); 或在创建时指定 task.setAssignee("kermit");将任务直接分配给指定用户,该用户拥有处理权。kermit 登录后可在”我的任务”中看到此任务。一个任务只能有一个 assignee。
候选用户(Candidate Users)<userTask id="task2" camunda:candidateUsers="kermit,grover" />taskService.addCandidateUser("taskId", "kermit"); / taskService.addCandidateUser("taskId", "grover");多个用户均可”领取”该任务,领取后变为 assignee。适合小组内任意成员可处理的场景。使用 addCandidateUser() 动态添加。
候选组(Candidate Groups)<userTask id="task3" camunda:candidateGroups="management,sales" />taskService.addCandidateGroup("taskId", "sales");将任务分配给一组用户,组内成员可领取。如”财务组”、“审批组”等角色化分配。组需在 Camunda Identity 或外部用户管理系统中存在。
动态分配(表达式)<userTask id="task4" camunda:assignee="${initiator}" camunda:candidateGroups="${department}Reviewers" />在流程执行时根据变量动态决定分配对象。流程启动时传入 initiator=alice,则任务分配给 alice。表达式在任务创建时求值。
领取任务(Claim)taskService.claim("taskId", "kermit");候选用户将任务”领取”为自己的任务。领取后 assignee 变为该用户,其他候选用户不再可见。非候选用户无法领取。
取消领取(Unclaim)taskService.setAssignee("taskId", null);将已领取的任务释放回候选池。任务重新变为”待领取”状态。assignee 设为 null 即可。

6.2 任务表单(Form Key 与 External Form)

表单类型配置方式(BPMN XML)用途示例说明注意事项
内嵌表单(Embedded Form)使用 HTML + AngularJS 编写的内嵌表单(旧版 Tasklist)。表单文件需打包在流程定义中。仅适用于 Camunda 7 的旧版 Web App。
<userTask id="task1">
  <extensionElements>
    <camunda:formKey>embedded:app/forms/approval.html</camunda:formKey>
  </extensionElements>
</userTask>

| 外部表单(External Form) | — | 通过 URL 加载外部独立的表单应用(如 React、Vue)。 | 前端可独立开发部署,技术栈自由。 | 需处理跨域问题(CORS)。 |

<userTask id="task2">
  <extensionElements>
    <camunda:formKey>http://localhost:3000/forms/expense-approval</camunda:formKey>
  </extensionElements>
</userTask>

| Camunda Tasklist 组件表单 | — | 在 Camunda Tasklist(Angular)中使用自定义 Angular 组件。 | app: 前缀表示 Tasklist 的前端组件。 | 需在 Tasklist 中注册该组件。 |

<userTask id="task3">
  <extensionElements>
    <camunda:formKey>app:components:expenseForm</camunda:formKey>
  </extensionElements>
</userTask>

| 表单字段(Form Fields) | — | 定义表单字段元数据,用于自动生成表单或验证。 | 工具可读取 formField 自动生成 UI。 | 非必需,但有助于低代码表单生成。 |

<camunda:formKey>embedded:form.html</camunda:formKey>
<camunda:formData>
  <camunda:formField id="amount" label="金额" type="long" />
  <camunda:formField id="reason" label="理由" type="string" />
</camunda:formData>

| 通过 API 获取表单 | 在自定义 UI 中动态加载任务表单。 | 用于构建自定义任务中心。 | getTaskForm() 返回表单资源或 URL。 |

String formKey = task.getFormKey();
FormService formService = processEngine.getFormService();
Object startForm = formService.getTaskForm(task.getId());

6.3 任务查询与分页(Task Query API)

查询条件Java API 方法用途示例代码注意事项
按任务ID查询taskService.createTaskQuery().taskId("taskId")精确查询单个任务。返回 Task 对象或 null。
Task task = taskService.createTaskQuery()
  .taskId("task123")
  .singleResult();

| 按处理人查询 | taskService.createTaskQuery().taskAssignee("kermit") | 查询某用户”我的任务”。 | 仅返回 assignee 匹配的任务。 |

List<Task> tasks = taskService.createTaskQuery()
  .taskAssignee("kermit")
  .listPage(0, 10);

| 按候选用户/组查询 | taskService.createTaskQuery().taskCandidateUser("kermit") / taskService.createTaskQuery().taskCandidateGroup("sales") | 查询某用户或组”可领取”的任务。 | 用户必须是候选人才能查询到。 |

List<Task> candidates = taskService.createTaskQuery()
  .taskCandidateUser("kermit")
  .list();

| 按流程实例ID查询 | taskService.createTaskQuery().processInstanceId("procInstId") | 查询某流程实例下的所有任务。 | 常用于流程跟踪。 | 可结合 taskFinished() 查询已完成任务(需历史级别 ≥ activity)。 | | 按任务名称/定义Key查询 | taskService.createTaskQuery().taskName("审批") / taskService.createTaskQuery().taskDefinitionKey("reviewTask") | 按名称或 BPMN 中的 id 查询。 | taskDefinitionKey 更稳定,推荐使用。 | 名称可能重复或修改。 | | 按创建时间查询 | taskService.createTaskQuery().taskCreatedAfter(date) / taskCreatedBefore(date) | 时间范围筛选。 | 时间参数为 java.util.Date。 |

LocalDateTime weekAgo = LocalDateTime.now().minusWeeks(1);
List<Task> recent = taskService.createTaskQuery()
  .taskCreatedAfter(Date.from(weekAgo.toInstant(ZoneOffset.UTC)))
  .list();

| 按优先级查询 | taskService.createTaskQuery().taskPriority(50) / taskPriorityHigherThan(30) | 查询指定优先级的任务。 | 用于高优先级任务优先处理。 | 优先级默认为 50。 | | 排序与分页 | .orderByTaskCreateTime().desc() / .listPage(firstResult, maxResults) | 控制结果顺序和分页。 | 生产环境必须分页,避免内存溢出。 |

List<Task> page = taskService.createTaskQuery()
  .taskAssignee("kermit")
  .orderByTaskCreateTime().desc()
  .listPage(0, 20); // 第1页,每页20条

| 获取总数 | taskService.createTaskQuery().count() | 获取满足条件的任务总数。 | 用于分页控件显示总条数。 | 与 listPage 配合使用。 |

6.4 任务委托、挂起与优先级设置

操作Java API 方法用途示例代码注意事项
任务委托(Delegation)taskService.delegateTask("taskId", "kermit"); / taskService.resolveTask("taskId"); / taskService.claim("taskId");将任务委托给他人处理,原处理人仍保留所有权。适用于临时替代或协助处理。委托后 delegationStatePENDING,处理后需 resolve 才能 complete
挂起任务(Suspend)Camunda 不支持直接”挂起任务”,但可挂起整个流程实例。暂停流程执行,所有任务不可操作。挂起后任务仍可查询,但无法 complete 或 claim。
// 挂起流程实例(间接挂起任务)
repositoryService.suspendProcessDefinitionById("procDefId");
runtimeService.suspendProcessInstanceById("procInstId");

| 激活任务(Activate) | runtimeService.activateProcessInstanceById("procInstId"); | 恢复被挂起的流程实例。 | 恢复后任务可继续处理。 | 需确保流程定义也处于激活状态。 | | 设置任务优先级 | task.setPriority(100); taskService.saveTask(task); | 为任务设置优先级(0-100),高优先级任务可优先处理。 | 在任务列表中按优先级排序。 | 默认优先级为 50。 | | 设置任务所有者(Owner) | taskService.setOwner("taskId", "admin"); | 记录任务的创建者或负责人,无操作权限。 | 用于审计或责任追溯。 | owner 不能处理任务,仅作记录。 | | 添加任务附件 | — | 为任务关联文件附件。 | 支持任意文件类型。 | 附件存储在 ACT_GE_BYTEARRAY 表中。 |

taskService.createAttachment(
  "text/plain",
  "taskId",
  "procInstId",
  "说明文件",
  "描述",
  new FileInputStream("note.txt")
);

| 添加任务评论 | — | 为任务添加文本评论,支持流程协作。 | 类似”评论区”功能。 | 评论可查询,用于沟通记录。 |

taskService.addComment("taskId", "procInstId", "审批通过!");
List<Comment> comments = taskService.getTaskComments("taskId");

第七章:流程控制与高级特性

7.1 事件子流程(Event Sub-Process)处理异常

配置项配置方式(BPMN XML)用途示例说明注意事项
开始事件类型使用非中断或中断的开始事件(如 Error、Message、Timer)作为事件子流程的触发器。在流程运行时动态捕获异常或事件,执行补偿或恢复逻辑。当主流程发生错误时,启动事件子流程记录日志并通知管理员。事件子流程在流程启动时即注册监听,无需显式调用。
非中断事件子流程触发后不中断主流程,主流程与子流程并行执行。接收到”暂停通知”消息时,记录日志但主流程继续运行。适用于通知、审计等辅助操作。
<subProcess id="eventSubProcess" triggeredByEvent="true">
  <startEvent id="messageStart">
    <messageEventDefinition messageRef="msgPause" />
  </startEvent>
  <!-- 子流程内容 -->
  <serviceTask id="logPause" camunda:class="LogDelegate" />
</subProcess>

| 中断事件子流程 | — | 触发后中断并终止当前作用域内的所有活动(如当前子流程或主流程),然后执行子流程。 | 发生业务错误时,终止当前分支,发送告警并结束。 | 仅影响同级及内部执行路径,外部流程不受影响。 |

<subProcess id="errorSubProcess" triggeredByEvent="true">
  <startEvent id="errorStart">
    <errorEventDefinition errorRef="businessError" />
  </startEvent>
  <serviceTask id="notifyAdmin" camunda:class="AlertAdminDelegate" />
  <endEvent id="endError" />
</subProcess>

| 作用域限制 | 事件子流程定义在哪个作用域内,就只能捕获该作用域内的事件。 | 实现精细化的异常隔离。 | 在用户任务作用域内定义错误事件子流程,仅处理该任务的错误。 | 不能跨作用域捕获事件。 | | 变量访问 | 事件子流程可访问其定义作用域内的所有流程变量。 | 共享上下文数据。 | 错误子流程中读取 orderId 并用于通知。 | 可读写变量,但修改可能影响主流程逻辑,需谨慎。 |

7.2 错误事件(Error Event)与错误边界事件

事件类型配置方式(BPMN XML)用途示例说明注意事项
错误边界事件(中断)捕获特定服务任务的 BpmnError,中断该任务并转向错误处理流程。验证失败时抛出 new BpmnError("ValidationError"),流程跳转至”错误处理”任务。errorRef 必须与 BpmnError 构造函数中的 errorCode 匹配。
<serviceTask id="validate" camunda:class="ValidationDelegate" />
<boundaryEvent id="errorBoundary" attachedToRef="validate">
  <errorEventDefinition errorRef="ValidationError" />
</boundaryEvent>
<sequenceFlow sourceRef="errorBoundary" targetRef="handleError" />

| 错误边界事件(非中断) | — | 捕获错误但不中断原任务,主流程继续执行,同时触发错误处理分支。 | 数据校验有警告时记录日志,但主流程继续审批。 | cancelActivity="false" 表示非中断。 |

<boundaryEvent id="nonInterruptingError" attachedToRef="task">
  <errorEventDefinition errorRef="WarningError" cancelActivity="false" />
</boundaryEvent>

| 错误结束事件 | — | 在服务任务中通过 throw new BpmnError("CheckFailed") 触发错误结束。 | 显式抛出错误,由上游边界事件或事件子流程捕获。 | 必须定义 errorRef,否则抛出未处理异常。 |

<serviceTask id="check" camunda:class="CheckDelegate" />
<endEvent id="errorEnd">
  <errorEventDefinition errorRef="CheckFailed" />
</endEvent>

| Java 中抛出 BpmnError | — | 在 JavaDelegate 或监听器中主动触发错误流程。 | 提供错误码和可选描述,用于精确控制。 | BpmnError 是流程引擎可处理的异常;RuntimeException 会导致任务失败进入 Job 重试。 |

if (invalid) {
  throw new BpmnError("ValidationError", "输入数据格式错误");
}

| 全局错误处理 | 在流程或子流程级别使用事件子流程 + 错误开始事件实现全局错误捕获。 | 避免为每个任务重复定义边界事件。 | 定义一个中断型事件子流程捕获所有未处理的 BpmnError。 | 适合作为兜底机制。 |

7.3 补偿事件(Compensation Event)实现回滚

事件类型配置方式(BPMN XML)用途示例说明注意事项
补偿边界事件为可补偿的活动(如收费)定义补偿处理器(如退款)。当流程需要回滚时,自动触发”退款”任务。补偿边界事件必须连接到一个补偿处理器(通常是服务任务)。
<serviceTask id="charge" camunda:class="ChargeDelegate" />
<boundaryEvent id="compensateCharge" attachedToRef="charge">
  <compensateEventDefinition />
</boundaryEvent>
<sequenceFlow sourceRef="compensateCharge" targetRef="refund" />

| 补偿处理器(Compensation Handler) | 在服务任务或子流程的 <extensionElements> 中标记为补偿处理器。 | 指定该任务是用于补偿的。 | 可配置重试策略,确保补偿成功。 |

<serviceTask id="refund" camunda:class="RefundDelegate">
  <extensionElements>
    <camunda:failedJobRetryTimeCycle>R3/PT5M</camunda:failedJobRetryTimeCycle>
  </extensionElements>
</serviceTask>

| 补偿开始事件 | 在子流程中使用补偿开始事件,表示该子流程是为补偿而启动的。 | 执行复杂的补偿逻辑。 | 启动一个子流程来撤销多个操作。 | 不需要连接入站 Sequence Flow。 | | 抛出补偿事件 | <intermediateThrowEvent id="throwCompensation"><compensateEventDefinition activityRef="charge" /></intermediateThrowEvent> | 显式触发补偿,回滚指定活动。 | 在审批被拒绝时,触发对”收费”任务的补偿。 | activityRef 指向被补偿的活动。 | | 可补偿活动 | 将服务任务标记为可补偿(默认非补偿性)。 | 声明该任务支持回滚。 | <serviceTask id="charge" camunda:isForCompensation="true" ... /> | 只有标记为 isForCompensation="true" 的活动才能被补偿。 | | 补偿执行机制 | 补偿按逆序执行,且只补偿已完成的可补偿活动。 | 确保回滚顺序正确。 | 若执行了 A → B → C,补偿时执行 C → B → A。 | 补偿由流程引擎自动管理,无需手动控制顺序。 |

典型场景:订单流程中”扣款”后若”发货”失败,通过补偿事件触发”退款”。

7.4 计时器事件(Timer Event)实现延迟触发

事件类型配置方式(BPMN XML)用途示例说明注意事项
定时开始事件按固定时间或周期启动流程实例。每天上午 9 点自动启动”日报生成”流程。timeCycle 支持 ISO 8601 周期表达式。
<startEvent id="timerStart">
  <timerEventDefinition>
    <timeCycle>R/2025-10-18T09:00:00+08:00/PT24H</timeCycle>
  </timerEventDefinition>
</startEvent>

| 中间定时事件 | — | 暂停流程执行一段时间。 | 任务 A 完成后等待 1 小时再执行任务 B。 | 流程实例在等待时处于”运行”状态,占用资源。 |

<sequenceFlow sourceRef="a" targetRef="timer" />
<intermediateCatchEvent id="timer">
  <timerEventDefinition>
    <timeDuration>PT1H</timeDuration>
  </timerEventDefinition>
</intermediateCatchEvent>
<sequenceFlow sourceRef="timer" targetRef="b" />

| 定时边界事件(中断) | — | 若任务在规定时间内未完成,则中断并转向超时处理。 | 审批任务 24 小时未处理,自动转交上级。 | 中断后原任务被取消,无法再处理。 |

<userTask id="review" camunda:assignee="kermit" />
<boundaryEvent id="timeout" attachedToRef="review">
  <timerEventDefinition>
    <timeDuration>PT24H</timeDuration>
  </timerEventDefinition>
</boundaryEvent>

| 定时边界事件(非中断) | — | 在任务进行中发送提醒,不影响原任务。 | 12 小时后发送”审批提醒”邮件。 | 主任务与提醒分支并行执行。 |

<boundaryEvent id="reminder" attachedToRef="review" cancelActivity="false">
  <timerEventDefinition>
    <timeDuration>PT12H</timeDuration>
  </timerEventDefinition>
</boundaryEvent>

| 时间表达式类型 | timeDate: 固定时间点 / timeDuration: 持续时间(如 PT1H)/ timeCycle: 重复周期(如 R5/PT10M 表示每 10 分钟一次,共 5 次) | 灵活定义时间逻辑。 | R/PT1M 表示每分钟重复一次。 | timeCycle 也支持 cron 表达式(如 0 0 12 * * ? 表示每天中午 12 点)。 | | 异步执行 | 定时事件由 Job Executor 异步触发。 | 不阻塞主线程。 | 需确保 Job Executor 已启用并正常运行。 | 定时事件的 Job 状态可在 ACT_RU_JOB 表中查询。 |

7.5 条件事件(Conditional Event)动态监听

事件类型配置方式(BPMN XML)用途示例说明注意事项
条件边界事件(中断)监听流程变量变化,若条件满足则中断当前任务。当外部系统设置 cancelRequest=true 时,取消审批等待。条件在每次变量更新时被评估。
<userTask id="waitApproval" />
<boundaryEvent id="condCancel" attachedToRef="waitApproval">
  <conditionalEventDefinition>
    <condition type="uel-value">${cancelRequest == true}</condition>
  </conditionalEventDefinition>
</boundaryEvent>

| 条件边界事件(非中断) | — | 条件满足时触发分支,但不中断原任务。 | 任务优先级变高时发送通知,任务继续执行。 | 适用于动态通知或监控。 |

<boundaryEvent id="condNotify" attachedToRef="task" cancelActivity="false">
  <conditionalEventDefinition>
    <condition type="uel-value">${priority == 'HIGH'}</condition>
  </conditionalEventDefinition>
</boundaryEvent>

| 中间条件事件 | — | 流程执行到此暂停,直到条件满足。 | 等待外部系统回调设置 paymentReceived=true。 | 流程实例在等待状态,直到变量更新触发事件。 |

<intermediateCatchEvent id="waitForPayment">
  <conditionalEventDefinition>
    <condition type="uel-value">${paymentReceived == true}</condition>
  </conditionalEventDefinition>
</intermediateCatchEvent>

| 条件类型 | uel-value: 值表达式 / uel-method: 方法表达式(较少用) | 使用 UEL 表达式定义触发条件。 | ${customer.creditScore > 700} | 表达式必须返回 boolean。 | | 变量更新触发 | 条件事件的监听依赖于流程变量的更新操作(如 setVariable)。 | 引擎在变量更新时评估所有注册的条件事件。 | 调用 runtimeService.setVariable(procInstId, "cancelRequest", true) 可能触发条件事件。 | 若变量未通过 API 更新(如直接改数据库),条件不会触发。 | | 性能考虑 | 大量条件事件可能影响性能,因为每次变量更新都要评估所有条件。 | 适用于关键路径的动态控制。 | 避免在高频率变量更新的流程中使用过多条件事件。 | 可结合 variableName 过滤以优化性能(Camunda 7.14+)。 |


第八章:Camunda REST API 使用

8.1 流程定义相关 API(查询、部署、挂起)

操作HTTP 方法URL请求/响应示例用途注意事项
查询流程定义GET/process-definitionGET /process-definition?q=approval → 响应:200 OK,返回 JSON 数组,包含 id, key, name, version, suspended 等字段。获取符合条件的流程定义列表。支持分页(firstResult, maxResults)、按 key, name, version 等过滤。
获取单个流程定义GET/process-definition/{id}GET /process-definition/ApprovalProcess:1:2345获取指定 ID 的流程定义详情。可用于前端展示流程信息。
部署流程POST/deployment/createPOST /deployment/create Content-Type: multipart/form-data,File: process.bpmn,Name: My Deployment上传 BPMN 文件并部署。支持 ZIP、BAR 包或单个文件;同名流程自动升级版本。
挂起流程定义POST/process-definition/{id}/suspend挂起流程定义,阻止新实例启动,并可选择挂起所有运行中的实例。executionDate 可延迟执行。
{
  "suspended": true,
  "includeProcessInstances": true,
  "executionDate": "2025-10-18T10:00:00"
}

| 激活流程定义 | POST | /process-definition/{id}/activate | 同上,"suspended": false | 恢复被挂起的流程定义。 | 挂起的流程无法启动新实例。 | | 获取流程图(Diagram) | GET | /process-definition/{id}/xml | GET /process-definition/ApprovalProcess:1:2345/xml → 响应:{ "id": "...", "bpmn20Xml": "..." } | 获取 BPMN XML 内容,可用于渲染流程图。 | 结合 diagram-js 等库实现流程可视化。 |

8.2 流程实例相关 API(启动、查询、删除)

操作HTTP 方法URL请求/响应示例用途注意事项
启动流程实例POST/process-definition/key/{processKey}/start根据流程 key 启动新实例,并传入初始变量。推荐使用 key 而非 id,key 不随版本变化。
POST /process-definition/key/ApprovalProcess/start
{
  "variables": {
    "applicant": { "value": "Alice", "type": "String" },
    "amount": { "value": 5000, "type": "Long" }
  }
}

| 查询流程实例 | GET | /process-instance | GET /process-instance?processDefinitionKey=ApprovalProcess&active=true | 查询符合条件的流程实例。 | 支持按 processDefinitionId, businessKey, suspended, superProcessInstanceId 等过滤。 | | 获取单个实例 | GET | /process-instance/{id} | GET /process-instance/procInst123 → 响应包含 id, definitionId, businessKey, suspended, startTime 等。 | 获取实例运行状态。 | — | | 删除流程实例 | DELETE | /process-instance/{id} | DELETE /process-instance/procInst123?skipCustomListeners=true&skipIoMappings=true | 终止并删除指定流程实例。 | skipCustomListeners 跳过自定义监听器;可用于测试环境清理。 | | 删除多个实例 | POST | /process-instance/delete | — | 批量删除流程实例。 | 适用于批量清理过期或测试数据。 |

{
  "processInstanceIds": ["id1", "id2"],
  "deleteReason": "cleanup"
}

| 获取流程实例树 | GET | /process-instance/{id}/activity-instances | GET /process-instance/procInst123/activity-instances → 返回当前执行的活动(任务、网关等)的树形结构。 | 用于流程跟踪和调试,显示当前执行路径。 | 返回 activityId, childActivityInstances, executionIds 等。 |

8.3 任务相关 API(领取、完成、查询)

操作HTTP 方法URL请求/响应示例用途注意事项
查询任务GET/taskGET /task?assignee=kermit&taskDefinitionKey=reviewTask&active=true查询用户待处理的任务。支持分页、排序(sortBy=createTime)、按 candidateUser, processInstanceId 等过滤。
获取任务详情GET/task/{id}GET /task/task123 → 返回 name, assignee, createTime, dueDate, description, formKey 等。获取单个任务的详细信息。
领取任务(Claim)POST/task/{id}/claim将候选任务分配给指定用户。用户必须是候选用户或组成员。
POST /task/task123/claim
{ "userId": "kermit" }

| 完成任务 | POST | /task/{id}/complete | — | 完成任务并设置输出变量,流程继续执行。 | 若任务有关联表单,应在完成前提交表单数据。 |

POST /task/task123/complete
{
  "variables": {
    "approved": { "value": true, "type": "Boolean" }
  }
}

| 设置任务变量 | POST | /task/{id}/variables | — | 动态设置任务级别的变量。 | 变量作用域为整个流程实例。 |

POST /task/task123/variables
[
  { "name": "comment", "value": "Good", "type": "String" }
]

| 添加任务评论 | POST | /task/{id}/comment/create | POST /task/task123/comment/create { "message": "审批通过" } | 为任务添加评论,支持协作沟通。 | 评论可通过 /task/{id}/comment 查询。 | | 委托任务 | POST | /task/{id}/delegate | POST /task/task123/delegate { "userId": "grover" } | 将任务委托给他人处理。 | 原处理人仍为 owner,受托人处理后需 resolve。 |

8.4 历史数据查询 API(Historic Process/Task)

需配置 history 级别(如 activity, audit, full)。

操作HTTP 方法URL请求/响应示例用途注意事项
查询历史流程实例GET/history/process-instanceGET /history/process-instance?finished=true&processDefinitionKey=ApprovalProcess获取已结束的流程实例记录。可统计流程耗时、成功率等。
查询历史任务GET/history/taskGET /history/task?processInstanceId=procInst123&finished=true → 返回 endTime, durationInMillis, deleteReason(如 completed)等。获取流程实例中所有已完成的任务。
获取历史变量GET/history/variable-instanceGET /history/variable-instance?processInstanceId=procInst123查询流程实例生命周期中的所有变量变更记录。用于审计和数据分析。
获取历史活动实例GET/history/activity-instanceGET /history/activity-instance?processInstanceId=procInst123 → 包含 startTime, endTime, durationInMillis。获取流程中每个活动(任务、网关)的执行记录。可用于性能分析。
获取历史详情(Detail)GET/history/detailGET /history/detail?processInstanceId=procInst123获取更细粒度的历史记录,包括变量更新、表单提交等。数据量大,谨慎使用。

8.5 变量操作 API(获取、设置、删除)

操作HTTP 方法URL请求/响应示例用途注意事项
获取流程实例变量GET/process-instance/{id}/variablesGET /process-instance/procInst123/variables → 返回 { "amount": { "value": 5000, "type": "Long" }, ... }获取流程实例的所有变量。支持获取特定变量:/variables/{varName}
设置变量POST/process-instance/{id}/variables批量设置一个或多个变量。变量类型支持 String, Integer, Long, Double, Boolean, Date, File 等。
POST /process-instance/procInst123/variables
[
  { "name": "status", "value": "approved", "type": "String" }
]

| 更新单个变量 | PUT | /process-instance/{id}/variables/{varName} | PUT /process-instance/procInst123/variables/amount { "value": 6000, "type": "Long" } | 更新指定变量的值。 | 更精确的控制。 | | 删除变量 | DELETE | /process-instance/{id}/variables/{varName} | DELETE /process-instance/procInst123/variables/tempData | 删除流程实例中的指定变量。 | 释放存储空间,清理临时数据。 | | 获取变量值(原始) | GET | /process-instance/{id}/variables/{varName}/data | GET /process-instance/procInst123/variables/report/data | 获取 File 类型变量的二进制数据。 | 响应为文件流,可用于下载附件。 |


第九章:历史数据与监控

9.1 历史级别(History Levels)配置

历史级别配置值(history)记录内容适用场景注意事项
nonenone不记录任何历史数据。仅用于测试或对性能要求极高且无需审计的场景。无法使用历史 API 或 Cockpit 查看已完成实例。
activityactivity记录流程实例、活动实例(任务、网关等)的开始/结束时间;记录任务负责人(assignee);不记录变量。平衡性能与基本审计需求,推荐生产环境使用。可查询流程执行路径与时长,但无法查看变量值。
auditaudit包含 activity 级别所有内容 + 记录变量更新(值和类型)+ 记录任务评论、附件 + 记录用户操作日志。需要完整审计跟踪的场景(如金融、医疗)。数据量显著增加,需评估存储与性能影响。
fullfull包含 audit 级别所有内容 + 记录所有细节变更(如表单提交、属性修改)+ 更细粒度的历史快照。极端审计需求或深度调试场景。性能开销最大,仅在必要时启用;不推荐生产环境长期使用。

配置方式:

  1. Spring Boot 配置(application.yml):
camunda:
  bpm:
    history: audit  # 设置历史级别为 audit
    # 其他配置...
  1. camunda.cfg.xml 配置:
<property name="history">audit</property>
  1. Java API 动态设置(不推荐生产环境):
// 在流程引擎构建后设置(通常在启动时配置更佳)
processEngine.getProcessEngineConfiguration().setHistory("audit");

建议:

  • 生产环境推荐使用 audit 级别。
  • 若存储敏感,可使用 activity 并通过日志记录关键变量。
  • 调整历史级别后,新部署的流程定义将使用新级别,已有实例仍按原级别记录。

9.2 查询历史流程实例与任务

基于 HistoryService API,适用于 Java 应用集成。

查询目标Java API 方法示例代码用途注意事项
历史流程实例historyService.createHistoricProcessInstanceQuery()获取已完成的流程实例列表。可过滤:unfinished(), startedAfter(), startedBefore(), finished() 等。
List<HistoricProcessInstance> instances =
  historyService.createHistoricProcessInstanceQuery()
    .processDefinitionKey("ExpenseApproval")
    .finished()
    .orderByProcessInstanceEndTime().desc()
    .listPage(0, 10);

| 历史任务实例 | historyService.createHistoricTaskInstanceQuery() | — | 查询某流程实例中所有已完成的任务。 | 返回 endTime, durationInMillis, deleteReason(如 completed, deleted)。 |

List<HistoricTaskInstance> tasks =
  historyService.createHistoricTaskInstanceQuery()
    .processInstanceId("procInst123")
    .finished()
    .orderByHistoricTaskInstanceEndTime().asc()
    .list();

| 历史变量实例 | historyService.createHistoricVariableInstanceQuery() | — | 查询流程实例中变量的历史值。 | 即使变量已被覆盖或删除,仍可查到历史记录(需 audit 或 full 级别)。 |

List<HistoricVariableInstance> vars =
  historyService.createHistoricVariableInstanceQuery()
    .processInstanceId("procInst123")
    .variableName("totalAmount")
    .list();

| 历史活动实例 | historyService.createHistoricActivityInstanceQuery() | — | 获取流程中每个活动的执行详情(开始/结束时间、持续时间)。 | 用于计算任务耗时、瓶颈分析。 |

List<HistoricActivityInstance> acts =
  historyService.createHistoricActivityInstanceQuery()
    .processInstanceId("procInst123")
    .activityType("userTask")
    .finished()
    .list();

| 历史详情(Detail) | historyService.createHistoricDetailQuery() | — | 获取最细粒度的历史变更,包括变量更新、表单属性等。 | 数据量大,查询慢,慎用。 |

List<HistoricDetail> details =
  historyService.createHistoricDetailQuery()
    .processInstanceId("procInst123")
    .variableUpdates()
    .list();

提示:所有查询均支持分页(.listPage(firstResult, maxResults))和排序,避免内存溢出。

9.3 性能指标与流程分析(通过 Cockpit)

Camunda Cockpit 是官方提供的 Web 管理与监控工具,基于历史数据提供可视化分析。

分析功能Cockpit 页面提供信息用途注意事项
流程实例概览Process Definition 页面实例总数(运行中/已完成/已终止)、启动频率图表、实例生命周期分布图。了解流程使用情况和负载。可按时间范围筛选。
任务耗时分析Statistics 标签页每个任务的平均、最大、最小执行时间、直方图显示耗时分布。识别流程瓶颈(如审批过慢)。仅显示已完成任务的数据。
流程路径分析Incidents & Flow Nodes各网关分支的执行次数、实际执行路径热力图。分析流程走向,验证业务规则是否符合预期。可发现异常路径(如错误处理分支频繁触发)。
异常(Incidents)监控Incidents 标签页失败的服务任务(Job)、错误原因、重试次数、堆栈跟踪。快速定位技术故障(如服务不可用)。支持手动重试或删除。
变量趋势分析Variables 标签页(需 audit 级别)关键变量(如 amount)的值分布、随时间变化趋势。分析业务数据模式(如高金额申请占比)。可导出数据用于 BI 分析。
自定义报表Reports 功能(Camunda 7.15+)支持创建自定义图表(柱状图、折线图)。构建 KPI 仪表盘(如”平均审批时长”)。需编写 SQL 查询或使用内置模板。

最佳实践:

  • 定期检查 Cockpit 中的 Incidents,确保无积压失败任务。
  • 使用 Statistics 优化流程设计,减少等待时间。
  • 将关键流程的 Cockpit 视图嵌入企业监控大屏。

9.4 自定义历史数据扩展

当默认历史数据不足以满足业务需求时,可通过以下方式扩展。

扩展方式实现方法示例场景代码示例注意事项
自定义历史监听器实现 HistoryListener 接口,在特定历史事件发生时执行逻辑。记录额外上下文到外部数据库或发送审计消息。可捕获 taskFinished, processFinished 等事件。
public class CustomHistoryListener implements HistoryListener {
  @Override
  public void notify(HistoryEvent event) {
    if (event instanceof HistoricTaskInstanceEventEntity) {
      HistoricTaskInstance task = ((HistoricTaskInstanceEventEntity) event).getHistoricTaskInstance();
      if ("reviewTask".equals(task.getTaskDefinitionKey())) {
        // 记录到外部审计系统
        auditService.logTaskCompletion(task.getId(), task.getAssignee());
      }
    }
  }
}

// 注册:
processEngineConfiguration.setCustomHistoryListeners(...);

| 写入外部数据库 | 在监听器中将关键历史数据写入专用的数据仓库或 OLAP 数据库(如 PostgreSQL, ClickHouse)。 | 构建企业级流程分析平台,支持复杂 BI 查询。 | 使用 JDBC 或 JPA 将 HistoricProcessInstance 映射为实体类并持久化。 | 确保事务一致性或使用异步队列(如 Kafka)解耦。 | | 添加自定义历史实体 | 通过 Camunda 的 DbSqlSession 扩展数据库表结构,插入自定义历史记录。 | 记录业务特有的审计字段(如”审批意见来源”)。 | 较复杂,需直接操作 MyBatis 映射,不推荐除非必要。 | 高风险,升级引擎时可能冲突。 | | 结合日志框架 | 使用 MDC 或结构化日志记录流程关键点。 | 快速排查问题,与现有 ELK 日志体系集成。 | 日志非结构化,难以做聚合分析。 |

MDC.put("processInstanceId", execution.getProcessInstanceId());
log.info("User task started", "taskId", task.getId());

推荐方案:

  • 优先使用 HistoryListener + 外部数据库。
  • 对于实时性要求高的场景,结合 Kafka 发送事件流。
  • 避免修改 Camunda 内部表结构。

第十章:安全与权限管理

10.1 用户与组管理(Identity Service)

Camunda 提供内置的 IdentityService 用于管理用户(User)、组(Group)和成员关系(Membership),适用于轻量级场景。

操作Java API 方法示例代码用途注意事项
创建用户identityService.newUser(userId)注册新用户。密码以哈希形式存储(默认 SHA-1 + salt)。
User user = identityService.newUser("kermit");
user.setFirstName("Kermit");
user.setLastName("The Frog");
user.setPassword("pass123");
user.setEmail("kermit@camunda.org");
identityService.saveUser(user);

| 创建组 | identityService.newGroup(groupId) | — | 创建组织或角色组。 | type 可用于区分用途(如审批组、管理员组)。 |

Group group = identityService.newGroup("managers");
group.setName("Managers");
group.setType("assignment"); // 可选: 'security-role', 'assignment'
identityService.saveGroup(group);

| 添加用户到组 | identityService.createMembership(userId, groupId) | — | 建立用户与组的关联。 | 一个用户可属于多个组。 |

identityService.createMembership("kermit", "managers");

| 查询用户 | identityService.createUserQuery() | — | 按条件搜索用户。 | 支持分页、排序、按 id, email, lastName 等过滤。 |

List<User> users = identityService.createUserQuery()
  .userFirstNameLike("%mit%")
  .listPage(0, 10);

| 查询组 | identityService.createGroupQuery() | — | 获取组信息。 | 可结合 groupMember(userId) 查询某用户所属的组。 |

List<Group> groups = identityService.createGroupQuery()
  .groupName("Managers")
  .list();

| 删除用户/组 | identityService.deleteUser(id) / deleteGroup(id) | identityService.deleteUser("kermit"); | 清理无效账户。 | 删除用户不会自动删除其历史任务记录。 |

生产建议:

  • 内置 IdentityService 适用于开发、测试或小型系统。
  • 生产环境推荐集成外部系统(如 LDAP、Keycloak、数据库)。

10.2 流程与任务的权限控制

Camunda 支持对流程定义、流程实例和任务进行细粒度的权限控制。

控制对象权限类型配置方式示例用途
流程定义(Process Definition)READ、UPDATE、CREATE_INSTANCE、DELETE在流程部署后通过 AuthorizationService 设置。只允许 managers 组启动”薪资审批”流程。控制谁可以部署、启动或修改流程。
流程实例(Process Instance)READ、UPDATE、DELETE动态设置,通常在流程启动时或通过监听器配置。用户只能查看自己发起的流程实例。实现数据隔离(多租户)。
任务(Task)READ、UPDATE、DELEGATE、DELETE通过 camunda:assignee, camunda:candidateUsers, camunda:candidateGroups 在 BPMN 中声明。<userTask id="review" camunda:assignee="kermit" camunda:candidateGroups="managers,reviewers" />控制任务的处理人和候选人群体。
授权(Authorization)使用 AuthorizationService 创建授权规则。以编程方式分配权限。适用于动态权限分配场景。
Authorization auth = authorizationService.createNewAuthorization(Authorization.AUTH_TYPE_GLOBAL);
auth.setResourceId("*"); // 或具体流程定义 key
auth.setResource(Resource.PROCESS_DEFINITION);
auth.setPermissions(Arrays.asList(Permissions.READ, Permissions.CREATE_INSTANCE));
auth.setUserId("kermit"); // 或 groupId("managers")
authorizationService.saveAuthorization(auth);

权限继承:流程实例权限通常继承自流程定义;任务权限可独立设置,支持运行时动态分配。

10.3 集成 Spring Security

在 Spring Boot 应用中,推荐使用 Spring Security 统一管理认证与授权。

步骤 1:添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.camunda.bpm.springboot</groupId>
  <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
</dependency>

步骤 2:配置 Spring Security

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/engine-rest/**").hasRole("ACTIVITI_USER") // 保护 Camunda REST API
        .requestMatchers("/app/**").hasRole("USER")
        .anyRequest().authenticated()
      )
      .httpBasic(Customizer.withDefaults()) // 启用 Basic Auth
      .csrf().disable(); // Camunda REST 不依赖 session,可禁用 CSRF
    return http.build();
  }

  // 使用内存用户(生产环境应使用数据库或 LDAP)
  @Bean
  public UserDetailsService userDetailsService() {
    UserDetails kermit = User.withDefaultPasswordEncoder()
      .username("kermit")
      .password("pass123")
      .roles("ACTIVITI_USER", "USER")
      .build();
    return new InMemoryUserDetailsManager(kermit);
  }
}

步骤 3:同步 Spring Security 用户到 Camunda

@Component
public class SpringSecurityUserToCamunda implements ApplicationListener<AuthenticationSuccessEvent> {
  @Autowired
  private IdentityService identityService;

  @Override
  public void onApplicationEvent(AuthenticationSuccessEvent event) {
    Authentication auth = event.getAuthentication();
    String username = auth.getName();

    // 确保用户存在于 Camunda IdentityService(可选,仅当需使用 assignee/candidateGroups)
    if (identityService.createUserQuery().userId(username).count() == 0) {
      User user = identityService.newUser(username);
      user.setPassword("temp"); // 不用于登录
      identityService.saveUser(user);
    }
  }
}

优势:

  • 统一认证入口。
  • 支持 OAuth2、JWT、LDAP 等多种方式。
  • 与现有 Spring 生态无缝集成。

10.4 REST API 的认证与授权(Basic Auth / OAuth2)

Camunda REST API 默认无保护,生产环境必须启用安全机制。

认证方式配置方式请求示例用途注意事项
HTTP Basic Auth通过 Spring Security 或反向代理(如 Nginx)配置。简单、广泛支持,适用于内部系统。密码需通过 HTTPS 传输。
GET /engine-rest/process-definition HTTP/1.1
Authorization: Basic a2VybWl0OnBhc3MxMjM=
Host: localhost:8080

a2VybWl0OnBhc3MxMjM=kermit:pass123 的 Base64 编码。

| OAuth2 / JWT | 使用 Spring Security OAuth2 Resource Server。 | — | 适用于微服务架构,支持单点登录(SSO)。 | 需配置 spring.security.oauth2.resourceserver.jwt.issuer-uri。 |

GET /engine-rest/process-instance HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx
Host: localhost:8080

| Token 认证(Camunda 内置) | 启用 rest-api-authentication 模块(Camunda 7.18+)。 | GET /engine-rest/process-definition?access_token=abc123 | 无 session 的轻量级认证。 | 需自行管理 token 生命周期。 | | 反向代理认证 | 使用 Nginx、Apache 或 API Gateway(如 Kong、Keycloak Gatekeeper)前置认证。 | 所有请求先经网关验证 JWT 或 Basic Auth,再转发到 Camunda。 | 集中管理安全策略,解耦应用逻辑。 | 推荐生产环境使用。 |

Spring Security OAuth2 配置示例:

# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://your-keycloak/auth/realms/your-realm
// 启用 JWT 认证
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/engine-rest/**").hasAuthority("SCOPE_process:read")
        .anyRequest().authenticated()
      )
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
  }
}