Flowable 基础
1. 基础知识科普
1.1 工作流发展
BPM(BusinessProcessManagement),业务流程管理是一种管理原则,通常也可以代指BPMS(BusinessProcessManagementSuite),是一个实现整合不同系统和数据的流程管理软件套件。
BPMN(BusinessProcessModelandNotation)是基于流程图的通用可视化标准。该流程图被设计用于创建业务流程操作的图形化模型。业务流程模型就是图形化对象的网状图,包括活动和用于定义这些活动执行顺序的流程设计器。BPMN2.0正式版本于2011年1月3日发布,常见的工作流引擎如:Activiti、Flowable、JBPM 都基于 BPMN 2.0 标准。
然后来看看BPM的发展历程:

[!tip]
本次以6.X版本为例,不同版本所需要的环境不一样,请仔细了解。
对应的GitHub地址:https://github.com/flowable/flowable-engine
对应的中文文档地址:https://tkjohn.github.io/flowable-userguide/#download
1.2 流程设计器
1.2.1 官方的FlowableUI
Flowable官方给我们提供了一个功能完备的基于web应用的流程设计器。可以用于流程相关的操作。具体提供了如下的功能:
Flowable IDM:身份管理应用。为所有Flowable UI应用提供单点登录认证功能,并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。Flowable Modeler:让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。Flowable Task:运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。Flowable Admin:管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎,并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过REST API连接至引擎,并与Flowable Task应用及Flowable REST应用一同部署。
所有其他的应用都需要Flowable IDM提供认证。每个应用的WAR文件可以部署在相同的servlet容器(如Apache Tomcat)中,也可以部署在不同的容器中。由于每个应用使用相同的cookie进行认证,因此应用需要运行在相同的域名下。
1.2.2 BPMN.js自定义
FlowableUI是官方提供的,针对国内复杂的流程业务需求有时并不能很好的满足企业的工作流的需求。这时我们就可以基于bpmn.js来自定义流程设计器。
官网地址:https://bpmn.io/toolkit/bpmn-js/walkthrough/

开源的学习资料:https://github.com/LinDaiDai/bpmn-chinese-document/tree/master/LinDaiDai
1.2.3 第三方的设计器
如何感觉完全基于bpmn.js来从零开发一个流程设计器太费时了。也可以找一些开源的别人写好的流程设计器比如:
- https://gitee.com/zhangjinlibra/workflow-engine?_from=gitee_search
- https://gitee.com/MiyueSC/bpmn-process-designer?_from=gitee_search
- 其他的可以自行在GitHub或者gitee上查找
最后我们可以不借助流程设计器来定义流程图。我们可以通过BpmnModle对象来自定义流程图
2. 流程审批初体验
2.1 FlowableUI安装
官方提供的FlowableUI是集成在Flowable源码包中的。但是在最新的7.0.0中已经移除了。我们需要在6.7.2中获取。


下载好了之后,我们可以直接用java -jar flowable-ui.war命令来运行它。

之后访问地址:http://localhost:8080/flowable-ui


2.2 用户管理
流程审批是伴随着用户的审批处理的。所以肯定需要用户的参与。我们先通过身份管理应用程序来创建两个测试的用户。


点击进入后可以看到在FlowableUI中针对用户这块是从三个维度来管理的
- 用户
- 组
- 权限控制
我们先就创建普通的用户。并分配相关的权限:

创建了zhangsan并设置了密码为123。之后来到权限控制维度:

以下是每项权限的意义:
- 访问idm应用(Access IDM Application):IDM 是 Identity Management(身份管理)的缩写。赋予用户访问 Flowable IDM 应用的权限,用于管理用户、组和权限。
- 访问admin应用(Access Admin Application):赋予用户访问 Flowable Admin 应用的权限。该应用用于管理流程引擎,包括监控和管理流程实例、作业、历史数据等。
- 访问modeler应用(Access Modeler Application):赋予用户访问 Flowable Modeler 应用的权限,该应用用于创建和管理业务流程模型(BPMN、CMMN、DMN 等)。
- 访问workflow应用(Access Workflow Application):赋予用户访问 Flowable Task(或 Workflow)应用的权限,该应用主要用于任务处理、流程实例管理和用户操作界面。
- 访问REST API(Access REST API):赋予用户调用 Flowable 提供的 REST API 的权限,REST API 允许通过编程方式与 Flowable 引擎交互,如启动流程实例、完成任务等。
各个权限对应首页如下:

下面我们为新建的用户分配权限:

这里我为用户分配modeler以及workflow权限。同样的操作再给李四分配权限。
现在我们切换张三或者李四用户来首页看看:

2.3 流程定义
有了相关的用户信息。我们就可以来创建流程图了。这块我们需要通过建模应用程序来实现。

点击后进入,看到如下的界面:


点击创建新模型就会进入到具体的流程图的绘制界面:

创建第一个请假流程图:

上图流程涉及到常用的一些概念:
- 左侧的圆圈叫做**启动事件(start event)**。这是一个流程实例的起点。
- 左上角有小人的矩形是一个**用户任务(user task)**。这是流程中人类用户操作的步骤。在这个例子中,人事、经理需要批准。
- 右侧的粗线圆圈叫做**结束事件(end event)**。这是一个流程的结束。
- 还有一个很常用的没有再上图中体现出来:**网关(gateway)**,它能够控制流程的走向。
上面的流程解释:请假人发起流程,之后人事审批,人事审批之后,就来到经理审批,经理审批之后,整个流程就结束了。
但是咋们的用户任务其实还差东西的,既然叫用户任务了,那么肯定要人来执行这个任务,现在咋们就需要为用户任务指定用户来执行。

这里的用户分配有两种:

- 身份存储:选择这种方式的时候,是选择本系统当前存在的用户。
- 固定值:可以指定本系统内和外的用户,比如我当前系统只有3个用户:admin、zhangsan、lisi;你可以选择本系统外的用户。
[!caution]
这里说的系统指的是官方提供的flowable-ui;不是你自己的业务系统。

到此流程定义完成。
2.4 SpringBoot插件支持
从上面的小结可以看出,我们是用了Flowable官方提供的流程设计器,其实,在SpringBoot中,目前也有相关的插件支持:

插件安装好了之后,我们就在resources文件夹下创建文件夹,里面存放xxx.bpmn20.xml相关的流程文件,比如我这里叫processes:


之后在绘制区右键就可以呼出各个绘制工具了:

各个绘制工具说明:
| 分类 | 节点 | 说明 |
|---|---|---|
| Start Events(开始事件) | Start Event | 流程的起点,手动触发。(重要) |
| Start Conditional Event | 条件满足时自动触发。 | |
| Start Message Event | 接收到特定消息时自动触发。 | |
| Start Error Event | 发生指定错误时自动触发。 | |
| Start Escalation Event | 需要升级或转交给更高级别人员时自动触发。 | |
| Start Signal Event | 接收到特定信号时自动触发。 | |
| Start Timer Event | 经过特定时间后自动触发。 | |
| Activities(活动) | User Task(用户任务) | 这是最常见的任务类型,需要指定人工参与者来完成, 常用于需要人工审批、处理的环节(重要) |
| Service Task(服务任务) | 用于执行自动化的系统服务, 适用于自动处理数据、调用外部系统等场景。 | |
| Script Task(脚本任务) | 使用脚本语言(如Groovy、JavaScript)编写的自定义任务。用于执行脚本代码。 | |
| Business Rule Task(业务规则任务) | 用于执行业务规则。适用于复杂的业务决策场景 。 | |
| Manual Task(手动任务) | 表示一个需要手动完成但不需要系统记录的任务,它不会创建任务实例, 适用于线下操作的场景。 | |
| Receive Task(接收任务) | 用于等待外部消息的到达,流程执行到此会暂停,直到收到特定消息。常用于流程间的通信 。 | |
| Mail Task(邮件任务) | 用于发送电子邮件,可配置邮件模板、收件人等信息。用于流程中的通知环节 。 | |
| Camel Task(驼峰任务) | 集成Apache Camel框架,用于复杂的企业集成场景。支持多种协议和数据格式转换。 | |
| Http Task(HTTP任务) | 用于发送HTTP请求,支持REST API调用。适用于与外部系统的HTTP交互。 | |
| Mule Task(Mule任务) | 集成Mule ESB,用于企业服务总线集成。支持复杂的系统集成场景。 | |
| Structural(结构) | Sub Process(子流程) | 封装一组相关任务,适用于减少流程复杂性。 |
| Event Sub Process(事件子流程) | 用于响应特定的事件(如超时、错误等),适合处理异常或特殊情况。 | |
| Collapsed Sub Process(折叠子流程) | 简化流程视图,隐藏细节,适用于复杂但不需要展示细节的流程。 | |
| Call Activity(调用活动) | 复用其他流程,提高模块化,适合流程间的依赖和复用。 | |
| Adhoc Sub Process(自组网子流程) | 灵活执行,不受顺序限制,适用于并行或自由执行的任务。 | |
| Gateways(网关) | Exclusive Gateway(排他网关) | 用于选择唯一的分支路径,互斥选择,只有一个路径执行。(重要) |
| Parallel Gateway(并行网关) | 用于分发并行任务,所有路径同时执行。任务并行执行,所有任务必须完成后才能继续。 | |
| Inclusive Gateway(包容网关) | 选择一个或多个路径执行。有多个路径可以执行,选择一个或多个并行执行。 | |
| Complex Gateway(复合网关) | 理复杂的分支和合并逻辑。业务逻辑复杂,需要自定义复杂条件控制。 | |
| Boundary Events(边界事件) | Error Boundary Event(边界错误事件) | 用于捕获流程执行过程中的错误,当任务执行出错时触发。可以定义错误处理流程,实现异常处理逻辑。 |
| Escalation Boundary Event(边界升级事件) | 用于业务升级场景,当需要将任务升级或提交到更高级别处理时使用。可以触发额外的处理流程。 | |
| Timer Boundary Event(边界计时器事件) | 基于时间的触发机制,可以设置特定时间点、时间间隔或循环时间。常用于超时处理、定时检查等场景。 | |
| Signal Boundary Event(边界信号事件) | 用于捕获流程范围内的信号, 可以跨越流程实例进行通信。适用于需要广播通知的场景。 | |
| Cancel Boundary Event(边界取消事件) | 专门用于事务子流程,当需要取消事务时触发。用于处理事务回滚场景。 | |
| Registration Boundary Event(边界注册事件) | 用于处理注册相关的边界事件,可以监听特定的注册活动。适用于需要响应注册行为的场景。 | |
| Boundary variable listener event(边界变量监听事件) | 监听流程变量的变化,当指定变量发生改变时触发。用于响应数据状态的变化。 | |
| Conditional Boundary Event(边界条件事件) | 基于条件表达式的触发机制,当条件满足时触发。用于业务规则的动态判断。 | |
| Compensation Boundary Event(边界补偿事件) | 用于处理补偿逻辑,当需要撤销或补偿某个已完成的活动时使用。适用于长事务的补偿处理。 | |
| Intermediate Catching Events(中间捕获事件) | Intermediate Timer Catching Event(中间计时器捕获事件) | 用于在流程执行过程中等待特定的时间条件,可以设置具体的时间点、持续时间或循环间隔。适用于等待特定时间后继续流程、定期执行某些任务、实现延时处理。 |
| Intermediate Signal Catching Event(中间信号捕获事件) | 用于捕获流程中的信号,可以跨流程实例接收信号。适用于流程间的异步通信、全局事件的处理、多流程的协调。 | |
| Intermediate Message Catching Event(中间消息捕获事件) | 等待特定消息的到达,需要明确定义消息的名称和数据结构。适用于业务流程间的消息传递、等待外部系统响应、流程同步点。 | |
| Intermediate Conditional Catching Event(中间条件捕获事件) | 等待特定条件满足后继续执行,基于表达式评估条件。适用于业务规则触发、数据状态监控、条件控制流程。 | |
| Intermediate event registry catching event(中间事件注册捕获事件) | 用于捕获已注册的事件,监听特定类型的系统事件。适用于事件驱动架构、系统集成场景、外部事件处理。 | |
| Intermediate variable listener catching event(中间变量监听捕获事件) | 监听流程变量的变化,当指定变量满足条件时触发。适用于数据变更监控、状态转换处理、变量依赖流程。 | |
| Intermediate Throwing Events(中间抛出事件) | None Intermediate Throwing Event(中间无抛出事件) | 是一个空的抛出事件,不包含特定的数据。主要用于流程的标记点或同步点。可以用来表示流程中的某个重要节点,但不需要传递特定信息,常用于流程的可视化或文档化。 |
| Signal Intermediate Throwing Event(中间信号抛出事件) | 用于向所有可能的接收者广播信号,信号可以被同一流程或其他流程中的信号捕获事件捕获。适用于跨流程通信、广播式的消息通知、触发多个相关流程。 | |
| Escalation Intermediate Throwing Event(中间升级抛出事件) | 用于触发业务升级流程,与错误事件不同,升级事件表示正常的业务场景。适用于任务升级处理、上报机制、管理层介入流程。 | |
| Compensation Intermediate Throwing Event(中间补偿投掷事件) | 用于触发补偿处理,可以激活之前已完成活动的补偿处理器。适用于回滚操作、补偿性事务处理、撤销已完成的工作。 | |
| End Events(结束事件) | End Event(结束事件) | 最基本的结束事件类型,表示流程或子流程的正常结束,不触发任何额外的行为。(重要) |
| Error End Event(结束错误事件) | 用于抛出业务错误并结束当前流程,可以被上层流程的错误边界事件捕获。适用于异常流程的结束、错误场景的处理 、触发错误处理流程。 | |
| Escalation End Event(结束升级事件) | 触发业务升级并结束当前流程,可以被父流程中的升级边界事件捕获。适用于需要上级介入的情况、流程升级处理、特殊情况的上报。 | |
| Cancel End Event(结束取消事件) | 专门用于事务子流程中,触发取消操作并结束流程。适用于事务的回滚、取消整个事务、触发补偿处理。 | |
| Terminate End Event(结束中止事件) | 立即终止整个流程实例,结束所有正在执行的活动。适用于需要强制结束流程、紧急停止处理、流程的非正常终止。 |
[!tip]
上表不仅对应插件的绘制工具,同时和flowable官方的也对应。
3. SpringBoot整合Flowable
因为本次是用的是Flowable6.x,所以JDK用JDK8、SpringBoot用2.x就可以了。
添加Flowable与SpringBoot整合依赖:
<!--Flowable的核心依赖-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
3.1 流程部署
流程部署我们可以在SpringBoot的环境下部署。也可以在非SpringBoot的环境下部署。
3.1.1 非SpringBoot环境部署
使用非SpringBoot环境来部署,主要有两步
流程引擎的配置与构造:主要是数据源的配置
//构造流程引擎配置 ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration() .setJdbcUrl("jdbc:mysql://localhost:3306/flowable_test?serverTimezone=UTC&nullCatalogMeansCurrent=true") .setJdbcUsername("root") .setJdbcPassword("root") .setJdbcDriver("com.mysql.cj.jdbc.Driver") .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); //构建流程引擎 ProcessEngine processEngine = cfg.buildProcessEngine();在这个过程中,Flowable会在你的数据库中自动创建对应的表结构。其中databaseSchemaUpdate,它用于设置流程引擎启动关闭时使用的数据库表结构控制策略:
DB_SCHEMA_UPDATE_FALSE:不更新数据库表结构,适用于生产环境。DB_SCHEMA_UPDATE_TRUE:自动更新数据库表结构,适用于开发和测试环境。DB_SCHEMA_UPDATE_DROP_CREATE:删除现有表并重新创建,适用于早期开发或特殊需求,但会丢失现有数据。
使用配置来构造流程引擎对象并且部署
Deployment deploy = processEngine.getRepositoryService().createDeployment() .addClasspathResource("processes/请假审批流程.bpmn20.xml") .name("请假审批流程") .deploy(); System.out.println("流程部署的ID:"+deploy.getId());这里我的流程图类路径资源给的
processes/请假审批流程.bpmn20.xml,因为默认类路径加载的其实为resource目录,而我的流程图存放在resource下的processes文件夹下,流程图的来源就是我之前在Flowable-UI中绘制的然后下载的:

