Article
第一章:Flowable 概述与入门
1.1 工作流与 BPMN 2.0 基础概念
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 工作流(Workflow) | 指业务流程的自动化执行路径,用于协调人员、系统和任务之间的协作。在软件中通常表现为一系列按规则流转的任务。 | 工作流不等于完整业务系统,需与其他模块(如用户管理、数据存储)集成。 |
| 业务流程管理(BPM) | 一种管理企业业务流程的方法论,旨在优化流程效率、透明度和可控性。Flowable 是一个轻量级 BPM 引擎。 | BPM 不仅是技术工具,也包含流程建模、分析、执行、监控等管理活动。 |
| BPMN 2.0(Business Process Model and Notation) | 由 OMG 组织制定的标准流程建模语言,使用图形化符号描述业务流程。支持流程定义、协作、事件、网关等语义。 | BPMN 2.0 是 XML 格式的,可被引擎解析执行,不仅是绘图标准。 |
| 流程定义(Process Definition) | 描述一个业务流程的模板,通常以 .bpmn20.xml 文件形式存在,包含流程结构、任务、网关、事件等元素。 | 同一 key 的流程定义每次部署会生成新版本,版本号递增。 |
| 流程实例(Process Instance) | 流程定义的一次具体执行,代表一个实际运行中的业务流程(如”张三的请假申请”)。 | 一个流程定义可启动多个流程实例。 |
| 执行流(Execution) | 流程实例内部的执行路径,用于表示当前运行到哪个节点,支持并行分支等复杂路径。 | 在并行网关中,一个流程实例可能对应多个执行流。 |
| 任务(Task) | 流程中需要人工或系统完成的工作单元,如”审批请假”、“发送邮件”等。 | 用户任务(User Task)需人工处理,服务任务(Service Task)自动执行。 |
| 事件(Event) | 表示流程中发生的特定时刻,如开始、结束、消息到达、定时触发等。 | 事件驱动流程行为,如边界事件可中断或附加到任务上。 |
| 网关(Gateway) | 控制流程分支与汇聚的元素,如排他网关(决策)、并行网关(分叉/汇聚)等。 | 网关不执行任何操作,仅控制流程走向。 |
1.2 Flowable 简介与核心组件
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| Flowable 项目 | 一个开源的轻量级业务流程引擎,支持 BPMN 2.0、CMMN、DMN 标准,可用于自动化和管理业务流程。 | 由 Activiti 分支发展而来,社区活跃,API 简洁,适合嵌入 Spring Boot 应用。 |
| ProcessEngine | Flowable 的核心执行引擎,负责管理流程的部署、启动、执行和监控。所有操作都通过其提供的服务接口进行。 | 一个 JVM 中可存在多个 ProcessEngine,但通常只创建一个。 |
| RepositoryService | 用于管理流程资源的服务,如部署流程定义、查询流程定义、获取流程图资源等。 | 部署后流程定义不可修改,需重新部署新版本。 |
| RuntimeService | 用于启动流程实例、操作流程变量、查询运行时执行流等。 | 启动流程实例时可传入变量,用于流程决策或任务分配。 |
| TaskService | 管理用户任务的服务,包括查询任务、办理任务、设置任务负责人等。 | 仅对用户任务(User Task)有效,不能操作服务任务。 |
| HistoryService | 提供对历史数据的查询功能,如已完成的流程实例、任务、变量等。 | 历史数据保留依赖于历史级别(historyLevel)配置。 |
| IdentityService | 简单的身份管理服务,用于管理用户、组及权限关系。 | 功能较基础,生产环境常与 LDAP、数据库或 Spring Security 集成。 |
| FormService | 管理流程表单的服务,支持动态表单定义与渲染。 | 可与 Flowable Modeler 配合使用,实现无代码表单设计。 |
| DynamicBpmnService | 允许在不重新部署的情况下动态修改流程定义的行为(如任务名称、监听器等)。 | 仅支持部分属性的动态修改,不能改变流程结构。 |
1.3 Flowable 与其他工作流引擎对比(Activiti、Camunda)
| 对比项 | Flowable | Activiti | Camunda |
|---|---|---|---|
| 起源 | 从 Activiti 6 分支独立出来,由原核心团队开发 | Alfresco 公司发起,早期由 jBPM 团队主导 | 由 Activiti 核心成员创建,专注于企业级流程引擎 |
| 社区与活跃度 | 活跃,持续更新,GitHub 星标高 | Activiti 7 后转向云原生,社区分裂,活跃度下降 | 非常活跃,文档完善,企业支持强 |
| 架构设计 | 轻量级,模块化,易于嵌入 Spring Boot | 模块较多,7.x 版本重构较大 | 模块清晰,支持微服务架构(Camunda Platform 8 为 Zeebe + Operate) |
| 标准支持 | 支持 BPMN 2.0、CMMN、DMN | 支持 BPMN 2.0,CMMN 和 DMN 支持有限 | 完全支持 BPMN 2.0、CMMN、DMN |
| API 易用性 | API 简洁,与 Spring 集成良好 | API 较复杂,7.x 版本变化大 | API 清晰,文档丰富,学习曲线平缓 |
| UI 工具 | 提供 Flowable UI(Modeler、Task、Admin) | 提供 Activiti App,但功能较弱 | 提供 Camunda Modeler(桌面)、Cockpit(Web 控制台) |
| 企业支持 | 有商业支持,但不如 Camunda 成熟 | Alfresco 提供企业版支持 | 提供完善的企业版支持(监控、高可用、安全) |
| 性能表现 | 性能优秀,适合中小规模系统 | 性能良好,但 7.x 版本稳定性曾受质疑 | 性能优异,尤其在高并发场景下表现突出 |
| 扩展性 | 支持插件、事件总线、自定义行为 | 扩展机制较弱 | 扩展性强,支持事件监听、外部任务等 |
| 适用场景 | 中小型项目、Spring Boot 快速集成 | 已有 Activiti 项目迁移 | 大型企业、高可用、复杂流程场景 |
注意事项:
- Flowable 和 Camunda 都源于 Activiti,但发展路径不同。
- 若追求稳定性、企业级支持,Camunda 更优;若追求轻量、快速集成,Flowable 是良好选择。
- Activiti 6 及之前版本稳定,但 7.x 重构后争议较大,社区推荐迁移到 Flowable 或 Camunda。
1.4 开发环境搭建与快速入门示例
本小节为实践引导,暂不涉及具体 API 方法,以下为环境准备与流程说明。
| 准备项 | 说明 | 注意事项 |
|---|---|---|
| JDK 版本 | 推荐使用 JDK 8 或以上版本 | Flowable 6.x 支持 JDK 8+,7.x 支持 JDK 11+ |
| 构建工具 | Maven 或 Gradle | 建议使用 Maven 管理依赖 |
| 数据库 | 支持 H2(测试)、MySQL、PostgreSQL、Oracle 等 | 需提前创建数据库,Flowable 自动建表 |
| Spring Boot(推荐) | 使用 flowable-spring-boot-starter 快速集成 | 可自动配置 ProcessEngine 和数据源 |
| Flowable 依赖(Maven) | org.flowable:flowable-spring-boot-starter:6.8.0 | 版本需与 Spring Boot 兼容,建议使用最新稳定版 |
| 数据库建表策略 | Flowable 自动创建 ACT_ 开头的数据表 | 第一次启动时自动建表,后续通过 databaseSchemaUpdate 控制 |
快速入门流程步骤:
- 创建 Spring Boot 项目
- 添加 Flowable 依赖
- 配置数据源
- 在
resources/processes/下放置.bpmn20.xml文件 - 启动应用,自动部署流程
- 使用 RuntimeService 启动流程实例
.bpmn20.xml文件名需与流程 id 一致或放在正确目录。
默认自动部署: 将 BPMN 文件放入 resources/processes/ 目录,Spring Boot 启动时自动部署。可通过 flowable.deployment-mode 配置部署行为。
核心表说明:
| 表前缀 | 用途 |
|---|---|
ACT_RE_* | 资源存储(流程定义) |
ACT_RU_* | 运行时数据 |
ACT_HI_* | 历史数据 |
ACT_ID_* | 身份数据 |
不要手动修改数据库表,应通过 API 操作。
第二章:Flowable 核心服务与 API 基础
2.1 ProcessEngine 与 Configuration 配置
| 方法/属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
ProcessEngines.init() | ProcessEngines.init(); | 初始化所有配置的 ProcessEngine 实例(基于 flowable.cfg.xml) | ProcessEngines.init(); ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); | 通常在应用启动时调用一次;Spring 环境下由容器自动管理,无需手动调用。 |
ProcessEngines.getProcessEngine(String name) | ProcessEngines.getProcessEngine("engineName"); | 获取指定名称的 ProcessEngine 实例 | ProcessEngine engine = ProcessEngines.getProcessEngine("default"); | 名称需与配置文件中 <process-engine-name> 一致。 |
ProcessEngines.destroy() | ProcessEngines.destroy(); | 销毁所有已初始化的 ProcessEngine,释放资源 | ProcessEngines.destroy(); | 应用关闭时调用,避免资源泄漏。 |
ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault() | ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault(); | 从默认配置文件 flowable.cfg.xml 创建配置对象 | ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault(); ProcessEngine engine = cfg.buildProcessEngine(); | 配置文件必须位于 classpath:flowable.cfg.xml。 |
ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration() | ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration(); | 创建独立运行的配置对象,适用于非 Spring 环境 | ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration(); cfg.setJdbcUrl("jdbc:h2:mem:flowable"); cfg.setJdbcUsername("sa"); cfg.setJdbcPassword(""); cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); cfg.setAsyncExecutorActivate(false); ProcessEngine engine = cfg.buildProcessEngine(); | 需手动设置数据源、事务管理器等。 |
cfg.setDatabaseSchemaUpdate(...) | cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); | 控制数据库表结构更新行为 | 参见上例 | 可选值:false(不更新)、true(启动时自动建表/更新)、create-drop(启动建表,关闭删表,测试用)。 |
cfg.setAsyncExecutorActivate(...) | cfg.setAsyncExecutorActivate(true); | 是否启用异步执行器(用于定时任务、异步服务任务) | 参见上例 | 生产环境建议开启,提高性能。 |
cfg.setHistoryLevel(...) | cfg.setHistoryLevel(HistoryLevel.FULL); | 设置历史数据记录级别 | cfg.setHistoryLevel(HistoryLevel.AUDIT); | 可选值:NONE、ACTIVITY、AUDIT、FULL;级别越高,存储越多,性能影响越大。 |
cfg.buildProcessEngine() | ProcessEngine engine = cfg.buildProcessEngine(); | 构建并返回 ProcessEngine 实例 | 参见上例 | 调用后会连接数据库并初始化表结构(若配置允许)。 |
说明: ProcessEngine 是 Flowable 的核心,所有服务(如 RepositoryService)都通过它获取。
2.2 核心服务接口概述(RepositoryService、RuntimeService 等)
| 服务接口 | 获取方式 | 用途 | 注意事项 |
|---|---|---|---|
| RepositoryService | processEngine.getRepositoryService() | 管理流程资源:部署流程、查询流程定义、挂起/激活流程等 | 是流程定义的”仓库”,不涉及运行时数据。 |
| RuntimeService | processEngine.getRuntimeService() | 管理运行时流程实例:启动实例、操作流程变量、查询执行流等 | 用于流程实例的生命周期控制。 |
| TaskService | processEngine.getTaskService() | 管理用户任务:查询、办理、委派、设置负责人等 | 仅对 userTask 有效,不能操作服务任务。 |
| HistoryService | processEngine.getHistoryService() | 查询历史数据:已完成的流程实例、任务、变量等 | 依赖 historyLevel 配置,否则无数据。 |
| ManagementService | processEngine.getManagementService() | 引擎管理和数据库操作:获取表元数据、执行原生 SQL、作业管理等 | 用于运维和监控,慎用原生 SQL。 |
| IdentityService | processEngine.getIdentityService() | 管理用户、组、权限关系 | 内置功能简单,生产环境建议与外部系统集成。 |
| FormService | processEngine.getFormService() | 管理流程表单:获取表单数据、提交表单等 | 支持动态表单,可与 Flowable UI 配合使用。 |
| DynamicBpmnService | processEngine.getDynamicBpmnService() | 动态修改流程定义行为(如任务名称、表达式),无需重新部署 | 仅支持部分属性,不能修改流程结构(如增删节点)。 |
说明: 这些服务是线程安全的,可在多线程环境中共享使用。
2.3 流程部署与资源管理
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
repositoryService.createDeployment() | repositoryService.createDeployment() | 创建一个部署构建器 | Deployment deployment = repositoryService.createDeployment().addClasspathResource("processes/simple-process.bpmn20.xml").name("请假流程").deploy(); | 每次调用 deploy() 会创建一次新部署。 |
.addClasspathResource(String resource) | .addClasspathResource("processes/demo.bpmn") | 添加类路径下的资源文件 | 同上 | 支持 BPMN、PNG、FORM 等文件。 |
.addInputStream(String resourceName, InputStream inputStream) | .addInputStream("dynamic.bpmn", inputStream) | 从输入流添加资源 | try (InputStream is = new FileInputStream("path/to/process.bpmn")) { repositoryService.createDeployment().addInputStream("dynamic.bpmn", is).deploy(); } | 适用于动态生成的流程文件。 |
.setDeploymentName(String name) | .setDeploymentName("报销流程V1") | 设置部署名称 | 同上 | 便于在历史记录中识别。 |
.setCategory(String category) | .setCategory("finance") | 设置部署分类 | repositoryService.createDeployment().addClasspathResource("expense.bpmn20.xml").category("finance").deploy(); | 可用于流程定义分类查询。 |
.deploy() | .deploy() | 执行部署操作,返回 Deployment 对象 | 同上 | 部署后流程定义即生效,可启动实例。 |
repositoryService.deleteDeployment(String deploymentId) | repositoryService.deleteDeployment("dep1000"); | 删除指定部署 | // 删除部署,若有运行中的实例会抛异常 repositoryService.deleteDeployment("dep1000"); // 强制删除,级联删除相关流程实例 repositoryService.deleteDeployment("dep1000", true); | 第二个参数 cascade=true 表示级联删除所有相关流程实例和历史数据。 |
repositoryService.getDeploymentResourceNames(String deploymentId) | List<String> names = repositoryService.getDeploymentResourceNames("dep1000"); | 获取某次部署包含的所有资源文件名 | List<String> resources = repositoryService.getDeploymentResourceNames("dep1000"); for (String name : resources) { System.out.println(name); } | 可用于判断是否包含 PNG 图或 FORM 文件。 |
repositoryService.getResourceAsStream(String deploymentId, String resourceName) | InputStream is = repositoryService.getResourceAsStream("dep1000", "simple-process.bpmn20.xml"); | 获取部署资源的输入流 | try (InputStream is = repositoryService.getResourceAsStream("dep1000", "diagram.png")) { // 处理图片流 } | 常用于返回流程图给前端展示。 |
2.4 流程定义查询与管理
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
repositoryService.createProcessDefinitionQuery() | repositoryService.createProcessDefinitionQuery() | 创建流程定义查询对象 | List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").latestVersion().list(); | 支持链式调用添加查询条件。 |
.processDefinitionKey(String key) | .processDefinitionKey("leave") | 按流程定义的 id(即 key)查询 | 同上 | 对应 BPMN 文件中 <process id="leave">。 |
.processDefinitionName(String name) | .processDefinitionName("请假流程") | 按流程定义名称查询 | ProcessDefinition def = repositoryService.createProcessDefinitionQuery().processDefinitionName("请假流程").singleResult(); | 名称可重复,建议结合 key 使用。 |
.latestVersion() | .latestVersion() | 只查询每个 key 的最新版本 | 同上 | 非常常用,避免查到旧版本。 |
.version(int version) | .version(2) | 按具体版本号查询 | ProcessDefinition def = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").version(2).singleResult(); | 用于精确查询某版本。 |
.deploymentId(String id) | .deploymentId("dep1000") | 按部署 ID 查询所属流程定义 | List<ProcessDefinition> defs = repositoryService.createProcessDefinitionQuery().deploymentId("dep1000").list(); | 一次部署可包含多个流程定义。 |
.suspended() / .active() | .suspended() | 查询已挂起或激活状态的流程定义 | List<ProcessDefinition> suspendedDefs = repositoryService.createProcessDefinitionQuery().suspended().list(); | 挂起后无法启动新实例。 |
.count() | .count() | 返回查询结果数量 | long count = repositoryService.createProcessDefinitionQuery().active().count(); | 用于分页统计。 |
.list() / .listPage(int first, int max) | .list() / .listPage(0, 10) | 返回结果列表或分页结果 | List<ProcessDefinition> page = repositoryService.createProcessDefinitionQuery().orderByProcessDefinitionVersion().asc().listPage(0, 10); | 分页避免内存溢出。 |
.singleResult() | .singleResult() | 返回唯一结果,若无或多个则返回 null 或抛异常 | ProcessDefinition def = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").latestVersion().singleResult(); | 适用于精确查询场景。 |
repositoryService.suspendProcessDefinitionById(String id) | repositoryService.suspendProcessDefinitionById("leave:2:123") | 挂起指定流程定义 | repositoryService.suspendProcessDefinitionById("leave:2:123"); | 挂起后不能启动新实例,但已有实例可继续执行。 |
repositoryService.activateProcessDefinitionById(String id) | repositoryService.activateProcessDefinitionById("leave:2:123") | 激活已挂起的流程定义 | repositoryService.activateProcessDefinitionById("leave:2:123"); | 激活后可重新启动新实例。 |
repositoryService.deleteProcessDefinition(String id) | repositoryService.deleteProcessDefinition("leave:2:123") | 删除指定流程定义 | 不允许直接删除,需通过删除部署实现 | 流程定义不可单独删除,必须删除其所属部署。 |
说明: 流程定义的 id 格式为
key:version:generated-id,例如leave:3:12345。
第三章:流程定义与 BPMN 2.0 元素
3.1 流程定义结构(process、id、name、version)
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
<process> 元素 | BPMN 2.0 文件中的根元素之一,定义一个完整的业务流程。包含任务、网关、事件等节点和连接线。 | 必须位于 .bpmn20.xml 文件中,且文件根为 <definitions>。 |
| id 属性 | 流程的唯一标识符(Key),在代码中用于启动流程、查询定义等。对应 Java 中的 key。 | 只能包含字母、数字、连字符和下划线;一旦设定不建议修改;同一 id 多次部署会生成不同版本。 |
| name 属性 | 流程的可读名称,用于展示给用户,无技术约束。 | 可包含中文、空格等,便于业务人员理解。 |
| isExecutable 属性 | 布尔值,表示该流程是否可执行。true 表示可被引擎解析运行,false 仅用于建模或文档。 | 非可执行流程不会被部署到运行时。 |
| version | 流程定义的版本号,由引擎自动生成。每次部署相同 id 的流程时版本递增。 | 版本号从 1 开始;可通过 latestVersion() 查询最新版本。 |
| key 与 id 的关系 | Flowable 中 process id 即为流程定义的 key,用于标识一类流程。 | 不要与数据库主键混淆;key + version 才能唯一确定一个流程定义。 |
| 流程定义 ID(Full ID) | 格式为 key:version:generated-id,例如 leave:2:12345,是数据库中的唯一标识。 | 在 API 中常作为参数使用,如挂起、查询等操作。 |
3.2 开始事件与结束事件
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 开始事件(Start Event) | 流程的入口,表示流程实例的触发点。必须有且仅有一个(不包括事件子流程)。 | 类型包括:空开始事件、消息开始事件、定时开始事件等。 |
| 空开始事件(None Start Event) | 最常见的开始事件,无特定触发条件,调用 startProcessInstanceByKey() 即可启动。 | 适用于人工触发的流程,如”提交请假申请”。 |
| 消息开始事件(Message Start Event) | 通过接收指定名称的消息来启动流程。 | 需调用 startProcessInstanceByMessage() 触发;可用于外部系统集成。 |
| 定时开始事件(Timer Start Event) | 按照设定的时间规则(如 cron 表达式)自动启动流程。 | 仅支持顶级流程(非子流程);需启用异步执行器。 |
| 结束事件(End Event) | 表示流程或子流程的终止点。到达结束事件后,流程实例或执行流结束。 | 可有多个结束事件,表示不同终止路径。 |
| 空结束事件(None End Event) | 默认结束事件,表示正常结束,不抛出异常。 | 到达后流程实例状态变为”已完成”。 |
| 错误结束事件(Error End Event) | 抛出一个错误,可被边界错误事件或错误事件子流程捕获。 | 必须设置 errorRef 属性;用于异常处理流程。 |
| 终止结束事件(Terminate End Event) | 立即终止整个流程实例,包括所有并行分支。 | 使用需谨慎,可能导致未完成任务被丢弃。 |
3.3 用户任务(User Task)与任务分配
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 用户任务(User Task) | 需要人工参与完成的任务,如审批、填写表单等。在任务列表中可见。 | 对应 TaskService 操作。 |
| assignee 属性 | 指定任务的办理人,格式为用户 ID。任务直接分配给该用户。 | 语法:assignee="zhangsan";该用户可在个人任务列表中看到任务。 |
| candidateUsers 属性 | 指定任务的候选人列表,任务出现在这些用户的”可领取任务”列表中。 | 语法:candidateUsers="zhangsan,lisi";需先 claim 才能办理。 |
| candidateGroups 属性 | 指定任务的候选组,组内所有用户均可领取任务。 | 语法:candidateGroups="managers";依赖身份服务中的组信息。 |
| formKey 属性 | 关联一个表单模板,用于前端渲染任务表单。 | 语法:formKey="leaveForm";可通过 FormService 获取表单数据。 |
| dueDate 属性 | 设置任务的截止日期,可用于提醒或超时处理。 | 支持表达式,如 ${dateUtils.addDays(now, 3)}。 |
| priority 属性 | 设置任务优先级(0-100),数字越大优先级越高。 | 默认为 50;可用于任务排序。 |
| 任务分配表达式 | 使用 ${} 表达式动态设置 assignee 或 candidateUsers。 | 示例:assignee="${initiator}" 或 candidateUsers="${approverList}";变量需在启动流程时传入。 |
3.4 排他网关(Exclusive Gateway)与流程分支
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 排他网关(Exclusive Gateway) | 也称”决策网关”,根据条件表达式选择一条 outgoing 路径继续执行。 | 图标为菱形内部带 ‘X’;只选择一条路径。 |
| 条件表达式(Condition Expression) | 设置在顺序流(Sequence Flow)上的表达式,决定是否选择该路径。 | 语法:${variable == 'value'};使用 Spring EL 表达式。 |
| 默认流(Default Flow) | 当所有条件都不满足时,执行的默认路径。通过 default 属性指定。 | 语法:在网关上设置 default="flow2",指向某条 sequenceFlow。 |
| 流程分支 | 排他网关后接多条顺序流,形成”if-else”结构。 | 所有 outgoing 流必须有唯一条件,或设置默认流。 |
| 条件求值顺序 | 引擎按顺序评估每条路径的条件,选择第一个为 true 的路径。 | 建议将最可能满足的条件放在前面。 |
| 无默认流的风险 | 若无默认流且所有条件为 false,流程将阻塞,无路径可执行。 | 必须确保至少有一条路径可执行,否则流程卡住。 |
3.5 并行网关(Parallel Gateway)与流程汇聚
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 并行网关(Parallel Gateway) | 用于分叉(Fork)或汇聚(Join)并行执行流。图标为菱形内部带 ’+‘。 | 同一个网关可同时作为分叉和汇聚点。 |
| 并行分叉(Parallel Fork) | 将一个执行流拆分为多个并行执行流,所有 outgoing 路径同时执行。 | 无需条件表达式;所有路径都会被执行。 |
| 并行汇聚(Parallel Join) | 等待所有 incoming 执行流完成,然后合并为一个执行流继续。 | 必须等待所有分支完成,否则阻塞。 |
| 汇聚行为 | 所有来自分叉的执行流必须全部到达汇聚网关,才能继续向下执行。 | 若某一分支未完成,其他分支即使完成也需等待。 |
| 使用场景 | 适用于可并行处理的任务,如”财务审批”和”人事审批”同时进行。 | 不能用于条件分支的汇聚,应使用”包容网关”(Inclusive Gateway)。 |
| 错误使用示例 | 在汇聚网关的 incoming 流上设置条件表达式。 | 并行网关的 incoming 流不允许有条件,否则行为未定义。 |
3.6 服务任务(Service Task)与 JavaDelegate
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 服务任务(Service Task) | 自动执行的任务,由系统完成,无需人工干预。如调用外部 API、发送邮件等。 | 图标为圆角矩形底部带齿轮。 |
| class 属性 | 指定实现 JavaDelegate 接口的 Java 类。 | 语法:class="com.example.SendEmailDelegate";类必须有无参构造函数。 |
| expression 属性 | 使用表达式调用 Spring Bean 的方法。 | 语法:expression="${emailService.sendMail(execution)}";需在 Spring 中注册 Bean。 |
| delegateExpression 属性 | 表达式返回一个 JavaDelegate 实例。 | 语法:delegateExpression="${myDelegate}";更灵活,支持运行时决定。 |
| resultVariable 属性 | 将服务任务的执行结果存储到指定变量中。 | 语法:resultVariable="sendResult";默认覆盖 result 变量。 |
| JavaDelegate 接口 | 自定义逻辑的核心接口,实现 execute(DelegateExecution execution) 方法。 | 方法内可通过 execution 获取流程变量、设置变量等。 |
| 异步执行(async) | 设置 async="true" 可将服务任务放入异步队列执行。 | 需启用异步执行器;提高吞吐量,但增加复杂性。 |
| 排他执行(exclusive) | 设置 exclusive="true"(默认)表示同一流程实例的异步任务串行执行。 | 避免数据竞争;可设为 false 允许并行异步执行。 |
3.7 脚本任务(Script Task)与表达式
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 脚本任务(Script Task) | 执行一段脚本代码的任务,如 Groovy、JavaScript、Python 等。 | 图标为圆角矩形底部带文档和代码符号。 |
| scriptFormat 属性 | 指定脚本语言类型,如 groovy、javascript。 | 语法:scriptFormat="groovy";需 JVM 支持该语言引擎。 |
| script 内容 | 在 BPMN 文件中直接编写脚本代码。 | 示例:<script>execution.setVariable("x", 10);</script>。 |
| autoStoreVariables 属性 | 是否自动将脚本中定义的变量保存到流程变量中。 | true 时,Groovy 脚本中 x=1 会自动存为流程变量。 |
| 表达式(Expression) | 在条件、赋值等场景使用 ${} 语法,基于 Spring EL。 | 示例:${order.amount > 1000} 用于网关条件判断。 |
| 变量访问 | 脚本中可通过 execution 对象访问流程变量和 API。 | Groovy 示例:def user = execution.getVariable("assignee")。 |
| 性能与安全 | 脚本任务性能低于 JavaDelegate,且存在安全风险。 | 不建议在生产环境使用复杂脚本或动态代码。 |
3.8 接收任务(Receive Task)与外部信号
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 接收任务(Receive Task) | 等待外部系统显式触发才能继续的等待任务。用于集成异步回调。 | 图标为圆角矩形底部带信封。 |
| waitStateType | 内部状态类型为 receive-task,表示处于等待状态。 | 任务不会自动完成。 |
| 完成方式 | 必须通过 API 调用 signalExecutionById() 或 trigger() 来继续流程。 | 示例:runtimeService.trigger(executionId);。 |
| 使用场景 | 适用于”等待第三方支付结果”、“等待人工确认”等场景。 | 避免长时间阻塞,建议结合超时边界事件。 |
| 与用户任务区别 | 接收任务无办理人概念,不出现于任务列表,纯系统级等待。 | 用户任务需人工领取和完成。 |
| 异步特性 | 接收任务天然异步,流程执行流在此暂停,释放线程。 | 适合高并发场景下的资源优化。 |
3.9 子流程与调用活动(Call Activity)
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 内嵌子流程(Sub-Process) | 在主流程内部定义的子流程,可包含自己的任务、网关等。 | 与主流程共享变量和执行流。 |
| 折叠与展开 | 子流程可折叠显示为一个节点,点击展开查看详情。 | 用于简化复杂流程图。 |
| 调用活动(Call Activity) | 调用另一个独立的流程定义(被调用流程),实现流程复用。 | 图标为圆角矩形底部带加号。 |
| calledElement 属性 | 指定被调用流程的 key。 | 语法:calledElement="auditProcess";被调用流程必须已部署。 |
| 变量传递 | 启动被调用流程时,可传递变量;结束时可返回变量。 | 通过 in 和 out 映射变量。 |
| in 映射 | 将主流程变量传递给被调用流程。 | 语法:<extensionElements><in source="x" target="y"/></extensionElements>。 |
| out 映射 | 将被调用流程的变量返回给主流程。 | 语法:<out source="result" target="finalResult"/>。 |
| 独立流程实例 | 被调用流程会创建独立的流程实例,但生命周期受主流程控制。 | 主流程实例未完成时,被调用流程实例状态为”运行中”。 |
| 错误传播 | 被调用流程抛出错误可被主流程的边界事件捕获。 | 支持跨流程异常处理。 |
第四章:流程实例与运行时管理
4.1 启动流程实例(RuntimeService)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
startProcessInstanceByKey | runtimeService.startProcessInstanceByKey(String processDefinitionKey) | 使用流程定义的 key 启动最新版本的流程实例 | ProcessInstance instance = runtimeService.startProcessInstanceByKey("leave"); | 最常用方法;自动选择最新版本。 |
startProcessInstanceById | runtimeService.startProcessInstanceById(String processDefinitionId) | 使用流程定义的完整 ID(含版本)启动特定版本的流程实例 | ProcessInstance instance = runtimeService.startProcessInstanceById("leave:2:12345"); | 适用于需要精确控制版本的场景。 |
| 启动时传入变量 | 方法重载支持 Map<String, Object> variables 参数 | 在启动流程时设置初始流程变量 | Map<String, Object> vars = new HashMap<>(); vars.put("applicant", "zhangsan"); vars.put("days", 3); ProcessInstance instance = runtimeService.startProcessInstanceByKey("leave", vars); | 变量可用于后续任务分配、条件判断等。 |
| 设置业务键(businessKey) | startProcessInstanceByKey(String key, String businessKey) | 为流程实例关联一个业务标识,如订单号、请假单号 | ProcessInstance instance = runtimeService.startProcessInstanceByKey("leave", "REQ-2025-001"); | 业务键应唯一标识业务实体,便于查询和关联。 |
| 使用消息启动 | startProcessInstanceByMessage(String messageName) | 通过消息事件启动流程(需定义消息开始事件) | ProcessInstance instance = runtimeService.startProcessInstanceByMessage("startLeave"); | 适用于异步触发或外部系统集成。 |
| 返回值:ProcessInstance | 方法返回 ProcessInstance 对象 | 包含流程实例的 ID、流程定义 ID、业务键等信息 | String procId = instance.getId(); String procDefId = instance.getProcessDefinitionId(); | 可用于后续操作,如查询变量、挂起实例等。 |
4.2 流程变量(Variables)的使用与作用域
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
setVariable | runtimeService.setVariable(String executionId, String variableName, Object value) | 在指定执行流上设置变量 | runtimeService.setVariable(executionId, "approved", true); | 变量存储在 ACT_RU_VARIABLE 表中。 |
setVariables | runtimeService.setVariables(String executionId, Map<String, Object> variables) | 批量设置多个变量 | Map<String, Object> vars = Map.of("user", "lisi", "role", "manager"); runtimeService.setVariables(executionId, vars); | 提高效率,减少数据库操作次数。 |
getVariable | runtimeService.getVariable(String executionId, String variableName) | 获取指定执行流上的变量值 | Boolean approved = (Boolean) runtimeService.getVariable(executionId, "approved"); | 若变量不存在,返回 null。 |
getVariables | runtimeService.getVariables(String executionId) | 获取指定执行流上的所有变量 | Map<String, Object> allVars = runtimeService.getVariables(executionId); | 返回 Map<String, Object>。 |
getVariableLocal | runtimeService.getVariableLocal(String executionId, String variableName) | 获取执行流本地的变量(不从父级继承) | String localVal = (String) runtimeService.getVariableLocal(executionId, "temp"); | 用于隔离变量作用域。 |
setVariableLocal | runtimeService.setVariableLocal(String executionId, String variableName, Object value) | 设置仅在当前执行流有效的本地变量 | runtimeService.setVariableLocal(executionId, "tempData", "tmp"); | 子执行流无法访问,但父级也无法直接访问。 |
变量作用域与类型说明:
| 特性 | 说明 |
|---|---|
| 变量作用域 | 变量可存在于不同执行流中,支持继承:子执行流可访问父执行流的变量。在并行分支中,各分支有独立的变量副本,修改本地变量不影响其他分支。 |
| 变量类型 | 支持基本类型、String、Serializable 对象、POJO(需序列化)。复杂对象会序列化存储。对于大对象,建议存储 ID 而非完整对象,避免序列化性能开销和数据库存储压力。 |
| 变量可见性 | 在表达式、条件、脚本中可通过 ${varName} 访问,例如网关条件 ${days > 5}。确保变量在作用域内可访问。 |
4.3 查询运行中的流程实例
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
createProcessInstanceQuery | runtimeService.createProcessInstanceQuery() | 创建流程实例查询对象 | ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery(); | 支持链式调用添加查询条件。 |
.processInstanceId | .processInstanceId(String procInstId) | 按流程实例 ID 精确查询 | ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId("5001").singleResult(); | 适用于已知 ID 的精确查询。 |
.processDefinitionKey | .processDefinitionKey(String key) | 按流程定义的 key 查询 | List instances = runtimeService.createProcessInstanceQuery().processDefinitionKey("leave").list(); | 可查询该 key 下所有版本的运行中实例。 |
.processDefinitionId | .processDefinitionId(String procDefId) | 按流程定义的完整 ID 查询 | List instances = runtimeService.createProcessInstanceQuery().processDefinitionId("leave:2:12345").list(); | 精确到特定版本。 |
.businessKey | .businessKey(String businessKey) | 按业务键查询 | ProcessInstance instance = runtimeService.createProcessInstanceQuery().businessKey("REQ-2025-001").singleResult(); | 便于与业务系统关联。 |
.variableValueEquals | .variableValueEquals(String varName, Object varValue) | 查询变量等于指定值的流程实例 | List instances = runtimeService.createProcessInstanceQuery().variableValueEquals("applicant", "zhangsan").list(); | 支持基本类型和 String。 |
.active | .active() | 只查询处于活动状态的流程实例 | List activeInstances = runtimeService.createProcessInstanceQuery().active().list(); | 排除已挂起的实例。 |
.suspended | .suspended() | 查询已挂起的流程实例 | List suspended = runtimeService.createProcessInstanceQuery().suspended().list(); | 挂起的实例不能继续执行。 |
.list / .listPage | .list() / .listPage(int first, int max) | 返回查询结果列表或分页结果 | List page = runtimeService.createProcessInstanceQuery().listPage(0, 10); | 分页避免内存溢出,适用于大数据量。 |
.count | .count() | 返回满足条件的流程实例数量 | long count = runtimeService.createProcessInstanceQuery().processDefinitionKey("leave").count(); | 用于分页统计总数。 |
.singleResult | .singleResult() | 返回唯一结果,若无或多个则返回 null 或抛异常 | ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId("5001").singleResult(); | 适用于精确查询场景。 |
4.4 挂起与激活流程定义
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
suspendProcessDefinitionById | repositoryService.suspendProcessDefinitionById(String processDefinitionId) | 挂起指定 ID 的流程定义 | repositoryService.suspendProcessDefinitionById("leave:2:12345"); | 挂起后无法启动新实例。 |
activateProcessDefinitionById | repositoryService.activateProcessDefinitionById(String processDefinitionId) | 激活已挂起的流程定义 | repositoryService.activateProcessDefinitionById("leave:2:12345"); | 激活后可重新启动新实例。 |
suspendProcessDefinitionByKey | repositoryService.suspendProcessDefinitionByKey(String processDefinitionKey) | 挂起指定 key 的所有版本流程定义 | repositoryService.suspendProcessDefinitionByKey("leave"); | 批量操作,影响所有版本。 |
activateProcessDefinitionByKey | repositoryService.activateProcessDefinitionByKey(String processDefinitionKey) | 激活指定 key 的所有版本流程定义 | repositoryService.activateProcessDefinitionByKey("leave"); | 恢复所有版本的可用性。 |
| 挂起时启动实例 | 尝试启动被挂起的流程定义 | 会抛出 FlowableException | // 抛异常 runtimeService.startProcessInstanceByKey("suspendedProcess"); | 必须先激活才能启动。 |
| 已有实例的影响 | 挂起流程定义不影响已启动的流程实例 | 已运行的实例可继续执行至完成 | repositoryService.suspendProcessDefinitionByKey("leave"); // 正在运行的实例不受影响 | 挂起是”冷启动”控制。 |
| 状态查询 | 查询流程定义状态 | 使用 ProcessDefinitionQuery 的 .suspended() 或 .active() | ProcessDefinition def = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").latestVersion().singleResult(); boolean suspended = def.isSuspended(); | isSuspended() 返回布尔值。 |
4.5 删除与终止流程实例
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
deleteProcessInstance | runtimeService.deleteProcessInstance(String processInstanceId, String deleteReason) | 删除指定的流程实例 | runtimeService.deleteProcessInstance("5001", "流程错误,需要重新提交"); | 删除后实例进入历史表,状态为”已删除”。 |
| 删除原因 | 第二个参数为删除原因 | 记录在历史数据中,用于审计 | 原因可为任意字符串 | 建议填写有意义的原因。 |
| 强制删除运行中实例 | 直接调用 deleteProcessInstance | 可删除正在运行的实例 | runtimeService.deleteProcessInstance("5001", "cancel"); | 实例关联的任务也会被删除。 |
| 终止流程实例 | Flowable 无直接”终止”方法,通常通过删除实现 | 彻底结束一个流程实例 | 同上 | ”终止”和”删除”在 Flowable 中常视为同义。 |
| 删除与完成的区别 | delete 是强制移除,complete 是正常结束 | 正常完成的实例状态为”已完成” | 调用 taskService.complete(taskId) 完成任务,最终到达结束事件 | 删除不会触发结束事件。 |
| 级联删除 | 删除部署时可级联删除相关实例 | 通过 repositoryService.deleteDeployment(deploymentId, true) | repositoryService.deleteDeployment("dep1000", true); | true 表示级联删除所有相关流程实例和历史数据。 |
| 历史数据保留 | 删除实例后,历史数据默认仍保留 | 可通过 HistoryService 查询 | HistoricProcessInstance historic = historyService.createHistoricProcessInstanceQuery().processInstanceId("5001").singleResult(); | 若需彻底清理,需手动清理历史表或配置自动清理策略。 |
| 无法部分删除 | 不能删除流程实例的某个分支或任务 | 只能删除整个流程实例 | 无对应 API | 需通过流程设计避免。 |
第五章:用户任务与任务管理
5.1 任务的创建与分配(Assignee、Candidate Users/Groups)
| 方法/属性名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| assignee 属性 | <userTask id="task1" flowable:assignee="zhangsan" /> | 直接指定任务办理人 | 在 BPMN 文件中设置 flowable:assignee="${approver}" | 任务将出现在该用户的个人任务列表中,无需领取。 |
| candidateUsers 属性 | <userTask id="task1" flowable:candidateUsers="zhangsan,lisi" /> | 指定任务候选人列表 | 支持表达式:flowable:candidateUsers="${userList}" | 候选人需先”领取”任务(claim)才能办理。 |
| candidateGroups 属性 | <userTask id="task1" flowable:candidateGroups="managers" /> | 指定任务候选组 | 支持表达式:flowable:candidateGroups="${dept}.reviewers" | 组内所有用户均可领取任务,依赖 IdentityService 中的组信息。 |
setAssignee 方法 | taskService.setAssignee(String taskId, String userId) | 运行时动态指定任务负责人 | taskService.setAssignee("task5001", "lisi"); | 可将任务从候选状态直接分配给某人,或变更负责人。 |
addCandidateUser 方法 | taskService.addCandidateUser(String taskId, String userId) | 为任务动态添加候选人 | taskService.addCandidateUser("task5001", "wangwu"); | 可在流程运行中扩展任务可办范围。 |
addCandidateGroup 方法 | taskService.addCandidateGroup(String taskId, String groupId) | 为任务动态添加候选组 | taskService.addCandidateGroup("task5001", "hr"); | 动态扩展组权限。 |
deleteCandidateUser 方法 | taskService.deleteCandidateUser(String taskId, String userId) | 移除任务的候选人 | taskService.deleteCandidateUser("task5001", "zhangsan"); | 用于权限调整。 |
deleteCandidateGroup 方法 | taskService.deleteCandidateGroup(String taskId, String groupId) | 移除任务的候选组 | taskService.deleteCandidateGroup("task5001", "interns"); | 与上类似。 |
| 任务分配表达式 | ${expression} | 在 BPMN 中使用表达式动态计算分配对象 | flowable:assignee="${initiator}" | 表达式在任务创建时求值,变量需在流程启动时传入。 |
5.2 任务查询(TaskQuery)与过滤
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
createTaskQuery | taskService.createTaskQuery() | 创建任务查询对象 | TaskQuery query = taskService.createTaskQuery(); | 支持链式调用添加条件。 |
.taskId | .taskId(String taskId) | 按任务 ID 精确查询 | Task task = taskService.createTaskQuery().taskId("task5001").singleResult(); | 适用于已知 ID 的查询。 |
.taskAssignee | .taskAssignee(String assignee) | 查询指定办理人的个人任务 | List tasks = taskService.createTaskQuery().taskAssignee("zhangsan").list(); | 仅查已领取的任务。 |
.taskCandidateUser | .taskCandidateUser(String userId) | 查询某用户可领取的候选任务 | List candidates = taskService.createTaskQuery().taskCandidateUser("lisi").list(); | 结合 candidateUsers 或 candidateGroups 生效。 |
.taskCandidateGroup | .taskCandidateGroup(String groupId) | 查询某组可领取的任务 | List groupTasks = taskService.createTaskQuery().taskCandidateGroup("managers").list(); | 需用户属于该组。 |
.taskInvolvedUser | .taskInvolvedUser(String userId) | 查询与某用户相关的所有任务(包括办理、候选、参与) | List allTasks = taskService.createTaskQuery().taskInvolvedUser("zhangsan").list(); | 范围最广的个人任务查询。 |
.processInstanceId | .processInstanceId(String procInstId) | 查询属于某流程实例的任务 | List tasks = taskService.createTaskQuery().processInstanceId("5001").list(); | 用于流程调试或监控。 |
.processDefinitionKey | .processDefinitionKey(String procDefKey) | 查询属于某类流程的任务 | List leaveTasks = taskService.createTaskQuery().processDefinitionKey("leave").list(); | 可跨多个流程实例查询。 |
.taskDefinitionKey | .taskDefinitionKey(String taskKey) | 按任务在 BPMN 中的 ID 查询 | Task task = taskService.createTaskQuery().taskDefinitionKey("approveTask").singleResult(); | 适用于查找特定节点任务。 |
.taskName / .taskNameLike | .taskName("审批") / .taskNameLike("%审批%") | 按任务名称精确或模糊查询 | List tasks = taskService.createTaskQuery().taskNameLike("请假%").list(); | 名称支持国际化。 |
.taskCreatedAfter / .taskCreatedBefore | .taskCreatedAfter(date) | 按创建时间范围查询 | Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, -7); List recent = taskService.createTaskQuery().taskCreatedAfter(cal.getTime()).list(); | 用于查询近期任务。 |
.active / .suspended | .active() | 查询活动或挂起状态的任务 | List active = taskService.createTaskQuery().active().list(); | 挂起任务不能办理。 |
.orderByTaskCreateTime | .orderByTaskCreateTime().desc() | 按创建时间排序 | List sorted = taskService.createTaskQuery().orderByTaskCreateTime().desc().list(); | 常用排序方式,结合分页使用。 |
.list / .listPage / .count | .list() / .listPage(0, 10) / .count() | 返回结果列表、分页结果或数量 | long total = taskService.createTaskQuery().taskAssignee("zhangsan").count(); | 分页避免性能问题。 |
5.3 任务办理(claim、complete)与委派
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
claim | taskService.claim(String taskId, String userId) | 用户领取候选任务 | taskService.claim("task5001", "zhangsan"); | 领取后任务变为该用户的个人任务。 |
unclaim | taskService.unclaim(String taskId) | 取消领取,将任务变回候选状态 | taskService.unclaim("task5001"); | 需当前办理人为 null 或 ""。 |
complete | taskService.complete(String taskId) | 完成任务,流程继续执行 | taskService.complete("task5001"); | 任务必须有办理人,否则抛异常。 |
| complete 带变量 | taskService.complete(String taskId, Map<String, Object> variables) | 完成任务并设置流程变量 | Map<String, Object> vars = Map.of("approved", true, "comment", "同意"); taskService.complete("task5001", vars); | 变量可用于后续流程判断。 |
delegate | taskService.delegateTask(String taskId, String userId) | 将任务委派给他人办理 | taskService.delegateTask("task5001", "lisi"); | 原办理人仍为负责人,受委派人需完成任务后归还。 |
resolve | taskService.resolveTask(String taskId) | 完成委派任务,将任务归还给原负责人 | taskService.resolveTask("task5001"); | 原负责人可继续处理或再次委派。 |
setOwner | taskService.setOwner(String taskId, String userId) | 设置任务所有者(负责人) | taskService.setOwner("task5001", "manager"); | 所有者可查看任务,但不能直接办理。 |
| 办理前检查 | 查询任务状态 | 确保任务存在且未被办理 | Task task = taskService.createTaskQuery().taskId("task5001").singleResult(); if (task != null && "zhangsan".equals(task.getAssignee())) { taskService.complete("task5001"); } | 避免并发操作异常。 |
5.4 任务附件与评论
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
createAttachment | taskService.createAttachment(String attachmentType, String taskId, String processInstanceId, String fileName, String description, InputStream content) | 创建任务附件 | try (InputStream is = new FileInputStream("contract.pdf")) { taskService.createAttachment("document", "task5001", "5001", "合同.pdf", "签署的合同", is); } | attachmentType 常用 document 或 image。 |
getAttachment | taskService.getAttachment(String attachmentId) | 获取附件元数据 | Attachment attachment = taskService.getAttachment("att1001"); | 包含文件名、大小、类型等信息。 |
getAttachmentContent | taskService.getAttachmentContent(String attachmentId) | 获取附件内容流 | InputStream content = taskService.getAttachmentContent("att1001"); | 需手动关闭流。 |
getProcessInstanceAttachments | taskService.getProcessInstanceAttachments(String processInstanceId) | 获取某流程实例的所有附件 | List attachments = taskService.getProcessInstanceAttachments("5001"); | 跨任务聚合附件。 |
deleteAttachment | taskService.deleteAttachment(String attachmentId) | 删除附件 | taskService.deleteAttachment("att1001"); | 同时删除元数据和内容。 |
addComment | taskService.addComment(String taskId, String processInstanceId, String message) | 添加评论 | taskService.addComment("task5001", "5001", "请尽快处理。"); | 评论与任务和流程实例关联。 |
getProcessInstanceComments | taskService.getProcessInstanceComments(String processInstanceId) | 获取流程实例的所有评论 | List | 按时间顺序返回。 |
getTaskComments | taskService.getTaskComments(String taskId) | 获取某任务的所有评论 | List | 仅限该任务的评论。 |
deleteComment | taskService.deleteComment(String commentId, String processInstanceId) | 删除评论 | taskService.deleteComment("com1001", "5001"); | 需提供流程实例 ID。 |
5.5 任务监听器(TaskListener)与事件类型
| 概念名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| taskListener 元素 | <taskListener event="create" class="com.example.TaskCreateListener" /> | 在 BPMN 中定义任务监听器 | 支持 class、expression、delegateExpression | 在 <extensionElements> 中配置。 |
| event 属性 | event="create" | 指定监听的事件类型 | 可选值:create, assignment, complete, delete | 不同事件对应任务生命周期的不同阶段。 |
| create 事件 | event="create" | 任务创建时触发(在分配前) | 常用于初始化任务变量、发送通知 | 此时 assignee 可能为空。 |
| assignment 事件 | event="assignment" | 任务被分配(setAssignee 或 claim)时触发 | 可用于发送待办提醒邮件 | 事件触发时 assignee 已更新。 |
| complete 事件 | event="complete" | 任务完成前触发(在 complete 方法执行后,流程继续前) | 可用于记录日志、更新外部系统 | 可通过 delegateTask.getVariable() 获取变量。 |
| delete 事件 | event="delete" | 任务被删除时触发(如流程实例被删除) | 用于清理关联资源 | 不常见。 |
| Java 实现 TaskListener | public class MyTaskListener implements TaskListener | 自定义监听逻辑 | public void notify(DelegateTask delegateTask) { String eventName = delegateTask.getEventName(); if ("create".equals(eventName)) { delegateTask.setVariable("createTime", new Date()); } } | 实现 notify 方法处理事件。 |
| 表达式监听器 | expression="${mailService.sendNotification(task)}" | 使用表达式调用服务 | 需在 Spring 中注册 mailService Bean | 更灵活,无需编写 Java 类。 |
| 委托表达式 | delegateExpression="${myTaskListener}" | 表达式返回一个 TaskListener 实例 | 支持运行时动态决定监听器 | 推荐用于复杂场景。 |
| 监听器执行上下文 | DelegateTask 对象 | 提供任务和流程的上下文信息 | 可获取任务 ID、名称、办理人、流程变量等 | 是监听器编程的核心对象。 |
| 异常处理 | 监听器中抛出异常 | 会中断流程执行 | try { ... } catch (Exception e) { log.error("监听器执行失败", e); } | 建议在监听器内捕获并处理异常。 |
第六章:流程控制与高级行为
6.1 执行流控制(Execution)与异步执行
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Execution 对象 | 表示流程中的一个执行路径,可对应主流程、并行分支或子流程。 | runtimeService.createExecutionQuery() | Execution execution = runtimeService.createExecutionQuery().processInstanceId(procInstId).activityId("userTask1").singleResult(); | 一个流程实例可有多个执行流(如并行网关后)。 |
| 查询执行流 | 根据流程实例 ID、活动 ID 等条件查询执行流 | createExecutionQuery() 支持 .processInstanceId(), .activityId(), .parentId() 等 | List executions = runtimeService.createExecutionQuery().processInstanceId("5001").list(); | 用于定位特定执行路径,支持流程跳转。 |
| 异步执行(async) | 将任务或流程节点放入异步执行器队列,由后台线程处理,不阻塞主线程。 | 在 BPMN 中设置 flowable:async="true" | <serviceTask id="sendEmail" flowable:class="SendEmailDelegate" flowable:async="true" /> | 需启用异步执行器(asyncExecutor)。 |
| 排他执行(exclusive) | 控制异步任务的执行顺序。exclusive="true"(默认)表示同一流程实例的异步任务串行执行。 | flowable:exclusive="true" | 可避免同一实例的数据竞争。 | 设为 false 可允许并行执行,提高吞吐量但需自行处理并发。 |
| 异步执行器配置 | 异步任务由独立线程池处理,可配置线程数、队列等。 | 在 flowable.cfg.xml 或 Spring 配置中设置 | <property name="asyncExecutorActivate" value="true"/> | 生产环境建议启用并合理配置线程池。 |
| 异步任务状态 | 异步任务在 ACT_RU_JOB 表中创建为 job,等待执行器触发。 | — | — | 可通过 ManagementService 查询和管理 job。 |
| 异步与性能 | 异步执行可显著提高流程启动和任务处理的吞吐量。 | — | — | 适用于耗时操作(如发送邮件、调用外部 API)。 |
6.2 事件监听器(ExecutionListener)
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| executionListener 元素 | 用于监听流程执行过程中的事件,如流程启动、活动开始/结束等。 | 在 <process> 或 <sequenceFlow> 等元素的 <extensionElements> 中定义 | <extensionElements><flowable:executionListener event="start" class="com.example.ProcessStartListener" /></extensionElements> | 可在流程级、活动级或连线级配置。 |
| event 属性 | 指定监听的事件类型 | 可选值:start, end, take(仅用于连线) | event="end" 表示活动或流程结束时触发 | start 在活动创建后触发,end 在活动完成后触发。 |
| class 属性 | 指定实现 ExecutionListener 接口的 Java 类 | class="com.example.MyExecutionListener" | public class MyExecutionListener implements ExecutionListener { public void notify(DelegateExecution execution) { if ("start".equals(execution.getEventName())) { execution.setVariable("startTime", new Date()); } } } | 类必须有无参构造函数。 |
| expression 属性 | 使用表达式调用 Spring Bean 方法 | expression="${auditService.logStart(execution)}" | 需在 Spring 中注册 auditService Bean | 更灵活,无需实现接口。 |
| delegateExpression 属性 | 表达式返回一个 ExecutionListener 实例 | delegateExpression="${myListener}" | 支持运行时动态决定监听器 | 推荐用于复杂场景。 |
| 流程级监听器 | 监听整个流程的 start 和 end 事件 | 在 <process> 元素上配置 | 适用于流程生命周期的日志记录、资源初始化等 | 一个流程可配置多个监听器。 |
| 活动级监听器 | 监听特定任务或网关的 start 和 end | 在 <userTask>, <serviceTask> 等元素上配置 | 适用于特定节点的前置/后置处理 | 如在用户任务 start 时发送通知。 |
| 连线监听器(take) | 在顺序流被选择时触发 | event="take" | 适用于记录流程走向或动态调整变量 | 只能在 <sequenceFlow> 上配置。 |
| 执行上下文 | DelegateExecution 对象提供流程实例、变量、执行流等信息 | 在 notify 方法中使用 | 可通过 execution.getProcessInstanceId(), execution.getVariable("x") 等方法获取数据 | 是监听器编程的核心。 |
6.3 流程跳转与动态流程(Dynamic Process Changes)
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| createChangeActivityIdBuilder | 创建动态流程变更构建器,用于修改当前执行流的目标活动。 | runtimeService.createChangeActivityIdBuilder() | RuntimeService runtimeService = processEngine.getRuntimeService(); runtimeService.createChangeActivityIdBuilder().processInstanceId("5001").moveExecutionToActivityId("executionId", "newTask").change(); | 需要执行流 ID 和目标活动 ID。 |
| 流程跳转(Jump) | 在运行时强制将执行流从当前节点跳转到另一个节点,绕过中间流程。 | 使用 ChangeActivityIdBuilder | — | 可用于”加签”、“跳过审批”等特殊场景。 |
| moveExecutionToActivityId | 指定将某个执行流移动到目标活动 | .moveExecutionToActivityId(executionId, targetActivityId) | 多个执行流可同时跳转 | 适用于并行流程的动态调整。 |
| change | 执行变更操作 | .change() | — | 调用后立即生效,流程继续执行。 |
| 使用场景 | 应急处理、流程纠错、特殊审批路径 | — | 如领导特批,跳过部门经理审批 | 需谨慎使用,避免破坏流程完整性。 |
| 限制条件 | 目标活动必须是可达的,且不能违反流程结构 | 不能跳转到已结束的分支或不兼容的节点 | — | 引擎会进行基本校验,但仍需业务逻辑保证合理性。 |
| 历史记录 | 跳转操作会被记录在历史表中 | 可通过 HistoricActivityInstance 查询 | 跳过的节点状态为 CANCELLED | 便于审计和追溯。 |
| 与变量结合 | 跳转前后可设置变量控制流程逻辑 | 在跳转前设置 skipReason 变量 | — | 增强流程的可解释性。 |
6.4 流程超时与定时边界事件(Boundary Timer Event)
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 定时边界事件(Boundary Timer Event) | 附加在任务上的定时器,当时间到时触发中断或非中断流程。 | 图形:附着在任务边缘的钟表图标 | <boundaryEvent id="timer1" attachedToRef="userTask1"><timerEventDefinition><timeDuration>PT2H</timeDuration></timerEventDefinition></boundaryEvent> | 用于实现任务超时。 |
| timeDuration | 设置相对时间,如”2小时后” | <timeDuration>PT2H</timeDuration> | PT2H = 2小时,P1D = 1天 | 常用于 SLA 超时提醒。 |
| timeDate | 设置绝对时间,如”2025-10-01T12:00:00” | <timeDate>2025-10-01T12:00:00</timeDate> | 适用于固定时间触发 | 需确保系统时间准确。 |
| cron 表达式 | 设置周期性或复杂时间规则 | <timeCycle>0 0 9 * * ?</timeCycle> | 每天上午9点触发 | 功能强大,但需熟悉 cron 语法。 |
| 中断性(cancelActivity) | 默认 true,定时器触发后中断原任务;false 则不中断,形成并行分支。 | cancelActivity="true" | 中断性常用于超时升级;非中断性用于提醒。 | 根据业务需求选择。 |
| 超时处理流程 | 定时器触发后,流程沿边界事件的 outgoing 流继续执行。 | 可连接服务任务发送通知,或用户任务升级审批 | — | 实现”2小时未处理,通知主管”等逻辑。 |
| 与异步执行器 | 定时事件由异步执行器(Job Executor)管理 | 需启用 asyncExecutor | — | 若未启用,定时器不会触发。 |
| 查询定时任务 | 通过 ManagementService 查询待处理的 job | List jobs = managementService.createJobQuery().list(); | 可用于监控和调试 | job 类型为 timer。 |
| 删除流程实例时 | 流程实例被删除时,关联的定时器 job 也会被自动删除 | — | — | 无需手动清理。 |
6.5 补偿事件与补偿处理
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 补偿事件(Compensation Event) | 用于撤销或补偿已执行的操作,通常在事务失败后触发。 | 图形:内部带两条曲线的圆圈 | 需配合 subProcess 的 isForCompensation="true" 使用 | 实现”回滚”机制。 |
| 补偿边界事件(Compensation Boundary Event) | 附加在活动上,当补偿被触发时执行补偿逻辑。 | attachedToRef="serviceTask1" | <boundaryEvent id="compensate1" attachedToRef="reserveStock"><compensateEventDefinition /></boundaryEvent> | 用于补偿特定服务任务。 |
| compensateEventDefinition | 定义补偿事件,可指向需要补偿的活动 | <compensateEventDefinition activityRef="reserveStock" /> | activityRef 指定被补偿的任务 | 若无 activityRef,则补偿所有可补偿的活动。 |
| 可补偿活动(Compensatable Activity) | 标记为可补偿的活动(如 serviceTask),其补偿处理器会在补偿事件触发时执行。 | 设置 isForCompensation="true" | <serviceTask id="cancelOrder" flowable:class="CancelOrderDelegate" isForCompensation="true" /> | 通常是一个反向操作。 |
| 错误事件子流程(Error Event Sub-Process) | 常用于捕获错误并触发补偿 | 顶级子流程,triggeredByEvent="true" | 捕获错误后,抛出 compensate 事件 | 实现”下单失败,取消库存预留”流程。 |
| 补偿执行顺序 | 补偿按与原执行顺序相反的顺序进行 | 先执行的活动后补偿 | — | 符合事务回滚的逻辑。 |
| 补偿范围 | 可补偿整个子流程或特定任务 | 通过 activityRef 控制范围 | — | 设计时需明确补偿边界。 |
| 使用场景 | 分布式事务、预留资源的释放、支付退款等 | — | 如”支付失败,释放库存” | 是实现最终一致性的重要机制。 |
| 限制 | 补偿事件不能跨流程实例 | 只能在当前流程实例内触发和执行 | — | 不能用于调用活动的被调用流程。 |
第七章:表达式与脚本支持
7.1 Spring EL 表达式在 Flowable 中的应用
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Spring EL(Spring Expression Language) | Flowable 默认的表达式语言,功能强大,支持方法调用、属性访问、运算符等。 | ${expression} | ${days > 5},${user.department == 'IT'} | 需在 Spring 环境中使用以获得完整支持。 |
| 变量访问 | 直接通过变量名访问流程变量 | ${variableName} | ${applicant},${totalAmount} | 变量需在流程作用域内存在。 |
| 属性访问 | 访问对象的属性(getter 方法) | ${object.property} | ${order.customer.name} | 支持链式访问。 |
| 方法调用 | 调用对象的方法 | ${object.method()} | ${userService.getManager(applicant)} | 方法必须是 public 且可被 Spring 管理。 |
| 运算符 | 支持算术、关系、逻辑、三元等运算符 | + - * / %,> < >= <= == !=,`&& | !,?:` | |
| 集合操作 | 支持 List、Map 的访问和操作 | list[0],map['key'] | ${approvers[0]},${config['timeout']} | 可用于动态数据处理。 |
| 类型转换 | EL 自动进行类型转换 | — | ${days}(String 变 int) | 需确保变量值可转换,否则抛异常。 |
| 空值处理 | 支持 null 安全访问 | ?. 操作符 | ${user?.profile?.email} | 避免 NullPointerException。 |
| Spring Bean 访问 | 调用 Spring 容器中的 Bean 方法 | ${beanName.method()} | ${notificationService.send(applicant, '任务已创建')} | Bean 需在 Spring 上下文中注册。 |
| 表达式限制 | 避免执行耗时或危险操作(如修改数据库) | — | 不推荐在表达式中调用 userService.deleteUser() | 表达式应为”纯查询”性质。 |
7.2 使用 Groovy 或 JavaScript 脚本
| 脚本类型 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
flowable:script 元素 | 在 BPMN 中定义脚本任务或执行监听器脚本 | <scriptTask flowable:scriptFormat="groovy"> | <scriptTask id="script1" name="计算费用" flowable:scriptFormat="groovy"><script>def total = days * 200; execution.setVariable('cost', total);</script></scriptTask> | scriptFormat 指定脚本语言。 |
| Groovy 脚本 | 功能强大,语法类似 Java,支持闭包,与 JVM 无缝集成。 | flowable:scriptFormat="groovy" | def manager = userService.getManager(execution.getVariable('applicant')); execution.setVariable('approver', manager); | 推荐使用,性能好,功能全。 |
| JavaScript 脚本 | 使用 Nashorn 引擎(Java 8+),语法灵活。 | flowable:scriptFormat="javascript" | var total = Number(days) * 200; execution.setVariable('cost', total); | Java 15+ 需额外引入 Nashorn。 |
| 脚本任务(Script Task) | 用于执行脚本逻辑,不需人工干预。 | <scriptTask> | — | 常用于数据计算、变量初始化。 |
| 执行上下文 | 脚本中可通过 execution 访问 DelegateExecution 对象 | execution.getVariable(), execution.setVariable() | — | 是脚本与流程交互的主要方式。 |
| 变量操作 | 在脚本中读取和设置流程变量 | execution.setVariable("var", value) | def days = execution.getVariable('days'); if (days > 3) { execution.setVariable('level', 'senior'); } | 变量变更对后续流程可见。 |
| 调用 Spring Bean | 在脚本中访问 Spring Bean | def bean = execution.getProcessEngineServices().getProcessEngineConfiguration().getBean('beanName') | def service = execution.getProcessEngineServices().getProcessEngineConfiguration().getBean('approvalService'); def result = service.check(applicant); | 需通过配置获取 Bean。 |
| 脚本安全性 | 脚本拥有较高权限,可能访问系统资源 | — | 避免执行 System.exit()、文件 I/O 等危险操作 | 生产环境需严格审查脚本内容。 |
| 性能考量 | 脚本解析和执行有开销 | — | 避免在循环中执行复杂脚本 | 对性能敏感的场景建议使用 Java Delegate。 |
| 错误处理 | 脚本中抛出异常会中断流程 | throw new RuntimeException("计算失败") | — | 建议在脚本中添加 try-catch。 |
7.3 表达式在条件、监听器、任务分配中的使用
| 应用场景 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 排他网关(Exclusive Gateway)条件 | 根据表达式结果决定流程走向 | conditionExpression="${variable > 10}" | <conditionExpression xsi:type="tFormalExpression">${totalAmount > 10000}</conditionExpression> | 返回 true 或 false,第一个为 true 的连线被选中。 |
| 并行网关(Inclusive Gateway)条件 | 控制并行分支的激活 | 同上 | 可同时激活多个满足条件的分支 | 与排他网关不同,并行网关不互斥。 |
| 任务分配(Assignee) | 动态指定任务办理人 | flowable:assignee="${manager}" | <userTask id="task1" flowable:assignee="${userService.getManager(applicant)}" /> | 表达式在任务创建时求值。 |
| 任务候选人(Candidate Users/Groups) | 动态指定候选人 | flowable:candidateUsers="${approvers}" | <userTask id="task2" flowable:candidateUsers="${department}Leaders" /> | approvers 可为 List<String> 或逗号分隔的字符串。 |
| 执行监听器(ExecutionListener) | 使用表达式调用服务方法 | expression="${service.method(execution)}" | <flowable:executionListener event="start" expression="${auditService.logStart(execution)}" /> | 避免实现 ExecutionListener 接口。 |
| 任务监听器(TaskListener) | 在任务事件中调用表达式 | expression="${mailService.notify(task)}" | <taskListener event="create" expression="${notificationService.sendTaskCreated(task)}" /> | 常用于发送邮件或消息通知。 |
| 条件启动事件(Conditional Start Event) | 当满足条件时启动流程 | 需配置全局条件 | ${order.status == 'APPROVED'} | 需启用条件启动事件功能。 |
| 定时器表达式(Timer) | 使用表达式动态计算定时时间 | timeDuration="${timeoutDuration}" | <timeDuration>${processService.getTimeout()}PT1H</timeDuration> | 表达式需返回符合格式的字符串。 |
| 默认流程(Default Flow) | 当无连线条件为 true 时使用的默认路径 | 在网关上设置 default 属性 | <exclusiveGateway id="decision" default="flow2"> | 不依赖表达式,但作为兜底逻辑。 |
| 表达式缓存 | Flowable 会缓存表达式编译结果以提高性能 | — | — | 首次执行稍慢,后续更快。 |
| 表达式调试 | 表达式错误会导致流程中断 | 检查日志中的 FlowableException | 确保变量存在且类型正确 | 建议在测试环境充分验证表达式逻辑。 |
第八章:历史数据与流程审计
8.1 历史服务(HistoryService)与历史级别配置
| 配置项/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| history 配置 | processEngineConfiguration.setHistory("full"); | 设置历史级别,控制历史数据的详细程度 | 可在 flowable.cfg.xml 或 Spring 配置中设置 | 历史级别影响性能和存储空间。 |
| none | history="none" | 不记录任何历史数据 | 适用于纯运行时流程 | 历史表为空,无法查询历史。 |
| activity | history="activity" | 记录流程实例、执行流和任务的开始/结束时间 | 最低级别,记录活动实例 | 不记录变量和详细信息。 |
| audit | history="audit" | 记录任务、变量、作业等详细信息,推荐用于审计 | 默认级别 | 包含大多数审计所需数据。 |
| full | history="full" | 记录所有数据,包括详细变更日志 | 最详细,用于深度分析 | 性能开销最大,存储占用高。 |
| HistoryService 接口 | historyService.createHistoricProcessInstanceQuery() | 提供查询历史数据的方法 | HistoryService historyService = processEngine.getHistoryService(); | 与 RuntimeService 对应,用于历史查询。 |
| asyncHistoryExecutor | asyncHistoryExecutorActivate="true" | 启用异步历史记录,提升流程性能 | 将历史数据写入操作放入队列异步处理 | 生产环境推荐启用,避免阻塞主流程。 |
| 历史表命名 | ACT_HI_* | 历史数据存储在以 ACT_HI_ 开头的表中 | 如 ACT_HI_PROCINST, ACT_HI_TASKINST, ACT_HI_VARINST | 与运行时表 ACT_RU_* 区分。 |
| 历史清理策略 | — | 定期清理过期历史数据 | 可通过自定义作业或外部脚本实现 | 避免数据库无限增长。 |
| 历史级别变更 | 修改 history 配置后重启引擎 | 影响新启动的流程实例 | 已运行的流程仍按原级别记录 | 建议在系统设计初期确定级别。 |
8.2 查询历史流程实例与任务
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
createHistoricProcessInstanceQuery | historyService.createHistoricProcessInstanceQuery() | 创建历史流程实例查询对象 | HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); | 支持链式调用。 |
.processInstanceId | .processInstanceId(String procInstId) | 按流程实例 ID 查询 | HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId("5001").singleResult(); | 精确匹配。 |
.processDefinitionKey | .processDefinitionKey(String procDefKey) | 查询某类流程的历史实例 | List instances = historyService.createHistoricProcessInstanceQuery().processDefinitionKey("leave").finished().list(); | 可跨多个流程定义查询。 |
.finished() / .unfinished() | .finished() | 查询已结束或未结束的流程实例 | .finished().orderByProcessInstanceEndTime().desc() | 用于统计完成情况。 |
.startedBy | .startedBy(String userId) | 查询某用户启动的流程实例 | List myInstances = historyService.createHistoricProcessInstanceQuery().startedBy("zhangsan").list(); | 结合时间范围使用更高效。 |
.startedAfter / .startedBefore | .startedAfter(Date date) | 按启动时间范围查询 | Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, -30); List recent = query.startedAfter(cal.getTime()).list(); | 常用于月度报表。 |
.finishedAfter / .finishedBefore | .finishedBefore(Date date) | 按结束时间范围查询 | — | 仅对已结束实例有效。 |
.durationGreaterThan | .durationGreaterThan(Long millis) | 查询执行时长超过指定值的实例 | query.durationGreaterThan(3600000L) // 超过1小时 | 时长单位为毫秒。 |
createHistoricTaskInstanceQuery | historyService.createHistoricTaskInstanceQuery() | 创建历史任务实例查询对象 | HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery(); | 查询已完成的任务。 |
.taskAssignee | .taskAssignee(String userId) | 查询某用户办理过的历史任务 | List tasks = historyService.createHistoricTaskInstanceQuery().taskAssignee("lisi").finished().list(); | 包含已结束的任务。 |
.taskCandidateUser | .taskCandidateUser(String userId) | 查询某用户曾可办理的任务 | — | 即使未领取也可查询到。 |
.processInstanceId | .processInstanceId(String procInstId) | 查询某流程实例中的所有历史任务 | List taskList = historyService.createHistoricTaskInstanceQuery().processInstanceId("5001").orderByHistoricTaskInstanceStartTime().asc().list(); | 用于流程追溯。 |
.taskDeleteReasonLike | .taskDeleteReasonLike("%timeout%") | 按删除原因模糊查询 | 适用于被跳过或超时取消的任务 | 原因由 deleteTask 时指定。 |
.orderBy + .asc() / .desc() | .orderByProcessInstanceStartTime().desc() | 排序 | 支持按开始、结束、持续时间等排序 | 结合分页使用。 |
.listPage | .listPage(int firstResult, int maxResults) | 分页查询 | List page = query.listPage(0, 10); | 避免一次性查询大量数据。 |
.count | .count() | 获取查询结果总数 | long total = historyService.createHistoricProcessInstanceQuery().processDefinitionKey("loan").count(); | 用于分页控件显示总条数。 |
8.3 历史变量与审计日志
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
createHistoricVariableInstanceQuery | historyService.createHistoricVariableInstanceQuery() | 查询历史变量实例 | HistoricVariableInstanceQuery varQuery = historyService.createHistoricVariableInstanceQuery(); | 变量在 ACT_HI_VARINST 表中。 |
.variableName | .variableName("totalAmount") | 按变量名查询 | List vars = varQuery.variableName("approved").list(); | 可查询所有流程实例中的该变量。 |
.variableValueEquals | .variableValueEquals("status", "APPROVED") | 按变量值精确查询 | — | 支持 String, Integer 等基本类型。 |
.processInstanceId | .processInstanceId("5001") | 查询某流程实例的所有历史变量 | List instanceVars = historyService.createHistoricVariableInstanceQuery().processInstanceId("5001").list(); | 获取实例的完整变量快照。 |
.excludeVariableValues | .excludeVariableValues() | 查询时不加载变量值(仅元数据) | List metadata = varQuery.excludeVariableValues().list(); | 提高查询性能,适用于大数据量场景。 |
getActualValue | historicVariableInstance.getValue() | 获取变量的实际值(反序列化后) | Object value = historicVar.getValue(); | 对于 Serializable 对象,需确保类路径存在。 |
getTextValue / getDoubleValue 等 | historicVar.getTextValue() | 获取变量的文本或数字值 | String name = historicVar.getTextValue(); | 适用于简单类型,避免类型转换异常。 |
createHistoricDetailQuery | historyService.createHistoricDetailQuery() | 查询历史详情,包括变量更新、任务表单属性等 | HistoricDetailQuery detailQuery = historyService.createHistoricDetailQuery(); | 记录在 ACT_HI_DETAIL 表中,用于审计日志。 |
.processInstanceId | .processInstanceId("5001") | 查询某流程实例的详细变更记录 | List details = historyService.createHistoricDetailQuery().processInstanceId("5001").orderByTime().asc().list(); | 可追溯变量每次变更。 |
.variableUpdates() | .variableUpdates() | 仅查询变量更新记录 | List updates = detailQuery.variableUpdates().list(); | 过滤出 HistoricVariableUpdate 类型。 |
.formProperties() | .formProperties() | 查询表单属性提交记录 | — | 与 HistoricFormProperty 关联。 |
getVariableUpdates | (HistoricVariableUpdate) detail | 将 HistoricDetail 转换为变量更新对象 | for (HistoricDetail detail : details) { if (detail instanceof HistoricVariableUpdate) { HistoricVariableUpdate update = (HistoricVariableUpdate) detail; System.out.println(update.getVariableName() + " from " + update.getOldValue() + " to " + update.getValue()); } } | 获取变更前后的值,实现审计日志。 |
getTaskId | historicDetail.getTaskId() | 获取详情关联的任务 ID | 可用于关联任务上下文 | 为空表示全局变量变更。 |
getTime | historicDetail.getTime() | 获取变更发生的时间 | — | 精确到毫秒,用于时间线展示。 |
getRevision | historicDetail.getRevision() | 获取变量的版本号 | — | 反映变量的修改次数。 |
| 审计日志展示 | 综合 HistoricProcessInstance, HistoricTaskInstance, HistoricDetail | 构建完整的流程审计视图 | 按时间顺序合并流程启动、任务办理、变量变更等事件 | 是实现”流程追溯”功能的核心。 |
第九章:身份管理与用户组集成
9.1 IdentityService 与用户/组管理
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| IdentityService 接口 | Flowable 提供的身份管理服务,用于管理用户、组及成员关系 | identityService.createUserQuery() | IdentityService identityService = processEngine.getIdentityService(); | 仅用于流程引擎内部的身份存储。 |
| 内置身份表 | Flowable 自带用户和组的数据库表 | ACT_ID_* 表(如 ACT_ID_USER, ACT_ID_GROUP, ACT_ID_MEMBERSHIP) | — | 可独立使用,也可与外部系统集成。 |
| 用户(User) | 表示流程中的参与者,具有唯一 ID、姓名、邮箱等属性 | User 对象 | User user = new UserEntityImpl(); user.setId("zhangsan"); user.setFirstName("Zhang"); user.setLastName("San"); | id 字段为必填且唯一。 |
| 组(Group) | 用户的逻辑集合,用于任务分配(如 candidateGroup) | Group 对象 | Group group = new GroupEntityImpl(); group.setId("managers"); group.setName("Managers"); | id 通常用于流程定义中的 candidateGroup。 |
| 成员关系(Membership) | 用户与组的关联关系,一个用户可属于多个组 | 通过 createMembership(userId, groupId) 建立 | identityService.createMembership("zhangsan", "hr"); | 关系存储在 ACT_ID_MEMBERSHIP 表中。 |
| 密码管理 | 支持为用户设置密码,用于身份验证 | user.setPassword("pwd123") | 在用户创建或更新时设置 | Flowable 本身不提供登录功能,密码用于扩展场景。 |
| 外部系统集成 | IdentityService 可被替换,以连接 LDAP、数据库或 REST API | 实现 IdentityService 接口或使用 ReadOnlyIdentityServiceProvider | — | 推荐在企业环境中集成统一身份认证系统。 |
| 会话机制 | Flowable 在流程执行时使用 Authentication 类进行身份上下文管理 | Authentication.setAuthenticatedUserId("zhangsan") | 在任务提交前设置当前用户 | 用于自动设置 assignee 或 owner。 |
| 与流程执行关联 | 用户 ID 可作为 assignee 或 candidateUser 在任务中使用 | 在 BPMN 中设置 flowable:assignee="${userId}" | — | 身份数据是任务分配的基础。 |
| 只读模式 | 可配置为只读模式,从外部系统同步用户组数据 | flowable.identity-service.read-only=true | 避免在 Flowable 中修改用户数据 | 适用于与主身份系统同步的场景。 |
9.2 用户与组的增删改查
| 操作类型 | 方法名称 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 用户管理 | ||||
| 创建用户 | saveUser(User user) | 新增用户到 ACT_ID_USER 表 | User user = identityService.newUser("lisi"); user.setFirstName("Li"); user.setLastName("Si"); user.setEmail("lisi@company.com"); user.setPassword("pass"); identityService.saveUser(user); | id 必须设置,密码可选。 |
| 查询用户 | createUserQuery() | 构建用户查询对象 | List users = identityService.createUserQuery().userEmailLike("%@company.com").list(); | 支持按 ID、姓名、邮箱、组等条件查询。 |
| 按 ID 查询 | .userId("zhangsan") | 精确查询单个用户 | User user = identityService.createUserQuery().userId("zhangsan").singleResult(); | 返回 null 若用户不存在。 |
| 模糊查询 | .userFirstNameLike("Zhang%") | 按姓名模糊查询 | List zhangUsers = identityService.createUserQuery().userFirstNameLike("Zhang%").list(); | 支持 % 通配符。 |
| 按组查询 | .memberOfGroup("managers") | 查询属于某组的所有用户 | List managers = identityService.createUserQuery().memberOfGroup("managers").list(); | 基于 ACT_ID_MEMBERSHIP 关联查询。 |
| 更新用户 | saveUser(User user) | 修改已存在用户的信息 | User user = identityService.createUserQuery().userId("lisi").singleResult(); user.setEmail("newemail@company.com"); identityService.saveUser(user); | 调用 saveUser 更新,无需单独的 update 方法。 |
| 删除用户 | deleteUser(String userId) | 从系统中删除用户 | identityService.deleteUser("lisi"); | 同时删除其成员关系,但历史任务中的用户引用仍保留。 |
| 组管理 | ||||
| 创建组 | saveGroup(Group group) | 新增组到 ACT_ID_GROUP 表 | Group group = identityService.newGroup("dev"); group.setName("Developers"); identityService.saveGroup(group); | id 和 name 通常都需设置。 |
| 查询组 | createGroupQuery() | 构建组查询对象 | List groups = identityService.createGroupQuery().groupId("hr").list(); | 支持按 ID、名称等查询。 |
| 按名称查询 | .groupName("Managers") | 按组名称查询 | — | 支持精确和模糊查询(groupNameLike)。 |
| 更新组 | saveGroup(Group group) | 修改组信息 | Group group = identityService.createGroupQuery().groupId("dev").singleResult(); group.setName("Development Team"); identityService.saveGroup(group); | 同用户更新,使用 saveGroup。 |
| 删除组 | deleteGroup(String groupId) | 删除组及所有成员关系 | identityService.deleteGroup("dev"); | 组内用户不会被删除,仅解除关联。 |
| 成员关系管理 | ||||
| 添加成员 | createMembership(String userId, String groupId) | 将用户加入组 | identityService.createMembership("zhangsan", "managers"); | 若关系已存在,可能抛异常,建议先查询。 |
| 移除成员 | deleteMembership(String userId, String groupId) | 将用户从组中移除 | identityService.deleteMembership("zhangsan", "managers"); | 仅删除关联,不删除用户或组。 |
| 查询成员 | createUserQuery().memberOfGroup(groupId) 或 createGroupQuery().groupMember(userId) | 双向查询成员关系 | List userGroups = identityService.createGroupQuery().groupMember("zhangsan").list(); | 提供灵活的查询方式。 |
9.3 与 Spring Security 集成方案
| 集成方式 | 说明 | 配置/代码示例 | 注意事项 |
|---|---|---|---|
| 共享用户上下文 | 将 Spring Security 的当前用户同步到 Flowable | 在服务层调用 Authentication.setAuthenticatedUserId() | @Service public class WorkflowService { @Autowired private RuntimeService runtimeService; public void startProcess(String procDefKey) { String currentUser = SecurityContextHolder.getContext().getAuthentication().getName(); Authentication.setAuthenticatedUserId(currentUser); runtimeService.startProcessInstanceByKey(procDefKey); } } |
| 自定义身份提供者 | 实现 ReadOnlyIdentityServiceProvider,从 Spring Security 用户库加载用户组 | public class SpringSecurityIdentityProvider implements ReadOnlyIdentityServiceProvider { @Override public User getUserById(String userId) { return convertToFlowableUser(loadUserFromSpringSecurity(userId)); } @Override public List getGroupsForUser(String userId) { // 获取用户权限并转换为 Flowable Group } } | 需注册为 Spring Bean 并在 Flowable 配置中启用。 |
| 配置只读模式 | 避免 Flowable 修改身份数据,所有读取委托给自定义提供者 | 在 flowable.cfg.xml 中配置 | 确保 IdentityService 的写操作被禁用。 |
| 任务分配集成 | 使用 Spring Security 的 Authentication 信息动态分配任务 | 在表达式中使用 ${authenticatedUserId} | <userTask flowable:assignee="${authenticatedUserId}" /> |
| 权限控制 | 在 Web 层基于 Spring Security 的 @PreAuthorize 控制流程操作权限 | @PreAuthorize("hasRole('APPROVER')") public void approveTask(String taskId) { taskService.complete(taskId); } | 将业务角色映射到 Spring Security 的 GrantedAuthority。 |
| 用户信息转换 | 将 Spring Security 的 UserDetails 转换为 Flowable User 对象 | private User convertToFlowableUser(UserDetails userDetails) { User user = identityService.newUser(userDetails.getUsername()); user.setFirstName(userDetails.getUsername()); return user; } | 需处理字段映射,如邮箱、姓名等。 |
| 会话清理 | 在请求结束时清除 Flowable 的身份上下文 | 使用 @After 或 @EventListener 清理 | @EventListener public void handleRequestEnd(RequestHandledEvent event) { Authentication.clearAuthenticatedUserId(); } |
| 优势 | 统一身份认证、避免重复管理用户、利用现有权限体系 | — | 企业级应用推荐方案。 |
| 挑战 | 需要编写适配代码、处理用户同步延迟、调试复杂 | — | 建议充分测试用户加载和权限判断逻辑。 |
第十章:事件与消息机制
10.1 中间消息事件(Message Event)与消息触发
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 捕获消息中间事件(Intermediate Message Catch Event) | 流程执行到该节点时暂停,等待指定消息到达后继续。 | 图形:圆圈内带信封图标,位于流程线上 | <intermediateCatchEvent id="waitPayment" name="等待支付完成"><messageEventDefinition messageRef="paymentConfirmed" /></intermediateCatchEvent> | 用于同步外部系统调用。 |
| 发送消息中间事件(Intermediate Message Throw Event) | 主动向流程外发送一条消息。 | 图形:圆圈内带向上的信封图标 | <intermediateThrowEvent id="sendNotification" name="发送通知"><messageEventDefinition messageRef="taskAssigned" /></intermediateThrowEvent> | 消息可被其他流程或监听器捕获。 |
| messageRef | 消息的引用名称,用于关联发送和接收 | 在 BPMN 中定义 <message id="paymentConfirmed" name="Payment Confirmed" /> | <message id="orderShipped" name="Order Shipped" /> | 必须全局唯一。 |
| MessageCorrelationBuilder | 用于从外部向流程实例发送消息,触发捕获事件 | runtimeService.createMessageCorrelation("paymentConfirmed") | runtimeService.createMessageCorrelation("paymentConfirmed").processInstanceId("5001").setVariable("txnId", "txn_123").correlate(); | correlate() 触发流程继续执行。 |
| 消息关联(Correlation) | 通过流程实例 ID、业务键或消息关联键精确定位目标流程 | .processInstanceId("5001") 或 .processInstanceBusinessKey("ORDER-1001") | 可结合变量进行更复杂的关联 | 若未指定,可能匹配多个流程实例,导致异常。 |
| 消息边界事件(Message Boundary Event) | 附加在任务上,当消息到达时中断当前任务并跳转 | attachedToRef="userTask1", cancelActivity="true" | <boundaryEvent id="msgCancel" attachedToRef="reviewTask" cancelActivity="true"><messageEventDefinition messageRef="cancelReview" /></boundaryEvent> | 常用于”取消审批”场景。 |
| 消息非中断边界事件 | 消息到达时不中断原任务,而是开启并行分支 | cancelActivity="false" | — | 实现”提醒”或”补充流程”。 |
| 消息队列集成 | 可通过监听器将 Flowable 消息桥接到 JMS、RabbitMQ 等 | 实现 FlowableEngineEventListener 监听 MESSAGE_THROWED 事件 | 将 intermediateThrowEvent 发出的消息转发到外部队列 | 实现系统间解耦。 |
| 错误处理 | 若消息无法关联到任何等待的流程实例,会抛出 FlowableException | — | 建议在调用 correlate() 时捕获异常 | 可记录日志或重试。 |
| 使用场景 | 订单支付确认、物流状态同步、跨系统任务协调 | — | 如”支付系统回调后,订单流程继续发货” | 是实现异步集成的关键机制。 |
10.2 信号事件(Signal Event)与广播
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 信号中间事件(Intermediate Signal Catch Event) | 等待一个全局信号,可被任意流程实例触发。 | 图形:圆圈内带三角形图标 | <intermediateCatchEvent id="waitForUpdate"><signalEventDefinition signalRef="systemUpdate" /></intermediateCatchEvent> | 信号是全局广播,不限于单个流程实例。 |
| 信号抛出事件(Intermediate Signal Throw Event) | 发送一个全局信号,所有监听该信号的流程实例都会收到。 | 图形:圆圈内带向上的三角形图标 | <intermediateThrowEvent id="broadcastUpdate"><signalEventDefinition signalRef="systemUpdate" /></intermediateThrowEvent> | 实现”一对多”通知。 |
| signalRef | 信号的引用名称,用于标识信号类型 | 定义 <signal id="systemUpdate" name="System Update" /> | — | 必须在流程定义中声明。 |
| SignalEventPropagationMode | 控制信号的传播范围 | 可设置为 DEFAULT, EXCLUSIVE, ASYNC | 默认为 DEFAULT,信号同步传播到所有监听器 | ASYNC 可提高性能,但顺序不保证。 |
runtimeService.signalEventReceived | 从外部代码触发一个信号事件 | runtimeService.signalEventReceived("systemUpdate") | runtimeService.signalEventReceived("systemUpdate"); | 可选地传递变量:.signalEventReceived("alert", variables)。 |
| 信号边界事件(Signal Boundary Event) | 附加在活动上,当信号到达时中断或并行执行 | attachedToRef="task1" | <boundaryEvent id="emergency" attachedToRef="normalProcess" cancelActivity="true"><signalEventDefinition signalRef="EMERGENCY_STOP" /></boundaryEvent> | 常用于”紧急停止”或”系统维护”通知。 |
| 非中断信号边界事件 | 信号触发时不中断原流程,开启新分支 | cancelActivity="false" | — | 用于”提醒”、“数据刷新”等场景。 |
| 信号事件子流程 | 顶级子流程,监听信号,收到后启动 | triggeredByEvent="true" | <eventSubProcess id="signalSubProcess" triggeredByEvent="true"><startEvent><signalEventDefinition signalRef="newUserRegistered" /></startEvent>...</eventSubProcess> | 实现”事件驱动”流程启动。 |
| 广播特性 | 一个信号可被多个流程实例同时捕获 | — | 如”发布新版本”信号,触发所有相关系统的更新流程 | 与消息事件的”点对点”不同。 |
| 与消息事件对比 | 信号是广播、全局、无状态;消息是定向、实例级、有上下文 | — | 选择依据:是否需要精确关联特定流程实例 | 信号更灵活,但控制粒度较粗。 |
| 使用场景 | 系统级通知、批量操作触发、事件驱动架构 | — | 如”夜间批处理开始”、“价格更新” | 适合解耦和事件驱动设计。 |
10.3 事件子流程(Event Sub-Process)
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 事件子流程(Event Sub-Process) | 嵌套在主流程中的子流程,由特定事件(如错误、消息、信号)触发执行。 | 图形:虚线边框的子流程 | <eventSubProcess id="errorHandler" triggeredByEvent="true">...</eventSubProcess> | triggeredByEvent="true" 是关键属性。 |
| 开始事件类型 | 事件子流程必须以捕获型开始事件(如错误、消息、信号、定时器)启动 | 支持 errorEventDefinition, messageEventDefinition, signalEventDefinition, timerEventDefinition | <startEvent id="errorStart"><errorEventDefinition errorRef="BusinessError" /></startEvent> | 不能使用普通开始事件。 |
| 错误事件子流程(Error Event Sub-Process) | 捕获特定错误,执行补偿或降级逻辑 | errorRef 指向 error 元素 | <error id="BusinessError" errorCode="BUS-001" /> | 可定义在任务、事务子流程或主流程级别。 |
| 范围(Scope) | 事件子流程的捕获范围取决于其位置 | 定义在 userTask 内 → 仅捕获该任务的错误;定义在流程根 → 捕获整个流程的错误 | — | 位置决定作用域。 |
| 中断性 | 默认为中断性,触发后终止主流程的当前执行流 | 可设置 cancelActivity="false" 为非中断 | 非中断用于并行处理(如记录日志) | 中断性常用于错误处理。 |
| 消息事件子流程 | 由消息触发的事件子流程 | 以 messageEventDefinition 开始 | <startEvent><messageEventDefinition messageRef="timeoutAlert" /></startEvent> | 可在流程运行中动态注入处理逻辑。 |
| 信号事件子流程 | 由全局信号触发 | 以 signalEventDefinition 开始 | 如上文 10.2 所示 | 实现跨流程协调。 |
| 定时器事件子流程 | 在特定时间触发,如”超时处理” | 以 timerEventDefinition 开始 | <startEvent><timerEventDefinition><timeDuration>PT1H</timeDuration></timerEventDefinition></startEvent> | 常用于 SLA 超时升级。 |
| 执行上下文 | 事件子流程与主流程共享变量和执行上下文 | 可直接访问主流程的变量 | 在子流程中可修改变量,影响主流程 | 便于状态传递。 |
| 结束行为 | 事件子流程结束后,主流程的后续行为取决于中断性 | 中断性:主流程终止;非中断性:主流程继续 | — | 设计时需明确预期行为。 |
| 使用场景 | 异常处理、超时升级、动态流程扩展 | — | 如”用户任务 24 小时未处理,启动催办子流程” | 增强流程的健壮性和灵活性。 |
| 限制 | 一个活动或流程只能有一个同类型事件子流程 | 如不能有两个错误事件子流程 | — | 需合并逻辑到单个子流程中。 |
第十一章:REST API 与外部集成
11.1 Flowable REST 模块介绍
| 概念名称 | 说明 | 语法/方法 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| flowable-rest 模块 | Flowable 提供的独立 REST API 服务,基于 Spring MVC 实现 | Maven 依赖:org.flowable:flowable-rest | 可部署为独立 WAR 包或嵌入 Spring Boot 应用 | 提供标准 HTTP 接口操作流程引擎。 |
| REST API 端点 | 提供对流程定义、实例、任务、历史等资源的操作接口 | 基础路径:/flowable-rest/service | GET /process-definition 获取所有流程定义 | 遵循 RESTful 风格,使用 JSON 格式。 |
| 认证方式 | 默认无认证,需集成 Spring Security 或其他机制 | 可通过 HTTP Basic、Token、OAuth2 等方式 | Authorization: Basic dXNlcjpwYXNz | 生产环境必须配置安全认证。 |
| 资源分类 | API 按资源类型组织,如 process, task, history, identity | 各资源有独立的 URL 路径 | POST /task/{taskId}/complete 完成任务 | 易于发现和使用。 |
| 流程定义管理 | 支持部署、查询、挂起/激活流程定义 | POST /repository/deployment 部署 BPMN 文件 | 使用 multipart/form-data 上传文件 | 部署后返回部署 ID 和流程定义 ID。 |
| 流程实例操作 | 支持启动、查询、删除流程实例 | POST /runtime/process-instance 启动实例 | 请求体:{"processDefinitionKey": "leave", "variables": [...]} | 可传递启动变量。 |
| 任务管理 | 支持查询、领取、完成、委托任务 | GET /task 查询任务,POST /task/{id}/complete 完成 | 完成任务时可提交表单变量 | 任务操作需用户上下文(通过认证传递)。 |
| 历史数据查询 | 支持查询历史流程实例、任务、变量 | GET /history/historic-process-instances | 可添加过滤条件如 finished=true | 用于报表和审计。 |
| 身份管理 | 提供用户、组的 CRUD 接口(若使用内置身份) | POST /identity/users 创建用户 | 请求体包含用户信息 | 与 IdentityService 对应。 |
| 错误响应 | 统一的错误格式,包含错误码和消息 | 返回 JSON:{"message": "Not found", "exception": "..."} | HTTP 状态码反映错误类型(404, 500 等) | 便于客户端处理异常。 |
| 跨域支持(CORS) | 默认可能禁用,需配置以支持前端调用 | 在 WebMvcConfigurer 中配置 CorsRegistry | 允许前端跨域访问 | 前后端分离架构必需。 |
11.2 使用 REST API 操作流程与任务
| 操作类型 | HTTP 方法 | API 端点 | 用途 | 示例请求/响应 |
|---|---|---|---|---|
| 部署流程 | POST | /repository/deployments | 上传 BPMN 文件并部署 | 请求(multipart/form-data): file: leave.bpmn20.xml;响应: {"id": "5001", "name": "leave-process", "deploymentTime": "2025-10-17T10:00:00"} |
| 查询流程定义 | GET | /repository/process-definitions | 获取所有已部署的流程定义 | 请求: /repository/process-definitions?nameLike=leave%;响应: [{"id":"leave:1:4","key":"leave","name":"Leave Process"}] |
| 启动流程实例 | POST | /runtime/process-instances | 根据流程定义 Key 启动新实例 | 请求体: {"processDefinitionKey": "leave", "variables": [{"name": "days", "type": "integer", "value": 3}]};响应: {"id": "7501", "processDefinitionId": "leave:1:4", "startTime": "..."} |
| 查询用户任务 | GET | /task | 查询当前用户可办理的任务 | 请求: /task?assignee=zhangsan&sort=created&order=desc;响应: [{"id":"1001","name":"审批请假","assignee":"zhangsan","created":"2025-10-17T..."}] |
| 领取任务 | POST | /task/{taskId}/claim | 将候选任务分配给指定用户 | 请求体: {"userId": "lisi"};响应: 200 OK |
| 完成任务 | POST | /task/{taskId}/complete | 完成用户任务,流程继续 | 请求体: {"variables": [{"name": "approved", "value": true}]};响应: 200 OK |
| 查询历史流程实例 | GET | /history/historic-process-instances | 获取已结束的流程实例 | 请求: /history/historic-process-instances?processDefinitionKey=leave&finished=true;响应: [{"id":"7501","processDefinitionId":"...","endTime":"..."}] |
| 查询历史变量 | GET | /history/historic-variable-instances | 获取历史变量值 | 请求: /history/historic-variable-instances?processInstanceId=7501;响应: [{"variableName":"days","value":3,"type":"integer"}] |
| 查询流程图 | GET | /process-definition/{id}/diagram | 获取流程定义的 SVG 图像 | 响应: 直接返回 SVG 文件流 |
| 删除流程实例 | DELETE | /runtime/process-instances/{id} | 删除正在运行的流程实例 | 请求: /runtime/process-instances/7501;响应: 204 No Content |
11.3 自定义 REST 接口集成
| 集成方式 | 说明 | 配置/代码示例 | 注意事项 |
|---|---|---|---|
| 嵌入 Spring Boot 应用 | 将 flowable-rest 的控制器集成到自有 Spring Boot 项目中 | 添加 flowable-spring-boot-starter-rest-api 依赖 | 避免端口冲突,可自定义基础路径。 |
| 自定义 Controller | 编写自己的 REST 接口,调用 RuntimeService, TaskService 等 | @RestController @RequestMapping("/api/workflow") public class WorkflowController { @Autowired private RuntimeService runtimeService; @PostMapping("/start") public ResponseEntity startProcess(@RequestBody StartRequest req) { ProcessInstance instance = runtimeService.startProcessInstanceByKey(req.getProcDefKey(), req.getVariables()); return ResponseEntity.ok(instance.getId()); } } | 可添加业务逻辑、权限校验、日志等。 |
| AOP 或 Interceptor | 在 REST 调用前后添加横切逻辑 | 使用 @ControllerAdvice 或 HandlerInterceptor | 如记录操作日志、性能监控。 |
| 参数校验 | 使用 @Valid 和 JSR-303 注解校验请求参数 | public class StartRequest { @NotBlank private String procDefKey; @Min(1) private Integer days; } | 提高接口健壮性。 |
| 异常统一处理 | 使用 @ControllerAdvice 捕获 FlowableException 并返回友好错误 | @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(FlowableObjectNotFoundException.class) public ResponseEntity handleNotFound(FlowableObjectNotFoundException e) { return ResponseEntity.status(404).body(new ErrorResponse("PROCESS_NOT_FOUND", e.getMessage())); } } | 避免将内部异常暴露给客户端。 |
| 与前端框架集成 | 提供 API 供 Angular、React、Vue 等前端调用 | 在前端使用 axios 或 fetch 调用 API | 需处理认证(如 JWT)。 |
| API 文档 | 使用 Swagger/OpenAPI 生成接口文档 | 集成 springdoc-openapi | 方便前后端协作和测试。 |
| 安全加固 | 结合 Spring Security 实现细粒度权限控制 | @PreAuthorize("hasRole('APPROVER')") 在 Controller 方法上 | 确保只有授权用户能操作流程。 |
| 异步处理 | 对耗时操作(如批量启动)使用 @Async | @Async 方法返回 CompletableFuture | 提高接口响应速度。 |
| 性能监控 | 集成 Micrometer 和 Prometheus | 记录 API 调用次数、耗时 | 用于生产环境监控和优化。 |
第十二章:Spring Boot 集成与最佳实践
12.1 Spring Boot 中集成 Flowable
| 集成方式 | 说明 | 配置方法 | 注意事项 |
|---|---|---|---|
| Starter 依赖 | 使用官方 Spring Boot Starter 快速集成 | 添加 flowable-spring-boot-starter-engine 或 flowable-spring-boot-starter-rest-api | 推荐方式,自动配置。 |
| 自动配置 | Starter 会自动配置 ProcessEngine, DataSource, TransactionManager 等 | 无需手动创建 ProcessEngineConfiguration | 遵循 Spring Boot 约定优于配置。 |
| 数据源配置 | Flowable 使用 Spring 的 DataSource | 在 application.yml 中配置:spring.datasource.url, username, password | 需与业务数据源一致或独立配置。 |
| 事务管理 | 自动集成 Spring 的声明式事务(@Transactional) | 在 Service 方法上使用 @Transactional | 确保流程操作与业务操作在同事务中。 |
| 流程定义扫描 | 自动部署 resources/processes/ 目录下的 BPMN 文件 | 可通过 flowable.deployment-resource-pattern 自定义路径 | 如 classpath*:/my-processes/*.bpmn。 |
| 引擎配置 | 通过 application.yml 或 @Bean 自定义 ProcessEngineConfiguration | flowable.history-level=audit | 覆盖默认配置。 |
| 禁用自动部署 | 开发环境启用,生产环境建议手动控制 | flowable.deployment-mode=disabled | 避免意外部署。 |
| 多数据源支持 | Flowable 可配置独立的数据源 | 定义 @FlowableDataSource 注解的数据源 Bean | 隔离流程数据与业务数据。 |
| 健康检查 | 提供 /actuator/health 端点检查引擎状态 | 需启用 flowable.health.enabled=true | 用于运维监控。 |
| 指标监控 | 集成 Micrometer,暴露流程相关指标 | 如流程实例数、任务数等 | 需添加 micrometer-registry-* 依赖。 |
12.2 自动部署与配置项详解
| 配置项 | 配置键(application.yml) | 说明 | 示例值 | 注意事项 |
|---|---|---|---|---|
| 部署资源模式 | flowable.deployment-resource-pattern | 指定自动部署的 BPMN 文件路径 | classpath*:/processes/*.bpmn | 支持 Ant 风格通配符。 |
| 部署模式 | flowable.deployment-mode | 控制自动部署行为 | default, single-resource, resource-parent-folder, disabled | disabled 表示不自动部署。 |
| 部署名称 | flowable.deployment-name | 自动生成部署的名称 | MyApp Processes | 默认为应用名。 |
| 历史级别 | flowable.history-level | 设置历史记录级别 | none, activity, audit, full | 影响性能和存储,推荐 audit。 |
| 异步历史 | flowable.async-history-enabled | 是否异步写入历史数据 | true | 生产环境推荐开启,提升性能。 |
| 异步执行器 | flowable.async-executor-activate | 是否激活异步作业执行器 | true | 用于定时器、消息等异步任务。 |
| 数据库模式更新 | flowable.database-schema-update | 控制数据库表的创建和更新 | true, false, create-drop, custom | true 为开发环境常用。 |
| 作业执行器 | flowable.job-executor-activate | 是否激活作业执行器(旧版) | false | 新版推荐使用异步执行器。 |
| 时区 | flowable.process-definition-time-zone | 设置流程定义的默认时区 | Asia/Shanghai | 影响定时器事件的触发时间。 |
| 限制并发 | flowable.async-history-executor.nb-threads | 异步历史执行器线程数 | 5 | 根据服务器性能调整。 |
| 自定义配置 Bean | — | 通过 Java Config 覆盖默认配置 | @Bean public ProcessEngineConfigurationCustomizer customizer() { return config -> config.setHistoryLevel(HistoryLevel.FULL); } | 优先级高于 application.yml。 |
12.3 事务管理与异常处理
| 主题 | 说明 | 实现方式 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 声明式事务 | 使用 @Transactional 管理流程与业务数据的一致性 | 在 Service 方法上添加注解 | @Service public class LeaveService { @Autowired private RuntimeService runtimeService; @Transactional public void applyForLeave(LeaveForm form) { leaveRepository.save(form); runtimeService.startProcessInstanceByKey("leave", vars); } } | 若流程启动失败,业务数据也回滚。 |
| 事务传播 | 控制事务的传播行为 | @Transactional(propagation = Propagation.REQUIRED) | 默认行为,若存在事务则加入,否则新建 | 避免在异步方法中使用 REQUIRES_NEW 导致流程状态不一致。 |
| 异常处理 | 捕获 FlowableException 及其子类 | try-catch 或 @ControllerAdvice | try { taskService.complete(taskId, variables); } catch (FlowableObjectNotFoundException e) { log.warn("Task not found: " + taskId); throw new BusinessException("TASK_NOT_EXISTS"); } | 区分业务异常与流程引擎异常。 |
| 流程内异常 | 在流程中使用错误事件子流程处理异常 | 定义 errorEventDefinition | 如 10.3 节所述 | 实现流程级别的容错。 |
| 补偿机制 | 使用补偿边界事件和补偿处理器 | <compensateEventDefinition /> | 用于撤销已执行的操作 | 如”扣款后订单取消,需退款”。 |
| 重试机制 | 对异步作业(如服务任务)配置重试次数 | flowable:asyncExecutorRetryWaitTimeInMillis=5000 | 结合 failedJobRetryTimeCycle | 防止临时故障导致流程中断。 |
| 最终一致性 | 通过事件驱动或定时任务修复不一致状态 | 发布领域事件,由监听器处理 | — | 在分布式系统中保证数据最终一致。 |
| 日志记录 | 记录关键流程操作,便于排查 | 使用 SLF4J 在事务方法中记录 | log.info("Process started: {}", processInstanceId); | 结合 MDC 记录用户、请求 ID。 |
| 只读事务 | 查询操作使用 @Transactional(readOnly = true) | @Transactional(readOnly = true) | 提高性能,避免脏写 | 适用于历史查询、任务列表等。 |
12.4 多租户支持与性能优化建议
| 主题 | 说明 | 实现方式 | 建议/示例 | 注意事项 |
|---|---|---|---|---|
| 多租户支持 | 支持一个引擎实例为多个租户服务 | 1. 数据库隔离:每个租户独立数据库 | Flowable 原生支持通过 tenantId 参数区分租户 | 需在部署、启动流程时指定 tenantId。 |
| 2. Schema 隔离:每个租户独立 Schema | ||||
3. 数据行隔离:共享表,通过 TENANT_ID_ 字段区分 | ||||
| 租户 ID 设置 | 在流程定义和实例中设置租户 | repositoryService.createDeployment().tenantId("tenant1").deploy() | runtimeService.startProcessInstanceByKey("proc", variables).setTenantId("tenant1") | 租户 ID 会自动传递到任务、历史等表。 |
| 查询租户数据 | 按租户 ID 过滤查询结果 | runtimeService.createProcessInstanceQuery().tenantId("tenant1") | 所有查询接口均支持 tenantId() 方法 | 确保业务逻辑中正确传递租户上下文。 |
| 性能优化 - 异步执行 | 将历史记录、作业执行异步化 | 启用 async-history-enabled 和 async-executor-activate | 显著提升流程启动和任务完成的响应速度 | 需监控异步队列积压情况。 |
| 性能优化 - 历史级别 | 选择合适的 history-level | 生产环境使用 audit,避免 full | activity 级别性能最佳,但审计信息最少 | 根据业务需求权衡。 |
| 性能优化 - 分页查询 | 避免一次性查询大量历史数据 | 使用 listPage(firstResult, maxResults) | 查询任务列表时,每次加载 20 条 | 防止内存溢出。 |
| 性能优化 - 缓存 | 启用流程定义缓存 | Flowable 默认缓存流程定义 | 减少数据库查询 | 缓存失效机制需关注。 |
| 性能优化 - 数据库索引 | 为常用查询字段添加数据库索引 | 如 ACT_RU_EXECUTION.PROC_INST_ID_, ACT_HI_PROCINST.BUSINESS_KEY_ | 提高查询效率 | 根据慢查询日志分析。 |
| 性能优化 - 连接池 | 配置高性能数据库连接池 | 使用 HikariCP,合理设置连接数 | spring.datasource.hikari.maximum-pool-size=20 | 避免连接等待。 |
| 性能监控 | 监控流程实例数、任务数、作业数 | 使用 /actuator/metrics 或自定义监控 | 设置告警阈值 | 及时发现性能瓶颈。 |
| 定期清理 | 清理已完成的历史数据 | 自定义批处理任务,定期删除 ACT_HI_* 表中过期数据 | 如保留 2 年内的历史数据 | 避免数据库无限增长。 |
第十三章:监控、运维与调试
13.1 Flowable UI 应用介绍(Modeler、Task、Admin)
| UI 应用 | 说明 | 主要功能 | 访问路径 | 注意事项 |
|---|---|---|---|---|
| Flowable Modeler | 在线流程建模工具,用于设计 BPMN、表单、规则等 | 可视化拖拽设计 BPMN 2.0 流程图;设计 CMMN 案例和 DMN 决策表;创建内嵌表单(Form);管理流程分类(Category) | /flowable-modeler | 基于 Web 的图形化编辑器;生成 .bpmn 文件并可直接部署到引擎 |
| Flowable Task | 用户任务中心,用于办理和管理待办任务 | 查询个人任务、候选任务、组任务;领取、完成、委托任务;查看流程变量和流程图;上传附件、添加评论 | /flowable-task | 面向业务用户的操作界面;支持任务表单数据绑定 |
| Flowable Admin | 管理员控制台,用于监控和运维流程引擎 | 监控流程实例、任务、作业状态;查看历史数据和审计日志;管理部署、流程定义;管理用户、组(若使用内置身份);查看数据库信息和指标 | /flowable-admin | 面向系统管理员和开发人员;可连接多个 Flowable 引擎实例进行集中管理 |
| 技术栈 | 基于 AngularJS 和 REST API 构建 | 前端:AngularJS + Bootstrap;后端:Flowable REST API | 独立部署或集成到应用 | 需配置 flowable.admin.app.engine-api-connectors 连接引擎 |
| 用户认证 | 默认使用 Spring Security 内存用户或数据库用户 | 可配置为 LDAP、OAuth2 等 | admin / test 为默认管理员账号 | 生产环境需替换为安全认证机制 |
| 部署方式 | 可作为独立 WAR 包部署在 Tomcat,或嵌入 Spring Boot | Maven 打包后部署 | 需配置 application.properties 指向正确的数据库和 REST API | |
| 集成方式 | 可与自定义系统集成,通过 iframe 或单点登录(SSO) | 使用 iframe 嵌入已有系统 | 需处理跨域和认证传递 | |
| 优势 | 快速搭建流程管理前端,无需从零开发 | 提供开箱即用的流程管理功能 | 适合中小型项目或快速原型开发 | |
| 局限性 | 功能较为通用,难以满足复杂定制需求 | 表单能力有限,复杂业务逻辑需扩展 | 建议在 Task 应用基础上二次开发 | 大型企业通常基于 REST API 自研前端 |
| 维护状态 | Flowable 6.x 后,官方更推荐基于 REST API 自建 UI | Modeler 功能稳定,但更新较少 | 考虑未来向自定义前端迁移 |
13.2 流程建模与在线设计
| 建模功能 | 说明 | 使用方式 | 示例/技巧 | 注意事项 |
|---|---|---|---|---|
| BPMN 设计器 | 可视化拖拽设计流程图 | 在 Modeler 中创建”BPMN 2.0 模型” | 使用左侧工具栏拖拽任务、网关、事件 | 支持标准 BPMN 2.0 元素 |
| 元素属性配置 | 设置任务、流程、事件的属性 | 选中元素后在右侧属性面板编辑 | 任务:设置 Assignee(办理人)、Candidate Users/Groups(候选);流程:设置 Key、Name、Version | Key 用于 API 调用,应语义化(如 leave-approval) |
| 流程变量(Variables) | 定义流程中使用的变量 | 在任务或流程上配置 flowable:field 或通过脚本 | ${employeeName}, ${days} | 变量可用于表达式、条件判断、表单绑定 |
| 脚本任务(Script Task) | 执行 Groovy、JavaScript 等脚本 | <scriptTask scriptFormat="groovy"> | execution.setVariable("approved", true) | 避免复杂逻辑,影响可维护性 |
| 服务任务(Service Task) | 调用 Java 类或外部服务 | 配置 flowable:class 或 flowable:delegateExpression | class="com.example.LeaveService" | 推荐使用 delegateExpression 实现松耦合 |
| 用户任务(User Task) | 人工处理的任务节点 | 设置办理人(Assignee)或候选(Candidate) | assignee="${initiator}" | 候选用户/组可多人领取 |
| 排他网关(Exclusive Gateway) | 基于条件的分支判断 | 为每个流出连线设置 conditionExpression | ${days > 3} | 条件表达式使用 JUEL 语法 |
| 并行网关(Parallel Gateway) | 实现并行分支和汇聚 | 使用一对并行网关(分支 + 汇聚) | — | 汇聚时等待所有分支完成 |
| 内嵌表单(Form) | 为用户任务设计数据输入表单 | 在 Modeler 中设计表单字段 | 添加 text, number, date 等字段 | 表单字段自动绑定到流程变量 |
| 流程分类(Category) | 对流程进行分组管理 | 在 Modeler 中创建分类(如”HR”, “Finance”) | 分类名用于过滤和组织 | 提升管理效率 |
| 部署到引擎 | 将设计好的流程发布到运行时引擎 | 点击”部署”按钮 | 部署后可在 Task 或 Admin 应用中使用 | 部署后生成 ProcessDefinition |
13.3 流程实例监控与问题排查
| 监控/排查项 | 说明 | 工具/方法 | 操作示例 | 注意事项 |
|---|---|---|---|---|
| 流程实例状态 | 查看流程实例是运行中、已结束还是被挂起 | Flowable Admin 控制台 / RuntimeService / HistoryService | runtimeService.createProcessInstanceQuery().processInstanceId("7501").singleResult() | 运行中实例在 ACT_RU_EXECUTION,历史实例在 ACT_HI_PROCINST |
| 当前执行节点 | 确定流程实例当前停留在哪个任务 | Admin 控制台查看高亮节点 / 查询 Execution 对象 | runtimeService.createExecutionQuery().processInstanceId("7501").list() | 多实例或并行分支可能有多个执行流 |
| 任务分配情况 | 查看任务的办理人、候选人、创建时间 | TaskService.createTaskQuery() / Admin 任务列表 | taskService.createTaskQuery().taskAssignee("zhangsan").list() | 候选任务需调用 claim() 才能办理 |
| 流程变量值 | 检查流程执行过程中的变量值 | RuntimeService.getVariables(executionId) / HistoryService | Map<String, Object> vars = runtimeService.getVariables("7501"); | 变量是调试流程逻辑的关键 |
| 历史审计日志 | 追踪流程从启动到结束的所有操作 | HistoryService 查询历史表 / Admin 的历史视图 | List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery().processInstanceId("7501").orderByHistoricActivityInstanceStartTime().asc().list(); | 记录每个活动的开始/结束时间、执行人 |
| 作业(Job)监控 | 查看定时器、异步任务的执行状态 | Admin 的”作业”页面 / ManagementService.createJobQuery() | List<Job> jobs = managementService.createJobQuery().list(); | 失败的作业会重试,可手动执行或删除 |
| 数据库表分析 | 直接查询 Flowable 系统表定位问题 | ACT_RU_*(运行时),ACT_HI_*(历史),ACT_GE_*(通用) | SELECT * FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = '7501'; | 需熟悉表结构,生产环境谨慎操作 |
| 日志级别调整 | 开启 DEBUG 日志查看详细执行过程 | 在 logback.xml 或 application.yml 中设置 | logging.level.org.flowable=DEBUG | 会产生大量日志,仅用于问题排查 |
| 流程图可视化 | 图形化展示流程执行路径 | Admin 控制台自动高亮当前节点 | — | 直观了解流程卡点 |
常见问题:
| 问题 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 流程卡住不往下走 | 检查是否有等待消息/信号、定时器未到时、任务未完成 | 查看当前节点类型和作业状态 | 确认是否有外部依赖未触发 |
| 任务未分配给正确用户 | 检查 assignee 表达式或候选组配置 | 验证用户是否在候选组中:identityService.createMembership("user1", "hr") | 表达式语法错误会导致分配失败 |
| 变量值不正确 | 检查脚本任务、服务任务或表达式中的变量设置 | 打印变量日志或查询历史变量 | 变量作用域(流程实例 vs 执行流)需注意 |
第十四章:扩展与定制开发
14.1 自定义 ActivityBehavior
| 概念 | 说明 | 实现方式 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| ActivityBehavior | Flowable 中每个 BPMN 元素(如任务、网关)的行为接口 | 实现 org.flowable.engine.impl.bpmn.behavior.ActivityBehavior | public class CustomTaskBehavior implements ActivityBehavior { @Override public void execute(DelegateExecution execution) { System.out.println("执行自定义任务: " + execution.getCurrentActivityId()); execution.getProcessEngineServices().getRuntimeService().trigger(execution.getId()); } } | 必须手动调用 trigger 推进流程(除结束节点外) |
| 注册方式 | 将自定义行为绑定到 BPMN 元素 | 在 BPMN 文件中使用 flowable:behaviorClass 属性 | <userTask id="customTask" name="自定义任务" flowable:behaviorClass="com.example.CustomTaskBehavior" /> | 仅适用于 userTask 等支持该属性的元素 |
| 常用子接口 | 更具体的接口,简化实现 | UserTaskActivityBehavior, ServiceTaskDelegateExpressionActivity, AbstractBpmnActivityBehavior | 继承 AbstractBpmnActivityBehavior 可自动处理基础逻辑 | 推荐继承抽象类而非直接实现接口 |
| 访问执行上下文 | 在行为中获取流程变量、执行 ID 等 | 通过 DelegateExecution 参数 | String value = (String) execution.getVariable("key"); execution.setVariable("result", "success"); | DelegateExecution 提供了丰富的 API |
| 应用场景 | ||||
| 复杂业务逻辑 | 超出表达式或简单 Java 代理的能力 | 在 execute 方法中调用多个服务、处理事务 | — | 需注意事务边界 |
| 动态任务分配 | 根据运行时条件动态设置办理人 | 在行为中调用 taskService.setAssignee(taskId, userId) | — | 需获取当前任务 ID |
| 特殊网关逻辑 | 实现非标准的分支逻辑 | 自定义 ExclusiveGatewayBehavior | — | 需明确分支条件 |
| 优势 | 完全控制节点行为,灵活性极高 | — | — | |
| 风险 | 容易破坏流程引擎的正常执行流程 | 错误的推进逻辑可能导致流程卡住或死循环 | — | 需充分测试 |
14.2 扩展流程引擎行为(ProcessEngineConfiguration)
| 扩展点 | 说明 | 配置方式 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| customPreBPMNParseHandlers | 在 BPMN 解析前执行自定义处理 | 注册 BpmnParseHandler | @Bean public ProcessEngineConfigurationCustomizer customizer() { return config -> config.setCustomPreBPMNParseHandlers(Collections.singletonList(new CustomParseHandler())); }; | 可修改 BPMN 模型结构,如自动添加监听器 |
| customPostBPMNParseHandlers | 在 BPMN 解析后执行处理 | 同上 | — | 常用于添加全局行为 |
| variableTypes | 注册自定义变量类型 | 实现 VariableType 接口并注册 | config.getVariableTypes().addType(new CustomVariableType()); | 用于序列化复杂对象 |
| jobExecutor | 自定义作业执行器 | 实现 JobExecutor | — | 高级用法,一般无需修改 |
| transactionFactory | 自定义事务工厂 | 实现 TransactionFactory | — | 与特定事务管理器集成时使用 |
| dataSource | 指定数据源 | 通过 Spring 配置 DataSource Bean | @Bean @FlowableDataSource public DataSource flowableDataSource() { ... } | 多数据源场景必需 |
| idGenerator | 自定义 ID 生成策略 | 实现 IdGenerator | config.setIdGenerator(new UUIDIdGenerator()); | 默认为 DBIdGenerator |
| eventListeners | 注册全局事件监听器 | 实现 FlowableEventListener | config.setEventListeners(Collections.singletonList(new CustomEventListener())); | 见 14.3 节 |
| failedJobCommandFactory | 自定义失败作业的处理逻辑 | 实现 FailedJobCommandFactory | — | 控制重试策略 |
| Spring Boot 配置 | 通过 ProcessEngineConfigurationCustomizer | 使用 Java Config | 如上所示 | 优先级高于 application.yml |
| 注意事项 | 扩展需谨慎,避免影响引擎稳定性 | — | — | 建议在测试环境充分验证 |
14.3 插件机制与事件总线(Flowable Event Bus)
| 机制 | 说明 | 实现方式 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 事件总线(Event Bus) | Flowable 内置的事件发布/订阅机制 | 基于观察者模式,发布 FlowableEvent | — | 核心扩展点,用于解耦 |
| 事件类型 | 引擎在关键节点发布的事件 | ENTITY_CREATED, ENTITY_UPDATED, TASK_ASSIGNED, TASK_COMPLETED, PROCESS_STARTED, PROCESS_COMPLETED, JOB_EXECUTION_SUCCESS, JOB_EXECUTION_FAILURE | 全面覆盖流程生命周期 | 可用于监控、审计、通知 |
| 事件监听器 | 订阅并处理事件的组件 | 实现 FlowableEventListener 接口 | public class TaskCompleteListener implements FlowableEventListener { @Override public void onEvent(FlowableEvent event) { if (event instanceof FlowableEntityEvent && event.getType() == FlowableEngineEventType.TASK_COMPLETED) { TaskEntity task = (TaskEntity) ((FlowableEntityEvent) event).getEntity(); log.info("任务完成: " + task.getName()); } } } | 需注册到 ProcessEngineConfiguration |
| 监听器注册 | 将监听器添加到引擎配置 | config.setEventListeners(...) 或 config.setTypedEventListeners(...) | // 监听所有事件 config.setEventListeners(Arrays.asList(new GlobalEventListener())); // 监听特定类型 config.setTypedEventListeners(Collections.singleton(FlowableEngineEventType.TASK_COMPLETED), Arrays.asList(new TaskCompleteListener())); | typedEventListeners 性能更优 |
| 异步监听器 | 事件处理在异步线程中执行 | 实现 FlowableAsyncEventListener | public class AsyncEmailListener implements FlowableAsyncEventListener { @Override public void onEventAsync(FlowableEngineEntityEvent event) { // 异步发送邮件 } } | 避免阻塞主流程执行 |
| 插件机制 | 通过 ProcessEnginePlugin 在引擎启动/关闭时执行逻辑 | 实现 ProcessEnginePlugin 接口 | public class CustomPlugin implements ProcessEnginePlugin { @Override public void preInit(ProcessEngineConfigurationImpl config) { } @Override public void postInit(ProcessEngineConfigurationImpl config) { } @Override public void postProcessEngineBuild(ProcessEngine processEngine) { } } | 是最强大的扩展方式,可用于注册所有扩展点 |
| 插件注册 | 将插件注册到引擎 | Spring Boot 中声明为 @Bean | @Bean public ProcessEnginePlugin customPlugin() { return new CustomPlugin(); } | 自动被 ProcessEngineFactoryBean 发现并调用 |
| 应用场景 | ||||
| 全局审计日志 | 记录所有流程操作 | 在监听器中写入数据库或发送到日志系统 | — | 替代数据库触发器 |
| 自动通知 | 任务完成时发送邮件/消息 | 在 TASK_COMPLETED 事件中调用通知服务 | — | 异步执行避免影响性能 |
| 数据同步 | 流程状态变化时同步到其他系统 | 在 PROCESS_COMPLETED 事件中调用 REST API | — | 实现系统间集成 |
| 监控与告警 | 监控超时、失败作业 | 在 JOB_EXECUTION_FAILURE 事件中触发告警 | — | 提升系统稳定性 |
| 优势 | 非侵入式扩展,保持核心逻辑纯净 | — | — | 推荐的扩展方式 |
| 性能影响 | 监听器过多或处理逻辑过重会影响性能 | 使用异步监听器,优化处理逻辑 | — | 避免在监听器中执行耗时操作 |