第一章: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:delegateExpression、camunda:formKey。 | Camunda 特有配置需使用 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 与其他流程引擎的对比
| 对比项 | Camunda | Activiti | Flowable | JBPM | 备注 |
|---|---|---|---|---|---|
| 开源背景 | 由 Activiti 核心团队分裂后创建,专注企业级流程自动化。 | Alfresco 公司发起,早期流行,后发展缓慢。 | 从 Activiti 分支出,功能丰富,社区活跃。 | Red Hat 推出,集成于 JBoss 生态,强调规则与复杂流程。 | Camunda 和 Flowable 技术同源,均源自 Activiti 5。 |
| 核心标准支持 | BPMN 2.0、DMN 1.3、CMMN 1.1 | BPMN 2.0(DMN 支持较弱) | BPMN 2.0、DMN 1.3、CMMN 1.1 | BPMN 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 修改。 |
| 访问 Tasklist | http://localhost:8080/camunda/app/tasklist/default/ | 登录并处理用户任务。 | 默认用户:demo / demo | demo 用户拥有所有权限,生产环境需创建自定义用户。 |
| 访问 Cockpit | http://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-jpa 和 spring-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 记录所有历史数据,适合开发 | 生产环境建议使用 audit 或 activity 以提升性能。 |
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 时走此路径。 | 表达式返回 true 或 false,第一个为 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 变量操作流程变量。 | execution 是 DelegateExecution 对象。 |
<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} | execution 是 DelegateExecution 实例,可用于日志或调试。 |
| 访问 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) | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| execute | public 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) | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|---|
| ExecutionListener | start, end, take | 可应用于 process、serviceTask、userTask 等元素 | 监听流程、活动(如服务任务)的执行事件。 | 在流程开始时初始化变量或记录日志。 |
<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 中可使用 execution、task 等上下文对象。 |
<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"); | 将任务委托给他人处理,原处理人仍保留所有权。 | 适用于临时替代或协助处理。 | 委托后 delegationState 为 PENDING,处理后需 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-definition | GET /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/create | POST /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 | /task | GET /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-instance | GET /history/process-instance?finished=true&processDefinitionKey=ApprovalProcess | 获取已结束的流程实例记录。 | 可统计流程耗时、成功率等。 |
| 查询历史任务 | GET | /history/task | GET /history/task?processInstanceId=procInst123&finished=true → 返回 endTime, durationInMillis, deleteReason(如 completed)等。 | 获取流程实例中所有已完成的任务。 | — |
| 获取历史变量 | GET | /history/variable-instance | GET /history/variable-instance?processInstanceId=procInst123 | 查询流程实例生命周期中的所有变量变更记录。 | 用于审计和数据分析。 |
| 获取历史活动实例 | GET | /history/activity-instance | GET /history/activity-instance?processInstanceId=procInst123 → 包含 startTime, endTime, durationInMillis。 | 获取流程中每个活动(任务、网关)的执行记录。 | 可用于性能分析。 |
| 获取历史详情(Detail) | GET | /history/detail | GET /history/detail?processInstanceId=procInst123 | 获取更细粒度的历史记录,包括变量更新、表单提交等。 | 数据量大,谨慎使用。 |
8.5 变量操作 API(获取、设置、删除)
| 操作 | HTTP 方法 | URL | 请求/响应示例 | 用途 | 注意事项 |
|---|---|---|---|---|---|
| 获取流程实例变量 | GET | /process-instance/{id}/variables | GET /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) | 记录内容 | 适用场景 | 注意事项 |
|---|---|---|---|---|
| none | none | 不记录任何历史数据。 | 仅用于测试或对性能要求极高且无需审计的场景。 | 无法使用历史 API 或 Cockpit 查看已完成实例。 |
| activity | activity | 记录流程实例、活动实例(任务、网关等)的开始/结束时间;记录任务负责人(assignee);不记录变量。 | 平衡性能与基本审计需求,推荐生产环境使用。 | 可查询流程执行路径与时长,但无法查看变量值。 |
| audit | audit | 包含 activity 级别所有内容 + 记录变量更新(值和类型)+ 记录任务评论、附件 + 记录用户操作日志。 | 需要完整审计跟踪的场景(如金融、医疗)。 | 数据量显著增加,需评估存储与性能影响。 |
| full | full | 包含 audit 级别所有内容 + 记录所有细节变更(如表单提交、属性修改)+ 更细粒度的历史快照。 | 极端审计需求或深度调试场景。 | 性能开销最大,仅在必要时启用;不推荐生产环境长期使用。 |
配置方式:
- Spring Boot 配置(
application.yml):
camunda:
bpm:
history: audit # 设置历史级别为 audit
# 其他配置...
camunda.cfg.xml配置:
<property name="history">audit</property>
- Java API 动态设置(不推荐生产环境):
// 在流程引擎构建后设置(通常在启动时配置更佳)
processEngine.getProcessEngineConfiguration().setHistory("audit");
建议:
- 生产环境推荐使用
audit级别。- 若存储敏感,可使用
activity并通过日志记录关键变量。- 调整历史级别后,新部署的流程定义将使用新级别,已有实例仍按原级别记录。
9.2 查询历史流程实例与任务
基于
HistoryServiceAPI,适用于 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();
}
}