部署完成之后会在如下三张表中留下记录:
act_ge_bytearray:记录流程定义的资源信息。xml和流程图的图片信息。act_re_deployment:流程部署表,记录这次的部署行为act_re_procdef:流程定义表,记录这次部署动作对应的流程定义信息
这里需要区分一下流程部署和流程定义的关系:一次部署可以部署多个流程定义
Deployment deploy = processEngine.getRepositoryService().createDeployment()
.addClasspathResource("processes/请假审批流程.bpmn20.xml") //部署第一个流程定义
.addClasspathResource("processes/流程2.bpmn20.xml") //部署第二个流程定义
.addClasspathResource("processes/流程3.bpmn20.xml") //部署第三个流程定义
.name("请假审批流程")
.deploy();

当然上面的操作我们是自己定义ProcessEngine和ProcessEngineConfiguration来实现流程引擎对象的获取操作,我们完全可以把这些初始化的操作交给Spring容器来管理。接下来我们看看在SpringBoot项目中怎么简化处理。
3.1.2 SpringBoot环境
依赖我们上面已经添加了。然后需要在application.yml中配置如下信息:
server:
port: 8888
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable_test?serverTimezone=UTC&nullCatalogMeansCurrent=true
username: root
password: root
flowable:
# 异步执行器会被激活,系统会自动处理异步任务,如定时器事件、异步服务任务等;为false,系统不会执行异步任务
async-executor-activate: true
# 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
database-schema-update: true
基本上就是数据库以及flowable的相关配置了。
[!caution]
在url中需要添加
nullCatalogMeansCurrent=true属性。不然启动服务的时候会出现如下问题:Table xxx.act_ge_property doesn't exist。
之前我们是非SpringBoot环境的时候,我们需要先构造流程引擎,然后使用流程引擎来完成流程定义的部署,现在我们在SpringBoot环境中的配置文件中,配置好了数据库以及flowable的其他配置之后,我们只需要将流程引擎注入进来之后部署即可:
//注入流程引擎
@Resource
private ProcessEngine processEngine;
/**
* 部署流程
*/
@Test
void deployProcess(){
Deployment deploy = processEngine.getRepositoryService().createDeployment()
.addClasspathResource("processes/请假审批流程.bpmn20.xml")
.name("请假审批流程")
.deploy();
System.out.println("流程部署的ID:"+deploy.getId());
}
之后启动部署即可。
3.2 启动流程实例
流程部署完毕之后,我们就可以启动流程实例来开始走流程了。
启动流程步骤:
确保流程定义已经被部署到了流程引擎中
由于3.1小节我们已经做了,这里就不再重复进行。
获取RuntimeService
RuntimeService提供了启动流程实例、查询流程实例和管理运行时相关操作的 API。如果是在SpringBoot环境,直接注入即可,否则,需要通过ProcessEngine(流程引擎)来获取RuntimeService。确定流程定义的key或者流程定义的ID
这是启动某个具体流程的重要信息,信息来源均可查看表
act_re_procdef。
从图可知,对于同一个流程图,多次部署,他们的流程定义Key都是相同的,但是他们的流程定义ID均是不同的。不管是这里的启动流程实例还是查询流程实例,都可以通过流程实例ID或者流程定义Key查询出来。
通过RuntimeService的API来启动流程实例
主要API如下:


参数解释如下:
processDefinitionKey:流程定义Key,对应act_re_procdef表中的key字段。processDefinitionId:流程定义ID,对应act_re_procdef表中的id字段。businessKey:业务主键,通常用来关联业务数据与流程的。例如,可以将一个订单号、用户 ID 或其他业务相关的唯一标识作为businessKey,方便在后续流程中查询流程实例与业务对象的关联。variables:流程变量,是一个键值对的Map,用于启动流程时传递初始化参数。这些变量可以在流程中通过任务或网关等组件使用,也可以在执行过程中动态更新。
启动流程实例代码:
@Resource
private RuntimeService runtimeService;
/**
* 发起流程
*/
@Test
void startProcess() {
//发起流程之前应该确保流程已被部署
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:a52c68f3-b9cd-11ef-8673-286b35ef7f21");
log.info("当前流程的实例ID为:【{}】",processInstance.getId());
}

流程启动之后,会从发起节点走到人事审批节点,然后再这个节点阻塞着:

这时我们可以在act_ru_task表中看到对应的记录。act_ru_task记录的都是当前待办的记录信息:

