目录
Flowable 简介
Flowable 由 Activiti 项目分支演变而来,用于构建和管理各种业务流程。其核心是一个通用的流程引擎,支持 BPMN 2.0(Business Process Model and Notation)标准,这是一种业务流程建模和执行的国际标准。Flowable 完全支持 BPMN 2.0,使得非技术人员也能参与流程的设计。
BPMN 是一种用于业务流程建模的标准化图形表示法。它定义了一套符号和规则,用于描述业务流程的各个方面,如任务、事件、网关等。BPMN 的目标是提供一种统一的、易于理解的图形化语言来表示业务流程。
BPMN 2.0是 BPMN 规范的2.0版本,是当前比较稳定且广泛使用的版本。
Flowable 7.x 是目前 Flowable 的最新版本,该版本基于 JDK 17,如果使用 Spring Boot 集成的话,需要 Spring Boot 的版本最少为 SpringBoot 3.x。
流程设计器
安装
BPMN 定义了如何用符号来描述业务流程,这种符号组合在一起就是个模型,但是程序不能直接识别这些符号,因此 BPMN 规定使用 XML 格式来编码业务流程模型,这种编码形式称为 BPMN XML。
我们一般使用流程设计器画图业务流程模型,再导出为 BPMN 规范的 XML 文件,然后再使用。
Flowable 官方有提供一个名为 Flowable-UI 的东西,这是一个 Web 应用,可以直接在这上面设计业务流程,但是 Flowable-UI 从 Flowable 7 以后就没提供了,但是经过我的测试,用旧版本的UI 也可以实现对应功能,以下提供一个我目前使用版本的 Docker 命令:
docker run -p 8080:8080 flowable/flowable-ui:6.8.0
使用以下命令启动后,访问地址端口,可以看到以下界面:
使用默认的用户名密码登录:
user: admin
password: test
使用
打开建模应用程序,可以看到以下界面:
点击创建流程,可以创建一个业务流程,其中模型 key 用于唯一标识一个业务流程模型,这个后面会有用的,模型名称可以重复:
创建完成后,进入流程的编辑页面:
编辑完成后,保存,查看模型,会进到这个界面,这里可以将模型导出成为XML:
SpringBoot 3 整合
使用 SpringBoot 整合,首先需要引入以下依赖:
<!-- https://mvnrepository.com/artifact/org.flowable/flowable-spring-boot-starter -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.0.1</version>
</dependency>
由于 Flowable 运行需要数据库的支持,所以需要配置一个数据源:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/flowable_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
flowable:
# 关闭定时任务JOB
async-executor-activate: false
# 在引擎启动时,如果数据库架构与 Flowable 引擎期望的架构不一致,Flowable 会自动更新数据库架构。这包括创建缺失的表和列,以及修改现有的表和列以匹配最新版本
database-schema-update: true
配置数据源后,启动项目,Flowable会自动识别数据库,并帮你新建对应的表,大约有70个。
表结构
主要表前缀及其用途:
- ACT_RE_*:RE 代表 repository(存储)。这些表包含静态信息,如流程定义和流程的资源(图片、规则等)。RepositoryService 接口操作的表。
- ACT_RU_*:RU 代表 runtime。这些表存储运行时信息,如流程实例、用户任务、变量、作业等。Flowable 只在流程实例运行中保存运行时数据,并在流程实例结束时删除记录,以保证运行时表小且快。RuntimeService 接口操作的表。
- ACT_HI_*:HI 代表 history。这些表存储历史数据,如已完成的流程实例、变量、任务等。HistoryService 接口操作的表。
- ACT_ID_*:ID 表示 identity(组织机构)。这些表包含标识的信息,如用户、用户组等。IdentityService 接口操作的表。
- ACT_GE_*:通用数据表,用于存储各种情况下都可能需要的数据。
核心表:
- act_ge_bytearray:二进制数据表,用于存储流程定义、流程模板、流程图的图片等
- act_re_deployment:记录部署操作的表,一次部署操作对应一条记录
- act_re_procde:流程定义表,一次部署可以部署多个流程,一个流程对应一条记录
- act_ru_task:存储运行中流程的任务节点信息,常用于查询人员或部门的待办任务。
- act_ru_execution:运行时流程执行实例表,记录运行中流程运行的各个分支信息。
- act_hi_procinst:历史流程实例表,存储流程实例历史数据(包含正在运行的流程实例)。
流程部署
- 导出流程模型 XML
- 通过 ProcessEngine 获取 RepositoryService,或直接注入 RepositoryService
private ProcessEngine processEngine; private RepositoryService repositoryService; @Autowired public void setRepositoryService(RepositoryService repositoryService) { this.repositoryService = repositoryService; } @Autowired public void setProcessEngine(ProcessEngine processEngine) { this.processEngine = processEngine; } @Test public void test() { System.out.println(repositoryService != null && repositoryService == processEngine.getRepositoryService()); }
- 部署流程图,一次部署可以部署多个
@Test public void testDeploy() { Deployment deploy = repositoryService //创建一个部署 .createDeployment() //部署的流程图 .addClasspathResource("flowable/Example01.bpmn20.xml") //这次部署的名字 .name("test_deploye") //执行部署 .deploy(); }
启动流程
- 部署一个流程,获取到部署的流程ID,或者获取到流程定义 Key,就是一开始画图时候填写的那个 Key,ID 和 Key 可以从 ACT_RE_PROCDEF 表忠获取。
- 通过 ProcessEngine 获取 RepositoryService,或直接注入 RepositoryService
private RuntimeService runtimeService; @Autowired public void setRuntimeService(RuntimeService runtimeService) { this.runtimeService = runtimeService; }
- 通过流程 ID 或 Key 启动一个流程实例
@Test public void testStart() { ProcessInstance processInstanceById = runtimeService.startProcessInstanceById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418"); ProcessInstance processInstanceByKey = runtimeService.startProcessInstanceByKey("Example01"); }
流程审批
- 任务类的操作都是使用 TaskService 完成的,可以使用 ProcessEngine 获取,或直接注入
private TaskService taskService; @Autowired public void setTaskService(TaskService taskService) { this.taskService = taskService; }
- 查询某个用户的代办任务,获取到 task id
@Test public void testQuery() { List<Task> taskList = taskService .createTaskQuery() //查询所有zhangsan用户代办的任务 .taskAssignee("zhangsan") .list(); }
- 使用 complete 完成审批
@Test public void testComplete() { taskService.complete("e4c98b66-5856-11ef-85d0-4c034f4db418"); }
流程挂起和激活
Flowable 可以针对流程定义和流程实例进行流程挂起和激活。
如果将流程定义挂起,再尝试启动一个新的流程实例,会报 xxx is suspended 异常,但是不会影响已经创建的实例继续:
@Test
public void testSuspend() {
//挂起流程定义
repositoryService.suspendProcessDefinitionById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418");
}
@Test
public void testActivate() {
//激活流程定义
repositoryService.activateProcessDefinitionById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418");
}
如果对某一个流程实例进行挂起,则该实例无法再进行后续操作:
@Test
public void testSuspendTask() {
//挂起流程实例
runtimeService.suspendProcessInstanceById("997106c1-586f-11ef-8c2e-4c034f4db418");
}
@Test
public void testActivateTask() {
//激活流程实例
runtimeService.activateProcessInstanceById("997106c1-586f-11ef-8c2e-4c034f4db418");
}
任务分配
Flowable 的任务分配是流程管理中的一个重要环节,它决定了流程中的任务由谁来执行。
Flowable支持多种任务分配方式,主要包括固定分配、表达式分配和监听器分配。
固定分配
固定分配是最直接的任务分配方式,即在绘制流程图时,直接在流程文件中通过 Assignee 属性来指定任务的执行者。这种方式简单明了,但缺乏灵活性,一旦流程设计完成,任务的执行者就固定下来了。
表达式分配
表达式分配是 Flowable 中更为灵活的任务分配方式,它允许在流程执行时动态地确定任务的执行者。Flowable 支持两种 UEL(Unified Expression Language)表达式:值表达式(Value Expression)和方法表达式(Method Expression)。
值表达式
解析为一个值,通常用于直接指定任务的执行者。例如,${assignee} 这样的表达式会在流程执行时被解析为流程变量 assignee 的值,该值即为任务的执行者。
设置好表达式后,可以在启动流程实例时或调用 complete 时使用 map 传递变量,传递过的变量可以在后续的步骤中通用:
@Test
public void testStart() {
Map<String, Object> map = new HashMap<>();
map.put("assignee", "zhangsan");
ProcessInstance processInstanceById = runtimeService.startProcessInstanceById(
"Example_02:1:736ec65b-588c-11ef-8923-4c034f4db418", map
);
}
@Test
public void testComplete() {
Map<String, Object> map = new HashMap<>();
map.put("assignee1", "lisi");
taskService.complete("c228521f-588d-11ef-b80c-4c034f4db418", map);
}
方法表达式
调用一个方法,可以带或不带参数。这种方法允许在流程执行时通过调用某个方法来动态确定任务的执行者。例如,${userService.findAssigneeByTaskId(taskId)} 这样的表达式会调用userService 的 findAssigneeByTaskId 方法来获取任务的执行者。
这里的 userService 需要是容器中的一个 bean。
监听器分配
监听器分配是一种更为高级的任务分配方式,它通过在流程中设置监听器来动态地改变任务的执行者。监听器可以在任务创建、分配、完成等关键节点触发,并执行相应的逻辑来改变任务的执行者。例如,可以设置一个任务创建监听器,在任务创建时根据某些条件(如发起人的部门、任务的优先级等)来动态指定任务的执行者。
- 编写一个监听器类实现 TaskListener 接口
public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { } }
- 将类的全限定名设置到流程图
- 重写 notify 方法实现指派
public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { switch (delegateTask.getEventName()) { case EVENTNAME_CREATE -> { //节点创建,指派处理人 delegateTask.setAssignee("zhangsan"); } } } }
流程变量
流程变量是 Flowable 管理工作流时根据管理需要而设置的变量。它们用于在流程的不同阶段之间传递信息,如任务分配、条件判断等。流程变量可以是任何类型的数据,如字符串、整数、浮点数、布尔值等。
流程变量的作用域可以是一个流程实例(ProcessInstance)、一个任务(Task)或者是一个执行实例(execution)。流程变量的默认作用域是流程实例,此时可以称为global变量。Global变量在整个流程实例中都是可见的,但变量名不允许重复,后设置的值会覆盖之前设置的值。任务和执行实例的变量作用域相对较小,仅针对一个任务或一个执行实例范围,称为local变量。Local变量在不同的任务或执行实例中互不影响,即使变量名相同也不会互相冲突。
Flowable 将流程变量分为两种类型:运行时变量和历史变量。
- 运行时变量:流程实例运行时的变量,存入 act_ru_variable 表中。在流程实例运行结束时,这些变量在表中会被删除。因此,查询一个已经完结的流程实例的变量时,需要在历史变量表中查找。
- 历史变量:存入 act_hi_varinst 表中。在流程启动时,流程变量会同时存入历史变量表中;在流程结束时,历史表中的变量仍然存在。这可以视为“永久代”的流程变量。
运行时变量
在流程实例启动时,可以通过 startProcessInstanceByKey 或 startProcessInstanceById 等方法的可选参数来设置流程变量。这些变量将作为全局变量(Global Variables),在整个流程实例中有效:
@Test
public void testStart() {
Map<String, Object> map = new HashMap<>();
map.put("v1", "123456");
map.put("v2", "asfafs");
map.put("v3", "fasaf");
ProcessInstance processInstanceById = runtimeService.startProcessInstanceById(
"Example_02:1:736ec65b-588c-11ef-8923-4c034f4db418", map
);
}
在流程执行过程中,可以通过 setVariable、setVariableLocal、setVariables、setVariablesLocal 等方法为特定的执行实例(Execution)或任务(Task)设置变量。这些变量可以是局部变量(Local Variables),仅在当前执行实例或任务中有效:
runtimeService.setVariable(executionId, "variableName", variableValue);
// 或者为当前执行实例设置局部变量
runtimeService.setVariableLocal(executionId, "variableName", variableValue);
taskService.setVariable(taskId, "variableName", variableValue);
// 或者为当前task设置局部变量
taskService.setVariableLocal(taskId, "variableName", variableValue);
可以在流程审批的时候设置变量:
@Test
public void testStart() {
Map<String, Object> map = new HashMap<>();
map.put("v1", "123456");
map.put("v2", "asfafs");
map.put("v3", "fasaf");
taskService.complete(taskId, map);
}
获取流程变量:
Object variableValue = runtimeService.getVariable(executionId, variableName);
Object taskVariableValue = taskService.getVariable(taskId, variableName);
历史变量
Flowable 将历史变量存储在特定的数据库表中,通常这个表名为 ACT_HI_VARINST。这个表包含了流程实例运行过程中的所有变量实例的信息,包括变量的名称、类型、值以及它们所属的流程实例和任务实例等。
身份服务
候选人
在 Flowable 中,候选人是指可能被分配给某个任务的用户或用户组。当流程执行到某个任务节点时,该节点的候选人可以领取并执行任务。这种方式避免了在流程定义时固定设置任务负责人,使得在需要变更任务负责人时无需修改流程定义,提高了系统的可扩展性和灵活性。
可以在设计器直接分配候选人:
拾取任务
候选人不是审批人,需要进行拾取操作才能进行审批。多个候选人只有一个能变成审批人。
- 查看候选人任务
List<Task> zhangsan = taskService.createTaskQuery().taskCandidateUser("zhangsan").list();
- 拾取任务
taskService.claim(taskId,"zhangsan");
归还任务
@Test
public void testReturn() {
//审批人 -> 候选人
taskService.unclaim(taskId);
}
指派给别人
@Test
public void testSetAssignee() {
//审批人指派给另一个审批人
taskService.setAssignee(taskId, userId);
}
候选人组
创建用户
private IdentityService identityService;
@Autowired
public void setIdentityService(IdentityService identityService) {
this.identityService = identityService;
}
@Test
public void testCreateUser() {
User user = identityService.newUser("zhangsan");
user.setEmail("xxx@163.com");
identityService.saveUser(user);
}
创建用户组
@Test
public void testCreateGroup() {
Group group = identityService.newGroup("group");
identityService.saveGroup(group);
}
用户关联用户组
@Test
public void testMembership() {
Group group = identityService.createGroupQuery().groupId("group").singleResult();
List<User> userList = identityService.createUserQuery().list();
for (User user : userList) {
identityService.createMembership(user.getId(), group.getId());
}
}
流程图设置用户组
查询组待办任务
@Test
public void testQueryGroupTask() {
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("group").list();
}
网关
网关是Flowable 工作流引擎中的一个重要组成部分,用于控制流程的执行路径和决策。Flowable支持多种类型的网关,其中最常见的包括排他网关(Exclusive Gateway)、并行网关(Parallel Gateway)等。
排他网关
排他网关,也被称为异或网关(XOR Gateway),是 Flowable 流程模型中的一个重要组件。它的主要功能是根据设定的条件对流程的执行路径进行选择和判断。
当流程执行到排他网关时,会按照所有出口顺序流定义的顺序对它们进行计算,并选择第一个条件计算为 true 的顺序流继续执行。如果没有可选的顺序流(即所有条件都不满足),则会抛出异常。
并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起。其功能是基于进入和外出顺序流的。
当流程执行到并行网关的分支点时,会为每个外出顺序流创建一个并发分支,所有分支并行执行。当所有分支都执行完毕后,流程会汇聚到并行网关的汇聚点,然后继续执行。
与其他网关不同的是,并行网关会忽略条件。
包含网关
包含网关可以看作是排他网关(Exclusive Gateway)和并行网关(Parallel Gateway)的结合体。它允许根据条件选择性地执行多个路径,而不仅仅是单一路径或并行执行所有路径。
包含网关在需要基于条件选择性执行多个并行路径的场景中非常有用。例如,在审批流程中,可能需要根据不同的审批条件同时触发多个审批人进行审批。