[!tip]
每启动一个流程实例那么就会在
act_hi_procinst表中维护一条记录。然后在act_ru_execution会记录流程的分支。
这里需要提一下流程定义和流程实例的关系:
- 流程定义:相当于Java中的类。
- 流程实例,相当于Java中的类的实例对象。
3.3 流程查询
这里主要讲解流程查询的相关API。流程的查询主要包括3类:
- 流程实例查询(ProcessInstanceQuery):例如监控流程的运行状态,检查某些流程实例是否还在运行;查询某个流程实例的当前状态;根据业务主键关联流程实例,可以从业务系统中找到对应的流程实例等。
- 任务查询(TaskQuery):例如获取用户的代办任务;查询某任务的当前处理人或候选人,用于任务分配或转交;查询某任务的变量、所属流程实例、开始时间等信息,辅助决策或展示任务详情等。
- 历史查询(HistoryService):例如获取已完成的流程或任务;审计需求;历史变量的查询;查询被删除或终止的流程实例;流程变更后的回溯查询等。
3.3.1 流程实例查询(ProcessInstanceQuery)
首先需要创建流程实例查询对象:
ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
之后可以链式调用各个查询条件,常见的查询条件如下:
按流程实例ID查询
query.processInstanceId("流程实例ID");按业务主键查询
query.processInstanceBusinessKey("某业务主键");按流程定义Key查询
query.processDefinitionKey("流程定义Key");按流程定义ID查询
query.processDefinitionId("流程定义ID");查询激活的流程
query.active();查询已挂起的流程
query.suspended();按变量过滤
query.variableValueEquals("variableName", value);//等于某变量值 query.variableValueNotEquals("variableName", value); //不等于某变量值 query.variableValueGreaterThan("variableName", value); //变量大于值 query.variableValueLessThan("variableName", value); //变量小于值
查询条件构造好了之后,就可以获取查询结果:
单个结果:返回唯一匹配的流程实例;如果有多个结果,会抛出异常。
ProcessInstance instance = query.singleResult();结果列表:返回符合条件的流程实例列表。
List<ProcessInstance> instances = query.list();分页查询:分页返回流程实例,从第 0 条记录开始,返回 10 条。
List<ProcessInstance> instances = query.listPage(0, 10);统计数量:返回匹配条件的流程实例总数。
long count = query.count();
完整示例:
/**
* 流程实例查询
*/
@Test
void processInstanceQuery(){
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId("768dca14-b9ce-11ef-9405-286b35ef7f21")
.active()
.singleResult();
log.info("获取到的流程实例ID:【{}】",processInstance.getId());
}
[!tip]
这里补充一点,对于挂起的流程定义,不可以再新启对应的流程,但是已经启动了的流程不受影响。
3.3.2 任务查询(TaskQuery)
任务查询主要用于查询某个用户当前的任务实例。
首先创建任务查询对象:
TaskQuery taskQuery = taskService.createTaskQuery();
之后可以链式调用各个查询条件,常见的查询条件如下:
按任务ID查询
taskQuery.taskId("任务ID");按任务名称查询
taskQuery.taskName("taskName"); //任务名称精确匹配 taskQuery.taskNameLike("%approval%"); //任务名称模糊匹配按流程实例ID查询
taskQuery.processInstanceId("流程实例ID");按负责(指派)人查询
taskQuery.taskAssignee("负责(指派)人");按候选人查询
taskQuery.taskCandidateUser("候选人");按创建时间过滤
taskQuery.taskCreatedAfter(date); taskQuery.taskCreatedBefore(date);按变量过滤
taskQuery.processVariableValueEquals("variableName", value);//等于流程变量值 taskQuery.processVariableValueNotEquals("variableName", value);//不等于流程变量值 taskQuery.processVariableValueGreaterThan("variableName", value);//大于流程变量值 taskQuery.processVariableValueLessThan("variableName", value);//小于流程变量值
查询条件构造好了之后,就可以获取查询结果:
单个任务:返回唯一匹配的流程实例;如果有多个结果,会抛出异常。
Task task = taskQuery.singleResult();任务列表:返回符合条件的流程实例列表。
List<Task> tasks = taskQuery.list();分页查询:分页返回流程实例,从第 0 条记录开始,返回 10 条。
List<Task> tasks = taskQuery.listPage(0, 10);任务统计数量:返回匹配条件的流程实例总数。
long count = taskQuery.count();
完整案例
@Resource
private TaskService taskService;
/**
* 任务实例查询
*/
@Test
void taskQuery() {
List<Task> tasks = taskService.createTaskQuery()
.active()
.processInstanceId("768dca14-b9ce-11ef-9405-286b35ef7f21")
.list();
for (Task task : tasks) {
log.info("获取到任务的ID:【{}】;当前任务流程实例ID:【{}】", task.getId(), task.getProcessInstanceId());
}
}
3.3.3 历史查询(HistoryService)
历史查询主要用户查询已完成的流程实例和任务。
常见的查询对象:
历史流程实例查询(HistoricProcessInstanceQuery)
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();历史任务查询(HistoricTaskInstanceQuery)
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery();历史活动实例查询(HistoricActivityInstanceQuery)
HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery();历史变量查询(HistoricVariableInstanceQuery)
HistoricVariableInstanceQuery query = historyService.createHistoricVariableInstanceQuery();
常见的查询条件,这里以历史流程实例查询为例::
按流程实例ID查询
query.processInstanceId("流程实例ID");按结束时间查询
query.finishedBefore(date); query.finishedAfter(date);按流程变量查询
query.variableValueEquals("variableName", value);//等于某变量值 query.variableValueNotEquals("variableName", value); //不等于某变量值 query.variableValueGreaterThan("variableName", value); //变量大于值 query.variableValueLessThan("variableName", value); //变量小于值
常见的查询结果也和3.3.1以及3.3.2小节的相似。
完整示例
@Resource
private HistoryService historyService;
/**
* 历史查询
*/
@Test
void historyQuery() {
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId("768dca14-b9ce-11ef-9405-286b35ef7f21")
.singleResult();
log.info("获取到的历史流程实例ID:【{}】", historicProcessInstance.getId());
}
3.4 流程任务处理
流程启动后会进入到相关的流程节点。流程中任务是核心环节,通常由用户完成,也可以是系统自动完成。
在任务处理阶段,可能会调用如下API:
查询任务:参考3.3.2小节任务查询API。
完成任务:用户完成任务后,流程会根据模型定义流转到下一节点。
taskService.complete( "taskId", // 任务 ID variables // 可选:完成任务时传入的流程变量 );设置任务候选人
taskService.addCandidateUser("taskId", "userId");设置任务处理人
taskService.setAssignee("taskId", "userId");转办任务:直接将任务转交给其他用户处理。
taskService.setAssignee("taskId", "newUserId");委托任务:原处理人仍保留管理权。
taskService.delegateTask("taskId", "delegateUserId");
其中完成任务是核心。
在上面的请假流程案例中就会进入到人事审批节点。审批人是zhangsan,也就是zhangsan有了一条待办信息。这时候我们可以先查询下zhangsan有哪些待办任务,之后根据taskId(任务ID)依次完成任务。
/**
* 处理张三的代办任务
*/
@Test
void dealWithByZhangSan() {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("zhangsan") //根据责任(指派)人来查询
.list(); //张三可能有多个记录
for (Task task : tasks) {
log.info("获取到任务的ID:【{}】;当前任务流程实例ID:【{}】", task.getId(), task.getProcessInstanceId());
//依次处理多个代办
taskService.complete(task.getId()); //调用complete方法之后,当前节点就处理结束,流转到下一节点
log.info("当前流程处理结束");
}
}

根据请假流程图,人事审批通过后就会进入到经理审批的节点,由于目前请假流程还没结束,所以我们可以查看任务运行信息表(act_ru_task)中的信息:

之后我们再用相同的操作可以完成经理审批的待办处理。然后就完成了第一个流程的审批处理操作了,这里就不再重复演示了。
3.5 各个Service服务
在前面流程部署、启动、查询小节中,我们已经看到了一些Service服务,但是都没有仔细讲解一下,这一小节我们主要来了解一些各个Service服务。
参考地址:https://tkjohn.github.io/flowable-userguide/#chapterApi

3.5.1 各个Service创建方式
从上图中可以看出,ProcessEngineConfiguration是与各个Service交互的总入口,通过流程引擎配置对象能够拿到流程引擎对象,也就是ProcessEngine,之后就可以通过ProcessEngine实例对象来创建各个Service,如下:
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
不过这种方式都是在非SpringBoot环境中使用的,如果是在SpringBoot环境中,那么你可以直接通过依赖注入的方式来获取各个Service。
3.5.2 各个Service的介绍
| Service | 作用 | 常见使用场景 |
|---|---|---|
| RepositoryService | 管理流程定义和部署 | 部署流程模型,查询流程定义,删除部署。 |
| RuntimeService | 管理运行时流程实例和变量 | 启动流程实例,操作流程变量,挂起/激活流程。 |
| TaskService | 管理任务 | 查询、分配、完成任务,任务变量管理。 |
| HistoryService | 查询历史数据 | 查询已完成的流程实例、任务和变量记录。 |
| IdentityService | 管理用户和组 | 定义用户、组及其关系,权限管理。 |
| FormService | 管理流程表单 | 加载和提交启动表单或任务表单。 |
| ManagementService | 引擎管理与维护 | 作业管理,异常处理,获取数据库表信息。 |

4. 相关表结构介绍
当我们使用 Flowable 流程引擎的时候,虽然我们使用的是各种 API,但是小伙伴们都知道,这些 API 本质上操作的都是底层的数据表,Flowable基于6.7.2版本,默认一共生成了79张数据表。
4.1 Flowable数据库表命名规则
接下来我们就对这 79 张表进行一个简单的分类整理:
1. ACT_APP_*(5)
2. ACT_CMMN_*(12)
3. ACT_CO_*(3)
4. ACT_DMN_*(6)
5. ACT_EVT_*(1)
6. ACT_FO_*(6)
7. ACT_GE_*(2)
8. ACT_HI_*(10)
9. ACT_ID_*(9)
10. ACT_PROCDEF_*(1)
11. ACT_RE_*(3)
12. ACT_RU_*(13)
13. FLW_CHANNEL_*(1)
14. FLW_EV_*(2)
15. FLW_EVENT_*(3)
16. FLW_RU_*(2)
* 是通配符,后面的数字表示这种类型的表有多少个。大致看一下,其实还是有很多规律的。最明显的规律就表名通过两个 _ 隔开成了三部分,接下来我们就以此为线索,一部分一部分来讲。
4.1.1 表名前缀
首先大致看这个表的前缀,分了两种:ACT_、FLW_。
Flowable是基于Activiti开发出来的流程引擎,所以我们在很多地方都还能看到Activiti的影子,这个表名中ACT_就是Activiti。具体来说,与Flowable开源代码库相关的数据库表名以ACT_开头。特定于Flowable Work或Engage的数据库表以FLW_前缀开头。
4.1.2 表名中间部分
紧跟着ACT_或者FLW_后面的内容,基本上也都是简称,例如:
| 缩写 | 说明 |
|---|---|
| APP | 表示这都是跟应用程序相关的表。 |
| CMMN | 表示这都是跟 CMMN 协议相关的表。 |
| CO(CONTENT) | 表示这都是跟内容引擎相关的表。 |
| DM | 表示这都是跟 DMN 协议相关的表。 |
| EVT(EVENT) | 表示这都是跟事件相关的表。 |
| FO(FORM) | 表示这都是跟表单相关的表。 |
| GE(GENERAL) | 表示这都是通用表,适用于各种用例的。 |
| HI(HISTORY) | 这些是包含历史数据的表。当从运行时表中删除数据时,历史表仍然包含这些已完成实例的所有信息。 |
| ID(IDENTITY) | 表示这都是跟用户身份认证相关的表。 |
| PROCDEF(PROCESSDEFINE) | 表示这都是跟记录流程定义相关的表。 |
| RE(REPOSITORY) | 表示这都是跟流程的定义、流程的资源等等包含了静态信息相关的表。 |
| RU(RUNTIME) | 代表运行时,这些是包含尚未完成的流程、案例等的运行时数据的运行时表。Flowable 仅在执行期间存储运行时数据,并在实例结束后删除记录,这使运行时表保持小而快。 |
| CHANNEL | 表示这都是跟泳道相关的表。 |
| EV | 表示这个是跟 FLW_ 搭配的,在这里似乎并没有一个明确的含义,相关的表也都是跟 Liquibase 相关的表。 |
| EVENT | 表示这都是跟事件相关的表。 |
把这些简称的单词搞明白了,接下来的就容易看懂每一个表的含义了。
表名是由三部分构成的,我们现在已经分析了前两部分了,接下来我们来看第三部分。
4.1.3 表名后缀
表名的后缀,有一些是通用的后缀名词,我们先来看下。
DATABASECHANGELOG:表名中包含这个单词的,表示这个表是Liquibase执行的记录,Liquibase是一个数据库脚本管理的工具,有点像flyway。包含DATABASECHANGELOG后缀的表一共是 6 张。DATABASECHANGELOGLOCK:表名中包含这个单词的,表示这个表记录Liquibase执行锁的,用以确保一次只运行一个Liquibase实例,包含DATABASECHANGELOGLOCK后缀的表也是 6 张。
这两个加起来就 12 张表了,一共 79 张,现在还剩 67 张,我们来详细看下。
在后面的介绍中,凡是涉及到DATABASECHANGELOG和DATABASECHANGELOGLOCK的表,我就直接省略了。
4.1.3.1 ACT_APP_*
以ACT_APP_*开头的表负责应用引擎存储和应用部署定义。相关的表一共是五张,如下:

| 表名 | 说明 |
|---|---|
ACT_APP_APPDEF |
应用程序模型产生应用程序定义。此定义(如流程/案例等)是成功部署到应用引擎的应用模型的表示。 |
ACT_APP_DEPLOYMENT |
当通过应用引擎部署应用模型时,会存储一条记录以保存此部署。部署的实际内容存储在 ACT_APP_DEPLOYMENT_RESOURCE 表中,并从该表中引用。 |
ACT_APP_DEPLOYMENT_RESOURCE |
此表包含构成应用程序部署的实际资源(存储为字节)。当引擎需要实际模型时,将从该表中获取资源。 |
4.1.3.2 ACT_CMMN_*
Flowable CMMN Engine 的数据库名称都以ACT_CMMN_开头。这里涉及到一个东西就是CMMN,CMMN与BPMN协议一致,也是一种流程内容的规范,CMMN这类表一般用于存储处理BPMN所不能适用的业务场景数据,CMMN通常与BPMN搭配使用,不过只有符合CMMN规范的模型数据才会使用这类表。
这里涉及到的相关表一共是 12 张:

上图中:ACT_CMMN_CASEDEF、ACT_CMMN_DEPLOYMENT、ACT_CMMN_DEPLOYMENT_RESOURCE
这三个都是没有附加前缀的表,主要定义了静态信息,例如案例的定义和部署和以及相关的资源等。
接下来这些以ACT_CMMN_HI_开头的表代表历史数据,例如过去的案例实例、计划项目等。
| 表名 | 说明 |
|---|---|
ACT_CMMN_HI_CASE_INST |
此表记录由 CMMN 引擎启动的每个案例实例的数据。 |
ACT_CMMN_HI_MIL_INST |
此表记录了在案例实例中达到的每个里程碑的数据。 |
ACT_CMMN_HI_PLAN_ITEM_INST |
此表记录了作为案例实例执行的一部分创建的每个计划项实例的数据。 |
接下来以ACT_CMMN_RU_开始的表代表运行时的数据,这些数据包含案例实例、计划项等的运行时数据。Flowable仅在案例实例执行期间存储运行时数据,并在案例实例结束时删除记录,这使运行时表保持小且查询速度快:
| 表名 | 说明 |
|---|---|
ACT_CMMN_RU_CASE_INST |
此表包含每个已启动但尚未完成的案例实例的条目。 |
ACT_CMMN_RU_MIL_INST |
此表包含作为运行案例实例的一部分达到的每个里程碑的条目。 |
ACT_CMMN_RU_PLAN_ITEM_INST |
案例实例执行由案例定义中定义的计划项的多个实例组成,此表包含在案例实例执行期间创建的每个实例的条目。 |
ACT_CMMN_RU_SENTRY_PART_INST |
计划项目实例可以有守卫状态转换的哨兵,这样的哨兵在状态改变之前可以包含多个部分,这个表就是专门用来存储这种哨兵。 |
4.1.3.3 ACT_DMN_*
Flowable DMN 的数据库名称都以ACT_DMN_开头,这里涉及到的表一共是 6 张:

其中ACT_DMN_DEPLOYMENT与ACT_DMN_DEPLOYMENT_RESOURCE不多说,跟前面的都一样,只不过这里部署的是DMN。
值得一提的是如下两个表:
| 表名 | 说明 |
|---|---|
ACT_DMN_DECISION |
此表包含已部署决策表的元数据,并与来自其他引擎的定义相对应。 |
ACT_DMN_HI_DECISION_EXECUTION |
此表包含有关 DMN 决策表执行的审计信息。 |
4.1.3.4 ACT_RU_*
以ACT_RU_开头的表都是和流程引擎运行时信息相关的一些表。涉及到的表一共有 13 张:

其中ACT_RU_ACTINST为流程实例中的每个活动在此表中都有一行来指示活动的当前状态。Flowable引擎使用作业表来实现异步逻辑、计时器或历史处理。这些表存储每个作业所需的数据。相关的表为:ACT_RU_JOB、ACT_RU_TIMER_JOB、ACT_RU_SUSPENDED_JOB、ACT_RU_EXTERNAL_JOB、ACT_RU_HISTORY_JOB、ACT_RU_DEADLETTER_JOB。
其他关键表:
| 表名 | 说明 |
|---|---|
ACT_RU_ENTITYLINK |
此表存储有关实例的父子关系的信息。例如,如果流程实例启动子案例实例,则此关系存储在此表中。这样可以轻松查询关系。 |
ACT_RU_EVENT_SUBSCR |
当流程定义使用事件(信号/消息等或启动/中间/边界)时,引擎将对该事件的引用存储在该表中。这简化了查询哪些实例正在等待某种类型的事件。 |
ACT_RU_EXECUTION |
存储流程实例和指向流程实例当前状态的指针(称为执行)。 |
ACT_RU_IDENTITYLINK |
此表存储有关用户或组的数据及其与(流程/案例等)实例相关的角色。该表也被其他需要身份链接的引擎使用。 |
ACT_RU_TASK |
此表包含正在运行的实例的每个未完成用户任务的条目。然后在查询用户的任务列表时使用此表。CMMN 引擎也使用此表。 |
ACT_RU_VARIABLE |
此表存储与实例相关的变量。CMMN 引擎也使用此表。 |
4.1.3.5 ACT_HI_*
以ACT_HI_*开头的表包含正在运行和已完成的实例的历史数据,这些表的名称遵循其运行时对应的名称,这里一共涉及到 10 张表:

关键表:
| 表名 | 说明 |
|---|---|
ACT_HI_ACTINST |
历史活动信息。记录流程流转过的所有节点,与ACT_HI_TASKINST不同的是,ACT_HI_TASKINST 只记录Task内容。 |
ACT_HI_ATTACHMENT |
历史附件表。 |
ACT_HI_COMMENT |
流程的历史评论表。 |
ACT_HI_DETAIL |
历史详情表:流程中产生的变量详细,包括控制流程流转的变量,业务表单中填写的流程需要用到的变量等。 |
ACT_HI_ENTITYLINK |
历史参与的人员表。 |
ACT_HI_IDENTITYLINK |
任务参与者数据表,主要存储历史节点参与者的信息,可能是 Group 也可能是 User。 |
ACT_HI_PROCINST |
保存每一个历史流程,创建时就生成,一条流程实例对应一个记录。 |
ACT_HI_TASKINST |
记录每一个历史节点,一个 Task 对应一个记录。 |
ACT_HI_TSK_LOG |
每一次执行可能会带上数据,存在这里。 |
ACT_HI_VARINST |
流程历史变量表。 |
4.1.3.6 ACT_ID_*
以ACT_ID_*开头的都是和用户身份相关的表,一共是有 9 张表:

关键表:
| 表名 | 说明 |
|---|---|
ACT_ID_BYTEARRAY |
用户组的部署内容。 |
ACT_ID_GROUP |
用户组的表。 |
ACT_ID_INFO |
所有用户的信息,包括账号和密码。 |
ACT_ID_MEMBERSHIP |
用户和用户组的关联表。 |
ACT_ID_PRIV |
权限表。 |
ACT_ID_PRIV_MAPPING |
用户、用户组以及权限之间的关联表。 |
ACT_ID_PROPERTY |
用户的变量表。 |
ACT_ID_TOKEN |
用户访问记录表。 |
ACT_ID_USER |
用户表。 |
4.1.3.7 ACT_FO_FORM_*
以ACT_FO_FORM_开头的表存储表单引擎和围绕表单模型和这些表单的实例数据。一共4张表:

关键表:
| 表名 | 说明 |
|---|---|
ACT_FO_FORM_DEFINITION |
表单定义表。 |
ACT_FO_FORM_DEPLOYMENT |
表单部署表。 |
ACT_FO_FORM_INSTANCE |
表单实例表。 |
ACT_FO_FORM_RESOURCE |
表单源数据表。 |
4.1.3.8 ACT_GE_*
以 ACT_GE_ 开头的表表示一些通用信息表,涉及到的表一共是2个:

| 表名 | 说明 |
|---|---|
ACT_GE_BYTEARRAY |
存储每个流程的部署记录,bytes_ 字段中保存流程的具体内容。 |
ACT_GE_PROPERTY |
存储 Flowable 自身的一些变量,主要是版本号。 |
4.1.3.9 ACT_RE_*
以ACT_RE_开头的表表示这些表都是跟流程的定义、流程的资源等等包含了静态信息相关的表,一共有3个:

| 表名 | 说明 |
|---|---|
ACT_RE_DEPLOYMENT |
流程部署记录,每次服务重启会部署一次,这里会新增一条记录。 |
ACT_RE_MODEL |
创建模型时,额外定义的一些模型相关信息,存在这张表,默认不保存。 |
ACT_RE_PROCDEF |
记录流程的变更,流程每变更一次存一条记录,version_ 字段加 1。 |
4.1.3.10 FLW_EVENT_*
| 表名 | 说明 |
|---|---|
FLW_EVENT_DEFINITION |
已部署事件定义的元数据。 |
FLW_EVENT_DEPLOYMENT |
已部署事件部署元数据。 |
FLW_EVENT_RESOURCE |
事件所需资源。 |
4.1.3.11 其他表
还剩一些规则不太明显的表,如下:
| 表名 | 说明 |
|---|---|
FLW_RU_BATCH |
批量迁移流程时使用的表之一。 |
FLW_RU_BATCH_PART |
批量迁移流程时使用的表之一。 |
ACT_PROCDEF_INFO |
流程定义信息,对流程的说明。 |
ACT_CO_CONTENT_ITEM |
每项内容在此表中都有个条目。 |
ACT_EVT_LOG |
Flowable 引入了事件日志机制,保存事件日志。如果不使用事件日志,则可以删除此表。 |
FLW_CHANNEL_DEFINITION |
泳池管道定义表。 |
4.2 结合案例说明表结构
当然如果只是看懂表的命名于含义是远远不够的如何搞懂Service调用才是关键,那么下面我们将根据代码案例讲解表的关系。
4.2.1 流程部署
部署流程定义相关代码:
/**
* 部署流程
*/
@Test
void deployProcess() {
Deployment deploy = processEngine.getRepositoryService().createDeployment()
.addClasspathResource("processes/请假审批流程.bpmn20.xml")
.name("请假审批流程")
.deploy();
System.out.println("流程部署的ID:" + deploy.getId());
}
act_ge_bytearray资源表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | |
| REV_ | 版本号 | 如果你有多个部署或更新,REV_字段会随着更新而递增。 |
| NAME_ | 名称 | 部署的文件名称,如:请假审批流程.bpmn20.xml |
| DEPLOYMENT_ID_ | 部署ID | 这是一个外键,引用了 act_re_deployment 表中的 ID_ 字段 |
| BYTES_ | 字节(二进制数据) | 存储实际字节内容的字段,包含了实际的二进制数据,比如流程模型文件(BPMN文件)的字节内容。 |
| GENERATED_ | 是否系统生成 | 0为用户上传, 1为系统自动生成, 比如系统会 自动根据xml生 成png |
act_re_deployment部署ID表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | |
| NAME_ | 名称 | 存储部署的名称。通常是流程定义、模型或应用程序的名称,用于描述该部署的内容。 |
| CATEGORY_ | 分类 | 部署的分类,可以用于区分不同类型的部署。这个字段可以为空,具体根据应用场景来设置。 |
| TENANT_ID_ | 租户ID | |
| DEPLOY_TIME_ | 部署时间 | |
| DERIVED_FROM_ | 来源于 | 如果当前部署是从另一个部署派生而来的,这个字段会记录源部署的ID。通常用于版本控制,表示当前部署是从哪个原始部署派生出来的。 |
| DERIVED_FROM_ROOT_ | 来源于 | 如果当前部署是从一个根部署派生而来的,这个字段会记录根部署的ID。这个字段帮助追踪部署的原始来源。 |
| ENGINE_VERSION_ | 流程引擎的版本 | 存储与此部署相关的 Flowable 引擎版本号。可以用于追踪特定版本的引擎和其部署的流程模型之间的关系。 |
| PARENT_DEPLOYMENT_ID_ | 父部署ID | 如果当前部署是一个子部署(例如,某些情况下会有子流程的部署),这个字段会记录父部署的ID。 |
act_re_procdef流程实例表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 流程定义ID |
| REV_ | 版本号 | |
| CATEGORY_ | 分类 | 流程定义的Namespace就是类别 |
| NAME_ | 名称 | 流程名称 |
| KEY_ | 标识 | 流程定义Key |
| VERSION_ | 版本 | |
| DEPLOYMENT_ID_ | 部署ID | |
| RESOURCE_NAME_ | 资源名称 | 流程bpmn文件名称 |
| DGRM_RESOURCE_NAME_ | 图片资源名称 | |
| DESCRIPTION_ | 描述 | |
| HAS_START_FORM_KEY_ | 拥有开始表单标识 | start节点是否存在formKey 0否 1是 |
| HAS_GRAPHICAL_NOTATION_ | 拥有图形信息 | |
| SUSPENSION_STATE_ | 挂起状态 | 暂停状态 1激活 2挂起 |
| TENANT_ID_ | 租户ID | |
| DERIVED_FROM_ | 来源于 | 如果当前流程定义是从另一个流程定义派生出来的,这个字段会记录源流程定义的 ID。用于版本控制和派生关系的追踪。 |
| DERIVED_FROM_ROOT_ | 来源于 | 如果当前流程定义是从根流程定义派生出来的,这个字段会记录根流程定义的 ID,帮助追踪流程定义的根源。 |
4.2.2 挂起与激活
部署的流程默认的状态为激活,如果我们暂时不想使用该定义的流程,那么可以挂起该流程。但是已经启动的流程不受影响,还可继续完成流程。
当流程定义为挂起状态,该流程定义将不允许启动新的流程实例。
/**
* 流程挂起与激活
*/
@Test
void SuspendOrActivity() {
String processDefinitionId = "Leave_approval:2:a52c68f3-b9cd-11ef-8673-286b35ef7f21";
//获取流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
//判断流程定义的挂起状态
boolean suspended = processDefinition.isSuspended();
if (suspended) {
//表示流程被挂起
log.info("当前流程已被挂起,现在激活它....");
//激活流程定义
repositoryService.activateProcessDefinitionById(processDefinitionId);
} else {
//表示流程是激活的
log.info("当前流程已被激活,现在挂起它....");
//挂起流程定义
repositoryService.suspendProcessDefinitionById(processDefinitionId);
}
}


4.2.3 启动流程
这里启动流程就不重复给代码了,之前给过,现在我们来看看启动流程涉及到的表。
当我们启动了一个流程实例后,会在ACT_RU_*对应的表结构中操作,运行时实例涉及的表结构共 10 张:
| 表名 | 描述 |
|---|---|
| ACT_RU_DEADLETTER_JOB | 正在运行的任务表 |
| ACT_RU_EVENT_SUBSCR | 运行时事件 |
| ACT_RU_EXECUTION | 运行时流程执行实例 |
| ACT_RU_HISTORY_JOB | 历史作业表 |
| ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
| ACT_RU_JOB | 运行时作业表 |
| ACT_RU_SUSPENDED_JOB | 暂停作业表 |
| ACT_RU_TASK | 运行时任务表 |
| ACT_RU_TIMER_JOB | 定时作业表 |
| ACT_RU_VARIABLE | 运行时变量表 |
启动一个流程实例时涉及到的表:
| 表名 | 描述 |
|---|---|
| ACT_RU_EXECUTION | 运行时流程执行实例 |
| ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
| ACT_RU_TASK | 运行时任务表 |
| ACT_RU_VARIABLE | 运行时变量表 |
下面是启动流程实例涉及到的各个表字段解读。
act_ru_execution流程实例(执行流)表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | ID | 唯一标识,流程执行实例的ID |
| REV_ | 版本 | 记录版本信息 |
| PROC_INST_ID_ | 流程实例ID | 与流程实例相关联的ID |
| BUSINESS_KEY_ | 业务键 | 业务标识符,用于关联业务流程 |
| PARENT_ID_ | 父级ID | 当前执行的父级执行实例ID |
| PROC_DEF_ID_ | 流程定义ID | 与当前执行实例相关的流程定义ID |
| SUPER_EXEC_ | 超级执行ID | 父级执行ID,通常指向根执行实例 |
| ROOT_PROC_INST_ID_ | 根流程实例ID | 根流程实例的ID,指向最初的流程实例 |
| ACT_ID_ | 活动ID | 当前执行实例所属活动ID |
| IS_ACTIVE_ | 是否活跃 | 标识当前执行实例是否处于活跃状态;1-激活;0-未激活 |
| IS_CONCURRENT_ | 是否并发 | 标识当前执行实例是否为并发执行;1-并发;0-非并发 |
| IS_SCOPE_ | 是否作用域 | 是否为作用域范围内的执行实例;1-是;0否 |
| IS_EVENT_SCOPE_ | 是否事件作用域 | 是否为事件作用域内的执行实例;1-是;0-否 |
| IS_MI_ROOT_ | 是否多实例根 | 是否为多实例的根执行实例;1-是;0-否 |
| SUSPENSION_STATE_ | 暂停状态 | 当前执行实例的暂停状态;1-是;0-否 |
| CACHED_ENT_STATE_ | 缓存实体状态 | 当前执行实例的缓存状态 |
| TENANT_ID_ | 租户ID | 租户标识符,支持多租户功能 |
| NAME_ | 名称 | 当前执行实例的名称 |
| START_ACT_ID_ | 启动活动ID | 当前流程实例启动时的活动ID |
| START_TIME_ | 启动时间 | 当前执行实例的启动时间 |
| START_USER_ID_ | 启动用户ID | 启动当前执行实例的用户ID |
| LOCK_TIME_ | 锁定时间 | 当前执行实例的锁定时间 |
| LOCK_OWNER_ | 锁定拥有者 | 当前执行实例的锁定拥有者 |
| IS_COUNT_ENABLED_ | 是否启用计数 | 是否启用计数功能 |
| EVT_SUBSCR_COUNT_ | 事件订阅计数 | 当前执行实例的事件订阅计数 |
| TASK_COUNT_ | 任务计数 | 当前执行实例的任务计数 |
| JOB_COUNT_ | 作业计数 | 当前执行实例的作业计数 |
| TIMER_JOB_COUNT_ | 定时作业计数 | 当前执行实例的定时作业计数 |
| SUSP_JOB_COUNT_ | 暂停作业计数 | 当前执行实例的暂停作业计数 |
| DEADLETTER_JOB_COUNT_ | 死信作业计数 | 当前执行实例的死信作业计数 |
| EXTERNAL_WORKER_JOB_COUNT_ | 外部工作作业计数 | 当前执行实例的外部工作作业计数 |
| VAR_COUNT_ | 变量计数 | 当前执行实例的变量计数 |
| ID_LINK_COUNT_ | ID链接计数 | 当前执行实例的ID链接计数 |
| CALLBACK_ID_ | 回调ID | 与回调相关的ID |
| CALLBACK_TYPE_ | 回调类型 | 回调类型 |
| REFERENCE_ID_ | 参考ID | 参考的ID |
| REFERENCE_TYPE_ | 参考类型 | 参考的类型 |
| PROPAGATED_STAGE_INST_ID_ | 传播阶段实例ID | 当前实例相关的传播阶段实例ID |
| BUSINESS_STATUS_ | 业务状态 | 当前执行实例的业务状态 |
act_ru_task运行时任务表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 任务ID | 唯一标识,任务的主键 |
| REV_ | 版本 | 记录的版本号,用于乐观锁控制 |
| EXECUTION_ID_ | 执行实例ID | 关联到运行时执行实例表 (act_ru_execution) 的ID |
| PROC_INST_ID_ | 流程实例ID | 关联到运行时流程实例表 (act_ru_execution) 的ID |
| PROC_DEF_ID_ | 流程定义ID | 关联到流程定义表 (act_re_procdef) 的ID |
| TASK_DEF_ID_ | 任务定义ID | 当前任务的定义ID |
| SCOPE_ID_ | 作用域ID | 任务所属的作用域ID |
| SUB_SCOPE_ID_ | 子作用域ID | 任务所属的子作用域ID |
| SCOPE_TYPE_ | 作用域类型 | 作用域的类型(如流程、表单等) |
| SCOPE_DEFINITION_ID_ | 作用域定义ID | 任务作用域的定义ID |
| PROPAGATED_STAGE_INST_ID_ | 传播阶段实例ID | 关联的传播阶段实例ID |
| NAME_ | 任务名称 | 当前任务的名称 |
| PARENT_TASK_ID_ | 父任务ID | 父级任务的ID |
| DESCRIPTION_ | 描述 | 当前任务的描述信息 |
| TASK_DEF_KEY_ | 任务定义键 | BPMN文件中任务节点的唯一标识 |
| OWNER_ | 任务所有者 | 当前任务的所有者 |
| ASSIGNEE_ | 任务负责人 | 当前任务的指派人 |
| DELEGATION_ | 委托状态 | 任务的委托状态(如 PENDING 或 RESOLVED) |
| PRIORITY_ | 优先级 | 当前任务的优先级(数字越大优先级越高) |
| CREATE_TIME_ | 创建时间 | 任务的创建时间 |
| DUE_DATE_ | 截止日期 | 任务的到期时间 |
| CATEGORY_ | 类别 | 任务的分类信息 |
| SUSPENSION_STATE_ | 暂停状态 | 任务是否被暂停(1=激活,2=暂停) |
| TENANT_ID_ | 租户ID | 支持多租户功能的租户标识 |
| FORM_KEY_ | 表单键 | 关联的表单键,用于任务表单 |
| CLAIM_TIME_ | 认领时间 | 任务被认领的时间 |
| IS_COUNT_ENABLED_ | 是否启用计数 | 是否启用任务的计数功能 |
| VAR_COUNT_ | 变量计数 | 任务关联的变量数量 |
| ID_LINK_COUNT_ | ID链接计数 | 任务关联的ID链接数量 |
| SUB_TASK_COUNT_ | 子任务计数 | 当前任务的子任务数量 |
act_ru_variable运行时变量表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 变量ID | 唯一标识,变量表的主键 |
| REV_ | 版本 | 记录的版本号,用于乐观锁控制 |
| TYPE_ | 变量类型 | 存储变量的类型,例如字符串、数字、布尔值等 |
| NAME_ | 变量名称 | 变量的名称 |
| EXECUTION_ID_ | 执行实例ID | 关联到运行时执行实例表 (act_ru_execution) 的ID,表示变量属于哪个执行实例 |
| PROC_INST_ID_ | 流程实例ID | 关联到运行时流程实例表 (act_ru_execution) 的ID,表示变量属于哪个流程实例 |
| TASK_ID_ | 任务ID | 关联到运行时任务表 (act_ru_task) 的ID,表示变量所属任务 |
| SCOPE_ID_ | 作用域ID | 变量所属的作用域ID |
| SUB_SCOPE_ID_ | 子作用域ID | 变量所属的子作用域ID |
| SCOPE_TYPE_ | 作用域类型 | 变量所属的作用域类型(如流程、任务等) |
| BYTEARRAY_ID_ | 字节数组ID | 关联到字节数组表 (act_ge_bytearray) 的ID,用于存储大数据类型(如文件) |
| DOUBLE_ | 双精度数值 | 存储变量的数值类型(浮点型) |
| LONG_ | 长整型数值 | 存储变量的数值类型(整型) |
| TEXT_ | 文本内容 | 变量的文本值 |
| TEXT2_ | 辅助文本内容 | 辅助的文本值(通常用于存储附加信息) |
act_ru_identitylink运行时用户关系信息表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 唯一标识 | 主键,用于唯一标识每条用户或组的关联信息 |
| REV_ | 版本号 | 记录版本号,用于乐观锁控制 |
| GROUP_ID_ | 用户组ID | 标识关联的用户组ID,用于任务分配或权限定义 |
| TYPE_ | 类型 | 标识链接类型,例如 participant (参与者),owner (所有者),assignee (受理人)等 |
| USER_ID_ | 用户ID | 标识关联的用户ID,用于任务分配或流程实例的用户参与者 |
| TASK_ID_ | 任务ID | 标识关联的任务ID,链接任务和用户或组 |
| PROC_INST_ID_ | 流程实例ID | 标识关联的流程实例ID,链接流程实例和用户或组 |
| PROC_DEF_ID_ | 流程定义ID | 标识关联的流程定义ID,指向流程的元数据 |
| SCOPE_ID_ | 作用域ID | 链接到具体作用域的ID,用于更精细化的权限管理 |
| SUB_SCOPE_ID_ | 子作用域ID | 链接到子作用域的ID,用于多层级作用域管理 |
| SCOPE_TYPE_ | 作用域类型 | 描述作用域类型,例如流程、任务等 |
| SCOPE_DEFINITION_ID_ | 作用域定义ID | 用于指向作用域的定义,进一步说明作用域结构 |
4.3.4 流程任务处理
这里流程处理就不重复给代码了,之前给过,现在我们来看看流程任务处理涉及到的表。
在正常处理流程中涉及到的表结构:
| 表名 | 描述 |
|---|---|
| ACT_RU_EXECUTION | 运行时流程执行实例 |
| ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
| ACT_RU_TASK | 运行时任务表 |
| ACT_RU_VARIABLE | 运行时变量表 |
在处理流程任务的时候,ACT_RU_TASK运行时任务表,会新生成一条记录。如果你的流程处理中携带了变量,ACT_RU_VARIABLE运行时变量表也会生成一条记录。
4.3.5 完成流程
当我们把最后一个流程任务处理完成,该流程就结束了。流程结束后,我们会发现以下四张表中对应的数据都没有了,也就是这个流程已经不是运行中的流程了:
| 表名 | 描述 |
|---|---|
| ACT_RU_EXECUTION | 运行时流程执行实例 |
| ACT_RU_IDENTITYLINK | 运行时用户关系信息 |
| ACT_RU_TASK | 运行时任务表 |
| ACT_RU_VARIABLE | 运行时变量表 |
然后在对应的历史表中我们可以看到相关的信息:
| 表名 | 描述 |
|---|---|
| ACT_HI_ACTINST | 历史的活动实例 |
| ACT_HI_ATTACHMENT | 历史的流程附件 |
| ACT_HI_COMMENT | 历史的说明性信息 |
| ACT_HI_DETAIL | 历史的流程运行中的细节信息 |
| ACT_HI_IDENTITYLINK | 历史的流程运行过程中用户关系 |
| ACT_HI_PROCINST | 历史的流程实例 |
| ACT_HI_TASKINST | 历史的任务实例 |
| ACT_HI_VARINST | 历史的流程运行中的变量信息 |
act_hi_actinst历史的流程活动实例表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 唯一标识历史活动实例 |
| REV_ | 版本号 | 乐观锁版本号,默认为 1 |
| PROC_DEF_ID_ | 流程定义 ID | 流程定义的唯一标识,关联 act_re_procdef 表 |
| PROC_INST_ID_ | 流程实例 ID | 流程实例的唯一标识,关联 act_ru_execution 表 |
| EXECUTION_ID_ | 执行 ID | 运行时执行记录的标识 |
| ACT_ID_ | 活动节点 ID | BPMN 模型中活动元素的 ID |
| TASK_ID_ | 任务 ID | 关联任务表 act_ru_task,仅用户任务节点有值 |
| CALL_PROC_INST_ID_ | 调用流程实例 ID | 调用子流程实例的唯一标识 |
| ACT_NAME_ | 活动名称 | 流程图中活动元素的 name 属性 |
| ACT_TYPE_ | 活动类型 | 活动的类型,如 userTask(用户任务)、startEvent(启动事件)等 |
| ASSIGNEE_ | 受理人 | 仅用户任务活动有值 |
| START_TIME_ | 开始时间 | 活动实例的开始时间 |
| END_TIME_ | 结束时间 | 活动实例的结束时间 |
| TRANSACTION_ORDER_ | 事务排序 | 记录活动事务的执行顺序 |
| DURATION_ | 持续时间 | 活动实例的持续时间,单位为毫秒 |
| DELETE_REASON_ | 删除原因 | 活动被删除的原因,例如流程实例被终止 |
| TENANT_ID_ | 租户 ID | 多租户场景下用于标识所属租户 |
act_hi_identitylink历史的流程运行过程中用户关系表:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 唯一标识历史身份链接 |
| GROUP_ID_ | 组 ID | 关联组的标识 |
| TYPE_ | 链接类型 | 身份链接的类型,例如 candidate(候选人)或 participant(参与者) |
| USER_ID_ | 用户 ID | 关联用户的标识 |
| TASK_ID_ | 任务 ID | 关联任务表的主键,表明该身份链接与哪个任务关联 |
| CREATE_TIME_ | 创建时间 | 身份链接创建的时间 |
| PROC_INST_ID_ | 流程实例 ID | 关联流程实例表 act_ru_execution |
| SCOPE_ID_ | 作用域 ID | 记录该身份链接所属的作用域 |
| SUB_SCOPE_ID_ | 子作用域 ID | 记录子作用域 ID |
| SCOPE_TYPE_ | 作用域类型 | 例如 bpmn、cmmn 等 |
| SCOPE_DEFINITION_ID_ | 作用域定义 ID | 对应的作用域定义的标识 |
act_hi_procinst历史的流程运行中的细节信息:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 唯一标识历史流程实例 |
| REV_ | 版本号 | 用于乐观锁控制 |
| PROC_INST_ID_ | 流程实例 ID | 唯一标识一个流程实例,支持快速查询,具有唯一约束 |
| BUSINESS_KEY_ | 业务键 | 关联业务系统中的唯一标识,用于关联业务数据 |
| PROC_DEF_ID_ | 流程定义 ID | 关联流程定义表,标识当前流程实例基于的流程定义 |
| START_TIME_ | 启动时间 | 流程实例启动的时间 |
| END_TIME_ | 结束时间 | 流程实例结束的时间(如果已完成) |
| DURATION_ | 持续时间 | 流程实例运行的总时长,单位为毫秒 |
| START_USER_ID | 启动用户 | 启动流程实例的用户 ID |
| START_ACT_ID_ | 启动活动 ID | 启动流程实例时的活动节点 ID |
| END_ACT_ID_ | 结束活动 ID | 流程实例结束时的活动节点 ID |
| SUPER_PROCESS_INSTANCE_ID_ | 父流程实例 ID | 如果是子流程,则记录父流程实例的 ID |
| DELETE_REASON_ | 删除原因 | 记录流程实例被删除的原因 |
| TENANT_ID_ | 租户 ID | 多租户场景下的租户标识 |
| NAME_ | 流程实例名称 | 流程实例的显示名称 |
| CALLBACK_ID_ | 回调 ID | 与外部系统交互时记录的回调标识 |
| CALLBACK_TYPE_ | 回调类型 | 回调的类型,例如 HTTP 回调或消息队列 |
| REFERENCE_ID_ | 参考 ID | 外部系统中的参考标识 |
| REFERENCE_TYPE_ | 参考类型 | 参考标识的类型 |
| PROPAGATED_STAGE_INST_ID_ | 传播阶段实例 ID | 记录分阶段流程实例的传播信息 |
| BUSINESS_STATUS_ | 业务状态 | 流程实例的当前业务状态(例如处理中、已完成) |
act_hi_taskinst历史的任务实例:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 唯一标识历史任务实例 |
| REV_ | 版本号 | 用于乐观锁控制 |
| PROC_DEF_ID_ | 流程定义 ID | 关联流程定义表,标识任务所属的流程定义 |
| TASK_DEF_ID_ | 任务定义 ID | 唯一标识任务定义 |
| TASK_DEF_KEY_ | 任务定义 Key | 流程模型中的任务标识符 |
| PROC_INST_ID_ | 流程实例 ID | 关联任务所属的流程实例 |
| EXECUTION_ID_ | 执行实例 ID | 任务关联的执行实例 ID |
| SCOPE_ID_ | 作用域 ID | 表示任务关联的作用域 |
| SUB_SCOPE_ID_ | 子作用域 ID | 表示任务关联的子作用域 |
| SCOPE_TYPE_ | 作用域类型 | 任务所在的作用域类型(例如流程、子流程等) |
| SCOPE_DEFINITION_ID_ | 作用域定义 ID | 作用域的定义标识 |
| PROPAGATED_STAGE_INST_ID_ | 传播阶段实例 ID | 记录任务在分阶段场景下的传播信息 |
| NAME_ | 任务名称 | 任务实例的显示名称 |
| PARENT_TASK_ID_ | 父任务 ID | 如果是子任务,记录对应父任务 ID |
| DESCRIPTION_ | 任务描述 | 任务的描述信息 |
| OWNER_ | 拥有者 | 任务的拥有者 ID |
| ASSIGNEE_ | 任务执行人 | 当前分配的任务处理人 ID |
| START_TIME_ | 启动时间 | 任务开始时间 |
| CLAIM_TIME_ | 认领时间 | 任务被认领的时间 |
| END_TIME_ | 结束时间 | 任务完成或结束的时间 |
| DURATION_ | 持续时间 | 任务从开始到结束的持续时长,单位为毫秒 |
| DELETE_REASON_ | 删除原因 | 记录任务被删除的原因 |
| PRIORITY_ | 优先级 | 任务的优先级(数值越大优先级越高) |
| DUE_DATE_ | 到期时间 | 任务需要完成的截止时间 |
| FORM_KEY_ | 表单 Key | 关联的任务表单标识符 |
| CATEGORY_ | 任务分类 | 任务的分类信息 |
| TENANT_ID_ | 租户 ID | 多租户场景下的租户标识 |
| LAST_UPDATED_TIME_ | 最后更新时间 | 记录任务实例的最后修改时间 |
act_hi_varinst历史的流程运行中的变量信息:
| 字段 | 名称 | 备注 |
|---|---|---|
| ID_ | 主键 | 唯一标识历史变量实例 |
| REV_ | 版本号 | 用于乐观锁控制 |
| PROC_INST_ID_ | 流程实例 ID | 关联的流程实例标识 |
| EXECUTION_ID_ | 执行实例 ID | 关联的执行实例标识 |
| TASK_ID_ | 任务 ID | 如果变量与任务关联,则记录任务实例 ID |
| NAME_ | 变量名称 | 变量的唯一名称 |
| VAR_TYPE_ | 变量类型 | 记录变量的类型(如字符串、数值、布尔值等) |
| SCOPE_ID_ | 作用域 ID | 变量所在的作用域标识 |
| SUB_SCOPE_ID_ | 子作用域 ID | 变量所在的子作用域标识 |
| SCOPE_TYPE_ | 作用域类型 | 变量所在的作用域类型(如流程、任务等) |
| BYTEARRAY_ID_ | 二进制数组 ID | 如果变量是大对象(BLOB),关联其存储的字节数组 ID |
| DOUBLE_ | 双精度浮点值 | 存储变量的双精度浮点数值 |
| LONG_ | 长整型值 | 存储变量的长整型数值 |
| TEXT_ | 文本值 | 存储变量的字符串值 |
| TEXT2_ | 额外文本值 | 用于存储变量的附加字符串值 |
| CREATE_TIME_ | 创建时间 | 记录变量实例的创建时间 |
| LAST_UPDATED_TIME_ | 最后更新时间 | 记录变量实例的最后修改时间 |
5. 任务分配
在Flowable中,任务分配和流程变量是两个核心概念,主要用于控制流程的执行和数据流转。
任务分配的作用是确保用户任务能够被正确的流程或用户组处理,它可以通过指定接纳人、候选用户/用户组、动态分配等方式实现。机制能够明确任务的处理人,提高工作效率,并通过动态分配或规则分配适应复杂的业务需求,同时实现权限控制,确保任务。
流程变量用于在流程中存储和提交数据的容器,其贯穿整个生命周期的流程,支持周期条件判断、动态分配、数据共享等功能。流程变量,可以实现数据的动态提交和流程逻辑的灵活调整,使流程变得更加一致和标注。例如,可以使用变量驱动网关路径选择、记录箭头数据,或在任务之间共享关键信息。因此,任务分配和流程变量共同构成了Flowable流程控制和业务数据管理的基础。
在做流程定义的时候我们需要给相关的用户节点指派对应的处理人。在Flowable中提供了三种分配的方式:
- 固定分配
- 表达式分配
- 监听器分配
5.1 固定分配
固定分配就是我们前面介绍的,在绘制流程图或者直接在流程文件中通过Assignee来指定的方式



[!tip]
这里的固定值分配可以分配你自己业务系统里面的用户,不一定非要分配Flowable系统的用户。但是如果你是身份存储方式的分配的话,那么就只能选择Flowable系统中的用户了。
5.2 表达式分配
表达式分配是指在工作流中,任务的分配不再依赖静态的用户或组信息,而是通过动态计算的方式来确定。这个计算通常是通过表达式来完成,Flowable 支持在任务的分配中使用EL表达式(Expression Language)。这样,任务的分配对象(如用户或组)可以在运行时动态计算出来。
Flowable 使用的是一种基于Spring Expression Language (SpEL) 的表达式机制,通常可以通过以下几种方式来计算任务的分配对象:
基于流程变量:流程变量是Flowable流程中的一种重要机制,它们存储了流程中的状态和信息。例如,你可以在流程中动态地设置一个变量
assignee,然后在任务中通过表达式${assignee}来分配任务。<userTask id="task1" name="Task 1" flowable:assignee="${assignee}"></userTask>在执行时,如果流程变量
assignee被设置为某个用户,那么任务就会自动分配给该用户。基于脚本或条件:你还可以根据流程中的其他变量来决定任务的分配。例如,假设有一个条件,若某个变量
priority的值为 “high”,则将任务分配给 “admin” 用户,否则分配给其他用户。<userTask id="task2" name="Task 2" flowable:assignee="${priority == 'high' ? 'admin' : 'user'}"></userTask>在这个例子中,表达式根据
priority变量的值决定分配给”admin”还是”user”。基于Spring Bean值的表达式:一般我们获取bean中某个属性的值都是
bean.property,这里对于表达式分配的形式也是一样的。例如我这里有个User的bean,我要用用户名称来分配给具体的用户。<userTask id="task3" name="Task 3" flowable:assignee="${User.name}"></userTask>使用复杂的 SpEL 表达式:Flowable支持更复杂的SpEL表达式,可以进行运算、条件判断等。例如,如果你需要基于某些复杂的逻辑来分配任务。
<userTask id="task4" name="Task 4" flowable:assignee="${userService.getAssigneeForTask(taskId)}"> </userTask>在这个例子中,
${userService.getAssigneeForTask(taskId)}是一个调用 Java 类方法的表达式,通过userService服务来根据taskId动态确定任务的受理人。注意,如果调用的Java类方法中没有形参,那么你要把括号留出来,防止与Spring Bean值的表达式的形式混淆
<userTask id="task4" name="Task 4" flowable:assignee="${userService.getAssigneeForTask()}"> </userTask>
现在我流程图以及任务的指派如下:

启动流程时需要指定用户任务的处理人:
/**
* 发起流程
*/
@Test
void startProcess() {
//如果任务节点的分配人是动态分配的
//那么发起流程之前,一般要设置下一个节点的处理人
HashMap<String, Object> map = new HashMap<>();
map.put("testUser","zhangsan");
//发起流程时把变量传递进去
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:2781489b-bdbc-11ef-b32f-00ff89a0c505",map);
log.info("当前流程的实例ID为:【{}】",processInstance.getId());
}
查看对应的表信息数据:


5.3 监听器分配
在Flowable中,监听器分配(Listener Assignment)是另一种任务分配方式,它基于任务监听器(Task Listener)来动态地分配任务的受理人。这种方式相比于表达式分配具有更多的灵活性,因为它可以在流程执行过程中动态处理更复杂的逻辑和业务规则。
任务监听器分配通过定义一个任务监听器来处理任务的分配逻辑。任务监听器会在任务生命周期的不同阶段(如创建、完成、分配等)执行相应的代码。你可以通过任务监听器来动态分配任务的受理人、候选人,甚至根据某些条件进行不同的处理。
我们需要有一个自己的任务监听器类:
@Component
@Slf4j
public class MyFlowableTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
log.info("自定义任务监听器启动...");
if (EVENTNAME_CREATE.equals(delegateTask.getEventName())){
delegateTask.setAssignee("zhangsan");
}
}
}
TaskListener是Flowable提供的接口,用于监听任务的生命周期事件。任务监听器可以响应不同的事件,如任务创建、任务分配、任务完成等。notify是TaskListener接口的唯一方法,在任务的生命周期中,当特定事件(如任务创建、分配、完成等)触发时,Flowable会调用该方法。
DelegateTask是Flowable提供的一个接口,表示当前的任务实例。通过它,你可以访问和操作任务的各种属性(如任务的受理人、任务变量等)。delegateTask.getEventName() 返回触发事件的名称。
这里TaskListener继承了BaseTaskListener,其中BaseTaskListener有一些常量:

BaseTaskListener支持多种生命周期事件,你可以根据需求对不同事件进行处理。常见的事件如下:
EVENTNAME_CREATE:任务创建时触发。通常用于初始化任务,例如设置任务的受理人。EVENTNAME_ASSIGNMENT:任务分配时触发。用于动态修改任务的受理人或候选人。EVENTNAME_COMPLETE:任务完成时触发。用于任务完成后的清理操作、日志记录等。EVENTNAME_DELETE:任务删除时触发。用于任务被删除时的处理。
当任务监听器准备好了之后,来到FlowableUI中进行流程修改,指定任务监听器:


配置任务监听器:

配置好了之后,现在你重新部署改流程并且启动,这次启动的时候可以把流程变量给去掉,直接启动:
/**
* 发起流程
*/
@Test
void startProcess() {
//发起流程时把变量传递进去
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:99326e5d-bdd4-11ef-b95d-00ff89a0c505");
log.info("当前流程的实例ID为:【{}】",processInstance.getId());
}
执行的时候,你会发现你的自定义任务监听器已经执行了,并且去运行时任务表信息中,查询任务负责人是否是为你自定义任务监听器中指定的任务负责人:

6. 流程变量
在Flowable中,流程变量(Process Variables)是与流程实例相关的数据,用于在流程的各个任务、事件和子流程之间传递和存储信息。它们是流程执行的重要组成部分,支持跨任务、跨流程的状态传递和信息共享。这些变量是键值对,键是变量名,值是变量的实际数据。变量的值可以是不同类型的数据,包括字符串、数字、布尔值、日期,甚至复杂对象(如 Java 对象、JSON 等)。
参考文档:https://tkjohn.github.io/flowable-userguide/#apiVariables
6.1 运行时变量
流程实例运行时的变量,存入act_ru_variable表中。在流程实例运行结束时,此实例的变量在表中删除。在流程实例创建及启动时,可设置流程变量。所有的startProcessInstanceXXX方法都有一个可选参数用于设置变量。例如,RuntimeService中:

当然也可以在流程执行中加入变量。例如,(RuntimeService):

读取变量方法:

其实不管是获取流程实例变量还是设置流程实例变量,你都能够在对应的API找到xxxLocal的方法,这是因为运行时变量它其实还分为全局变量和局部变量。
6.1.1 全局变量
全局变量是指在整个流程实例的生命周期内都可以访问的变量,通常这些变量用于跨多个任务或子流程共享数据。全局变量在流程实例中是“全局”可见的,无论流程流转到哪个节点,它们的值都可以在任何任务、事件或子流程中访问。
全局变量的生命周期与流程实例一致。只要流程实例还在执行,全局变量就一直有效。一旦流程实例完成,所有的全局变量都会被清除。变量存储在流程实例级别的数据库表ACT_RU_VARIABLE中,并且可以在整个流程中访问。
全局变量通常通过runtimeService或processEngine来进行设置和访问。这些变量可以在启动流程时设置,也可以在流程执行过程中动态设置:
流程启动时设置全局变量
Map<String, Object> variables = new HashMap<>(); variables.put("leaveDays", 5); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Leave_approval", variables);获取全局变量
Object leaveDays = runtimeService.getVariable(processInstanceId, "leaveDays");修改全局变量
runtimeService.setVariable(processInstanceId, "leaveDays", 10);
6.1.2 局部变量
局部变量是指在某个特定任务或子流程范围内有效的变量。这些变量的作用范围仅限于它们所依附的任务、事件或子流程中。局部变量在任务完成后通常会失效,因此它们的生命周期通常较短。
局部变量的生命周期与任务实例或子流程实例一致。也就是说,局部变量仅在任务或子流程的执行期间有效。一旦任务或子流程完成,局部变量即被销毁。局部变量存储在任务级别或子流程级别的数据库表ACT_RU_VARIABLE中,但其范围仅限于该任务或子流程。
局部变量也可以通过runtimeService来访问:
设置或修改局部变量
runtimeService.setVariableLocal(executionId,variableName,value);获取局部变量
runtimeService.getVariableLocal(executionId,variableName);
当然,如果你是在任务监听器中操作,那么就需要用delegateTask或delegateExecution来设置和访问。它们只对当前的任务或执行实例有效:
设置局部变量
delegateTask.setVariableLocal("approvalStatus", "approved");获取局部变量
String approvalStatus = (String) delegateTask.getVariableLocal("approvalStatus");修改局部变量
delegateTask.setVariableLocal("approvalStatus", "rejected");
6.2 历史变量
历史变量,存入act_hi_varinst表中。在流程启动时,流程变量会同时存入历史变量表中;在流程结束时,历史表中的变量仍然存在。可理解为“永久代”的流程变量。
例如获取已完成的、id为’XXX’的流程实例中,所有的HistoricVariableInstances(历史变量实例),并以变量名排序:
historyService.createHistoricVariableInstanceQuery()
.processInstanceId("XXX")
.orderByVariableName.desc()
.list();
7. 身份服务
Flowable 的身份服务用于管理与身份相关的各种信息,包括用户、组和角色。它提供了一个 API 来执行用户认证、授权以及查询用户和组的信息。
- 用户(User):表示一个系统用户,通常是任务执行者或者审批人。
- 组(Group):表示一个用户组,通常是一个角色或者部门,用于组织用户。
- 角色(Role):在 Flowable 中通常通过组来进行管理,角色用来定义一组具有相同权限的用户。
Flowable 的身份服务是通过IdentityService接口来提供操作的,主要方法包括:
创建、查询、更新用户。
创建、查询、更新组。
为用户赋予角色(即将用户分配到组)。
查询用户和组的关系。
示例代码:
IdentityService identityService = processEngine.getIdentityService();
// 创建用户
User user = identityService.newUser("user1");
user.setFirstName("John");
user.setLastName("Doe");
identityService.saveUser(user);
// 创建组
Group group = identityService.newGroup("admin");
group.setName("Administrator Group");
identityService.saveGroup(group);
// 将用户添加到组中
identityService.createMembership("user1", "admin");
在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人或者候选人组,可以从候选人中选择参与者来完成任务。
7.1 候选人
候选人是指那些可以被分配任务的用户。Flowable 允许在任务创建时指定一组候选人,这些候选人可以是单个用户或者多个用户。
在任务被创建时,候选人是作为“候选”任务执行者的角色出现的。它们并不是任务的实际执行者,而是可以选择接管该任务的用户。
通常,候选人是在任务的创建过程中通过 API 被指定的,或者在任务待办列表中被显示给用户,用户可以选择接管任务。
下面给出一个示例流程。
7.1.1 定义流程图
这里的流程图我还是用之前请假那个流程,只是任务的分配我给多个候选人,如图:

对应于文件中的体现:

7.1.2 部署以及启动流程
这里部署就不用再重复说了,这里给出启动流程的相关代码:
/**
* 发起流程
*/
@Test
void startProcess() {
//发起流程时把变量传递进去
HashMap<String, Object> candidateMap = new HashMap<>();
candidateMap.put("candidate1", "张三");
candidateMap.put("candidate2", "李四");
candidateMap.put("candidate3", "王五");
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505",candidateMap);
log.info("当前流程的实例ID为:【{}】", processInstance.getId());
}
流程启动成功之后,我们来看看目前运行时任务表中,人事审批这个任务的负责人是谁:


当前人事审批任务未分配给任意用户,这是因为需要候选人自己去领取对应的任务,只有候选人领取了任务,那么这个任务的负责人才会被指定。
7.1.3 任务的查询与领取
要想人事审批有具体的负责人,则需要各个候选人自己去领取该任务才可。比如这里我想要李四来领取这个任务,那么我就会先查询一下李四这个人当前的任务列表,看看他有没有资格领取该任务(仅有候选人才可领取):
@Resource
private TaskService taskService;
/**
* 根据登录的用户查询对应的可以领取的任务
*
*/
@Test
public void queryTaskCandidate(){
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionId("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505")
.taskCandidateUser("李四")
.list();
for (Task task : taskList) {
log.info("任务ID【{}】;任务名称:【{}】", task.getId(), task.getName());
}
}

从执行结果可知,李四当前有一个任务,现在,如果他想要处理这个任务,就需要去领取来之后,才能够处理,所以,我们需要领取任务并且处理:
/**
* 领取任务
* 一个候选人领取了这个任务之后其他的用户就没有办法领取这个任务了
* 所以如果一个用户领取了任务之后又不想处理了,那么可以退还
*/
@Test
public void claimTaskCandidate(){
Task task = taskService.createTaskQuery()
.processDefinitionId("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505")
.taskCandidateUser("李四")
.singleResult();
if(task != null){
// 领取对应的任务
taskService.claim(task.getId(),"李四");
System.out.println("任务领取成功");
}
}
[!note]
核心API:
taskService.claim(taskId, userId);
任务领取成功之后,咋们去数据库再看看:

当一个候选人领取了这个任务之后其他的用户就没有办法领取这个任务了,如果你不想处理你领取的任务,那么你还可以归还让其他候选人来领取之后处理。
7.1.4 任务的归还
如果你领取了不想处理,那么你首先需要归还任务,其他候选人才可以来领取处理:
/**
* 退还任务
* 一个候选人领取了这个任务之后其他的用户就没有办法领取这个任务了
* 所以如果一个用户领取了任务之后又不想处理了,那么可以退还
*/
@Test
public void unClaimTaskCandidate(){
Task task = taskService.createTaskQuery()
.processDefinitionId("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505")
.taskAssignee("李四")
.singleResult();
if(task != null){
// 归还对应的任务
taskService.unclaim(task.getId());
System.out.println("归还成功");
}
}
[!note]
核心API:
taskService.unclaim(taskId);
任务归还之后,我们再去看看运行时任务信息表:

7.1.5 任务的交接
当然,如果候选人领取了任务,如果不想处理了也可以不用归还,可以选择交接给其他候选人:
/**
* 任务的交接
* 如果我获取了任务,但是不想执行,那么我可以把这个任务交接给其他的用户
*/
@Test
public void taskCandidate(){
Task task = taskService.createTaskQuery()
.processDefinitionId("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505")
.taskAssignee("李四")
.singleResult();
if(task != null){
// 任务的交接
taskService.setAssignee(task.getId(),"王五");
System.out.println("任务交接给了王五");
}
}

可见指定成功,其实就是这里的交接就是直接指定负责人。
[!note]
核心API:
taskService.setAssignee(taskId,userId);
7.2 候选人组
候选人组与候选人类似,不过候选人组指的是一个组(如某个部门或角色)内的所有用户都可以接管该任务。任务的候选人组通常是一个授权的角色或者权限范围。
当任务被分配给一个候选人组时,所有属于该组的用户都能查看并领取该任务。候选人组通常用于批量任务管理,避免每个用户单独被指定为候选人。
7.2.1 管理用户
我们需要先单独维护用户信息。后台对应的表结构是ACT_ID_USER。
[!note]
大多数情况下,我们一般是用自己的业务系统的中的用户表。但是也可以将业务系统中的用户表同步进
ACT_ID_USER表中方便管理。
新增用户:
@Test
public void addUser(){
User user = identityService.newUser("zhangsan");
user.setFirstName("张");
user.setLastName("三");
user.setEmail("zhangsan@qq.com");
identityService.saveUser(user);
}
[!caution]
注意,这里的新建的用户存放的数据库可不是FlowableUI这个程序的数据库,flowableUI是单独的一套程序,和当前这个没有关系。

7.2.2 管理用户组
维护对应的Group信息,后台对应的表结构是ACT_ID_GROUP。
新增用户组:
@Test
public void addGroup(){
Group group = identityService.newGroup("group1");
group.setName("开发部");
group.setType("type1");
identityService.saveGroup(group);
}

7.2.3 用户分配组
用户和组是一个多对多的关联关系,我们需要做相关的分配,后台对应的表结构是ACT_ID_MEMBERSHIP。
@Test
public void userGroup(){
// 根据组的id找到对应的Group对象
Group group = identityService.createGroupQuery()
.groupId("group1")
.singleResult();
//获取目前系统中所有的用户
List<User> list = identityService.createUserQuery().list();
for (User user : list) {
// 将用户分配给对应的组
identityService.createMembership(user.getId(),group.getId());
}
}

7.2.4 候选人组案例
还是使用之前的请假流程,只不过我我们这里指定任务节点的负责人为候选人组了。

为了体现出多个用户,我们这里再创建几个用户并且绑定关系:

现在我们发起流程并且传递参数:
/**
* 发起流程
*/
@Test
void startProcess() {
//发起流程时把变量传递进去
Group group = identityService.createGroupQuery()
.groupId("group1")
.singleResult();
HashMap<String, Object> candidateGroupMap = new HashMap<>();
candidateGroupMap.put("group1", group.getId());
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:97accaa8-c107-11ef-ae94-00ff89a0c505",candidateGroupMap);
log.info("当前流程的实例ID为:【{}】", processInstance.getId());
}

现在我们再来看看运行时用户关系表:

之后我们领取任务:
/**
* 根据登录的用户查询可领取的任务并且领取
*
*/
@Test
public void queryTaskCandidateGroupAndClaim(){
// 当前用户所在的组
Group group = identityService.createGroupQuery()
.groupMember("zhangsan")
.singleResult();
Task task = taskService.createTaskQuery()
.processDefinitionId("Leave_approval:4:57bbac16-c1d0-11ef-bd4a-00ff89a0c505")
.taskCandidateGroup(group.getId())
.singleResult();
if(task != null) {
// 任务拾取
taskService.claim(task.getId(),"zhangsan");
System.out.println("任务拾取成功");
}
}


之后完成它即可。
8. 网关服务
在Flowable中,网关(Gateway)是用于控制流程路径分支的元素,它决定了流程的执行方向。Flowable提供了多种类型的网关服务来处理不同的流程控制需求。网关主要用于流程的分支、合并和决策控制,它们是 BPMN 2.0 规范中的重要组成部分。
Flowable 支持的网关类型主要包括:
- 排他网关(Exclusive Gateway, XOR Gateway)
- 并行网关(Parallel Gateway, AND Gateway)
- 包容网关(Inclusive Gateway, OR Gateway)
- 事件网关(Event-based Gateway)
8.1 排他网关
排他网关用于基于条件判断选择一个分支路径。在排他网关中,流程根据某些条件选择一个分支路径执行。每次经过排他网关时,Flowable会评估网关的条件表达式,并选择其中一个路径。如果条件表达式的值为true,那么就通过,如果为false就拒绝。当多条顺序流的条件都计算为true时,会且仅会选择在XML中最先定义的顺序流继续流程。如果没有可选的顺序流,会抛出异常。
- 行为:只有一个分支会被激活。
- 使用场景:通常用于如果条件判断分支的情况(例如基于用户选择、系统状态等)。
例如我改造之前的流程,加入排他网关:

排他网关通过的流转条件为:

可见,流转条件一般也是表达式,当表达式的值为true的时候,就会走到经理审批任务,反之,又回到人事审批阶段。
/**
* 发起流程
*/
@Test
void startProcess() {
HashMap<String, Object> paramsMap = new HashMap<>();
String p1 = "zhangsan";
paramsMap.put("user", p1);
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("Leave_approval:2:42b19f30-c1db-11ef-bdbb-00ff89a0c505",paramsMap);
log.info("当前流程的实例ID为:【{}】", processInstance.getId());
//查询当前任务到哪了(人事审批)
Task task = taskService.createTaskQuery()
.processInstanceId(processInstance.getId())
.singleResult();
String p2 = "lisi";
paramsMap.put("managerUser", p2);//指定下一个节点负责人之后,再完成任务
taskService.complete(task.getId(), paramsMap);
log.info("当前任务的名称为:【{}】,由用户【{}】执行", task.getName(),p1);
//查询当前任务到哪了(经理审批)
Task task2 = taskService.createTaskQuery()
.processInstanceId(processInstance.getId())
.singleResult();
HashMap<String, Object> p2Map = new HashMap<>();
p2Map.put("opinion", "不通过");//设置流转的条件
taskService.complete(task2.getId(), p2Map);
log.info("当前任务的名称为:【{}】,由用户【{}】执行", task2.getName(),p2);
}
可见我这里的执行流程是这样的:用户先发起流程时,会指定第一个任务节点负责人,此时流程任务节点在人事审批处阻塞,等待人事审批,当要完成人事审批任务的时候,需要指定下一个任务的负责人,当来到经理审批节点的时候,此时任务又会在此阻塞,等待经理来审批,当要完成经理审批任务的时候,马上就要来到排他网关,当opinion的值为不通过的时候,就会回到人事审批,如果为通过的时候,整个流程就结束了,这里我给的是不通过,那么运行程序之后的结果应该是人事审批的负责人有一个代办。


8.2 并行网关
并行网关用于并发执行多个路径。当流程执行到并行网关时,所有输出的路径都会被激活,并且并行执行。并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起。
Fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
Join汇聚:所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程才会通过并行网关。
[!caution]
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。

- 行为:所有的分支都会同时执行。
- 使用场景:用于需要并发执行多个任务的场景。
[!tip]
与其他网关的主要区别是,并行网关不会解析条件,即使顺序流中定义了条件,也会被忽略。
现在我们部署流程定义并且启动流程:
/**
* 部署流程
*/
@Test
void deployParallelGatewayProcess() {
Deployment deploy = processEngine.getRepositoryService().createDeployment()
.addClasspathResource("processes/并行网关demo.bpmn20.xml")
.name("并行网关demo流程")
.deploy();
System.out.println("流程部署的ID:" + deploy.getId());
}
/**
* 发起流程
*/
@Test
void startProcess() {
HashMap<String, Object> paramsMap = new HashMap<>();
paramsMap.put("user", "zhangsan");
paramsMap.put("managerUser", "wangwu");
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("parallel_demo:2:6817c394-c273-11ef-bf2f-00ff89a0c505",paramsMap);
log.info("当前流程的实例ID为:【{}】", processInstance.getId());
}

现在假如我们完成zhangsan的人事审批,看看流程会继续往下走吗。
/**
* 完成任务
*/
@Test
void completeTask() {
Task zhangsanTask = taskService.createTaskQuery()
.processDefinitionId("parallel_demo:2:6817c394-c273-11ef-bf2f-00ff89a0c505")
.taskAssignee("zhangsan")
.singleResult();
taskService.complete(zhangsanTask.getId());
}

可见任务并没有来到副总审批或董事长审批,因为并行网关需要等待所有的流入分支任务,都完成了之后,才会继续往下走,所以现在我们需要完成经理审批。
/**
* 完成任务
*/
@Test
void completeTask() {
Task zhangsanTask = taskService.createTaskQuery()
.processDefinitionId("parallel_demo:2:6817c394-c273-11ef-bf2f-00ff89a0c505")
.taskAssignee("wangwu")
.singleResult();
HashMap<String, Object> paramsMap = new HashMap<>();
paramsMap.put("chairman", "lisi");//董事长审批
paramsMap.put("vicePresident", "zhaoliu");//副总审批
taskService.complete(zhangsanTask.getId(),paramsMap);
}
[!note]
这里因为马上要进入后面一个流程了,所以必须要给出后面任务节点的处理人,否则就会报错。推荐的是当这个流程启动的时候就把所有任务的处理人都指定好,然后传递。后续任务处理的时候,只需要取出参数,在任务完成的时候传递即可。

可见当并行网关流入的所有任务都完成之后,才会通过该网关进行流出的后续任务。
8.3 包含网关
包容网关类似于排他网关,但与排他网关不同,包容网关可以激活多个路径,你可以把他看作是排他网关和并行网关的结合体。它根据条件判断,选择一个或多个分支进行执行。
- 行为:可以激活一个或多个分支。
- 使用场景:当多个条件可能同时满足时使用。
[!caution]
这里有一点要注意,并行网关是等待所有的流入分支任务都完成了之后才会继续执行流出的后续任务,但是包含网关不同,由于包含网关流出条件可以设置条件判断,所以包含网关会等待所有被选中的流入分支任务都完成了之后才会继续执行后续的流出任务。
案例流程图如下:

现在我们部署流程定义并且启动流程:
/**
* 部署流程
*/
@Test
void deployIncludeGatewayProcess() {
Deployment deploy = processEngine.getRepositoryService().createDeployment()
.addClasspathResource("processes/包含网关demo.bpmn20.xml")
.name("包含网关demo流程")
.deploy();
System.out.println("流程部署的ID:" + deploy.getId());
}
/**
* 发起流程
*/
@Test
void startProcess() {
HashMap<String, Object> paramsMap = new HashMap<>();
paramsMap.put("num", 3); //包含网关的条件
paramsMap.put("viceManager", "zhangsan"); //副经理
paramsMap.put("user", "lisi");//人事
paramsMap.put("manager", "wangwu");//经理
paramsMap.put("isManager", true);//包含网关的条件
ProcessInstance processInstance =
runtimeService.startProcessInstanceById("include_gateway_demo:2:ecc9d3b7-c28a-11ef-8492-00ff89a0c505",paramsMap);
log.info("当前流程的实例ID为:【{}】", processInstance.getId());
}
由于我的num指定为3,那么应该会走到副经理以及人事审批这两个任务节点处:

可见确实走到了这两个节点,现在我完成副经理审批任务,看看,流程会继续往下执行吗。
/**
* 完成任务
*/
@Test
void completeTask() {
Task zhangsanTask = taskService.createTaskQuery()
.processDefinitionId("include_gateway_demo:2:ecc9d3b7-c28a-11ef-8492-00ff89a0c505")
.taskAssignee("zhangsan")
.includeProcessVariables()//获取到流程变量
.singleResult();
Map<String, Object> paramsMap = zhangsanTask.getProcessVariables();
paramsMap.put("chairman", "wuhu");//董事长审批
paramsMap.put("persident", "zhaoliu");//总裁审批
taskService.complete(zhangsanTask.getId(),paramsMap);
}

可见流程并没有继续往后走,仍然是在等待所有选中的分支都完成了才会走,现在我们,继续完成人事审批,再看看是否往后走。

当选中的分支都完成了之后,就会继续往后走,由于我们之前设置了isManager为true,所以这里来到的总裁审批,可见符合预期。
8.4 事件网关
[!tip]
由于我们还没有学到事件,这里先简单提一下,在Flowable的高级篇中,我会提到事件。
事件网关用于基于某些事件来决定流程的走向。与条件判断不同,事件网关会根据外部事件的发生来触发某条路径。
- 行为:流程等待某个事件的发生。
- 使用场景:适用于流程等待外部事件的情境,比如等待用户输入、等待消息等。
网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的”执行”, 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:
- 事件网关必须有两条或以上外出顺序流;
- 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)
- 连接到事件网关的中间捕获事件必须只有一个入口顺序流。