Flowable 入门指南:BPMN、流程设计器与 Spring Boot 集成

这篇文章虽然发布较早,但仍然是了解 Flowable 的一篇优秀入门资料,对于学习 Flowable 与 Spring 的集成也很有参考价值,因此在这里重新分享。

版本说明:本文示例基于 Flowable 6.5.0 和 MySQL Connector/J 8.0.11。部分界面、依赖坐标及配置项可能与新版本不同,实际使用时请结合对应版本的官方文档进行调整。

转载来源:原博客地址


1. 什么是 BPMN

1.1 工作流(引擎)介绍

在任何行业和企业中,都有各种各样的流程,例如:

  1. 请假流程
  2. 报销流程
  3. 入职流程
  4. 离职流程
  5. 出差流程
  6. 其他需要多人协作的流程

即使你没有设计过工作流,每天也很可能在使用各种流程。

工作流引擎本质上就是用代码实现 UML 流程图中的各个步骤。

工作流示意图

凡是需要多个人或多个部门按照先后顺序逐级审批的业务,都可以使用工作流来完成。

1.2 为什么要用 BPMN

业务流程模型与标记法(Business Process Model and Notation,BPMN)定义了描述流程的基本符号,以及这些图元如何组合成业务流程图(Business Process Diagram)。

对于流程控制,一种比较初级的实现方式是在业务代码中加入 status 状态字段来维护流程状态,审批人也可能直接使用硬编码。这种方式在流程较简单时实现很快,但从长远来看会出现几个问题:

  1. 流程健壮性差:一旦出现人员变动或组织结构调整,就需要修改代码,维护成本较高。
  2. 流程无法复用:组织出现新的工作流程时,往往需要重新开发一套代码,开发成本很高。
  3. 流程与业务代码耦合:流程逻辑和业务逻辑互相混杂,不符合单一职责与解耦原则。

1.3 认识 BPMN 基础元素

BPMN 2.0 的核心主要包括以下四类基础元素:

元素 英文 主要作用
流对象 Flow Objects 描述流程中发生的事件、活动和分支
数据 Data 描述流程使用、产生或存储的数据
连接对象 Connecting Objects 连接流程元素并表达流转关系
泳道 Swimlanes 按参与者或职责对活动进行分组

1.3.1 流对象(Flow Objects)

流对象是定义业务流程的主要图形元素,包括事件、活动和网关三类。

1. 事件(Events)

事件指业务流程运行过程中发生的事情,分为:

  • 开始事件:表示一个流程的开始。
  • 中间事件:发生在开始与结束事件之间,并会影响流程处理。
  • 结束事件:表示流程结束。

BPMN 事件

2. 活动(Activities)

活动包括任务和子流程两类。子流程的图形下方中间会增加一个小加号(+),用于与普通任务区分。

BPMN 活动

3. 网关(Gateways)

网关用于表示流程的分支与合并:

  • 排他网关:只有一条路径会被选择。
  • 并行网关:所有路径都会被同时选择。
  • 包容网关:可以同时执行多条路径,也可以在网关上设置条件。
  • 事件网关:专门为中间捕获事件设置,可以配置多个输出流,分别指向不同的中间捕获事件。流程执行到事件网关后会进入等待状态,直到捕获相应事件后才继续执行。

BPMN 网关

1.3.2 数据(Data)

数据主要通过四种元素表示:

  • 数据对象(Data Objects)
  • 数据输入(Data Inputs)
  • 数据输出(Data Outputs)
  • 数据存储(Data Stores)

1.3.3 连接对象(Connecting Objects)

流对象彼此连接,或与其他信息建立联系,主要有三种方式:

  • 顺序流:使用带实心箭头的实线表示,用于指定活动执行的顺序。
  • 消息流:使用带箭头的虚线表示,用于描述两个独立业务参与者(业务实体或业务角色)之间发送和接收消息的过程。
  • 关联:使用带箭头的点线表示,用于将相关数据、文本和其他人工信息与流对象联系起来,也可用于展示活动的输入和输出。

1.3.4 泳道(Swimlanes)

泳道用于对主要建模元素进行分组,将活动划分到不同的可视化类别中,以描述不同参与者的责任与职责。

1.4 BPMN 实例

实例 1:拍卖服务 BPMN 模板

拍卖服务 BPMN 模板

实例 2:书籍销售流程 BPMN

书籍销售流程 BPMN

2. Flowable 简介

Flowable 是 BPMN 的一种 Java 软件实现。除了 BPMN 流程引擎,Flowable 还包括 DMN 决策表、CMMN Case 管理引擎,以及用户管理、微服务 API 等一系列功能,因此也可以把它看作一个完整的流程服务平台。

2.1 Flowable 部署

2.1.1 下载

访问 Flowable Engine Releases 并选择需要的版本。本文使用的是 6.5.0。

下载 Flowable 6.5.0

2.1.2 部署 WAR 包

下载并解压后,wars 目录中共有 5 个 WAR 包:

Flowable WAR 包

  • flowable-admin:后台管理。
  • flowable-idm:用户与用户组权限管理。
  • flowable-modeler:流程定义管理。
  • flowable-rest:流程引擎对外提供的 API 接口。
  • flowable-task:用户任务管理。

将这些 WAR 包放入 Tomcat 的 webapps 目录,然后运行 tomcat/bin/startup.bat 启动 Tomcat。首次启动和解压通常需要等待一段时间。

2.1.3 修改配置文件

所有 WAR 包解压后,在各项目的 WEB-INF/classes 目录中找到配置文件。例如:

1
webapps/flowable-admin/WEB-INF/classes/application-dev.properties

1. 修改 application-dev.properties

将数据库连接改为自己的地址。数据库名 flowable 需要自行创建,再执行 flowable-6.5.0.zip 中 database/create/all 目录下的 SQL 脚本完成建表。

1
2
3
4
5
6
7
8
9
10
11
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

flowable.admin.app.server-config.process.port=9999
flowable.admin.app.server-config.cmmn.port=9999
flowable.admin.app.server-config.app.port=9999
flowable.admin.app.server-config.dmn.port=9999
flowable.admin.app.server-config.form.port=9999
flowable.admin.app.server-config.content.port=9999

2. 修改 flowable-default.properties

修改 webapps/flowable-admin/WEB-INF/classes/flowable-default.properties,主要是将数据库连接切换为 MySQL:

注意:

  1. 需要在每个项目的 WEB-INF/lib 目录下添加 MySQL 驱动包。

  2. 高版本 MySQL 驱动需要配置部分必需的连接参数。本文使用 mysql-connector-java-8.0.11.jar,连接地址如下:

    1
    jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
server.port=9988
server.servlet.context-path=/flowable-admin
management.endpoints.jmx.unique-names=true

# This is needed to force use of JDK proxies instead of using CGLIB
spring.aop.proxy-target-class=false
spring.aop.auto=false
spring.application.name=flowable-ui-admin
spring.liquibase.enabled=false
spring.servlet.multipart.max-file-size=10MB
spring.banner.location=classpath:/org/flowable/spring/boot/flowable-banner.txt

# The default domain for generating ObjectNames must be specified. Otherwise,
# when multiple Spring Boot applications start in the same servlet container,
# all would be created with the same name, for example:
# (com.zaxxer.hikari:name=dataSource,type=HikariDataSource)
spring.jmx.default-domain=${spring.application.name}

# Expose all actuator endpoints to the web.
# They are exposed, but only authenticated users can see /info and /health;
# users with access-admin can see the others.
management.endpoints.web.exposure.include=*

# Full health details should only be displayed when a user is authorized.
management.endpoint.health.show-details=when_authorized

# Only users with role access-admin can access full health details.
management.endpoint.health.roles=access-admin

# Spring prefixes roles with ROLE_. Flowable does not have that concept yet,
# so override it with an empty string.
flowable.common.app.role-prefix=

# H2 example (default)
# spring.datasource.driver-class-name=org.h2.Driver
# spring.datasource.url=jdbc:h2:tcp://localhost/flowableadmin
# spring.datasource.url=jdbc:h2:~/flowable-db/db;AUTO_SERVER=TRUE;AUTO_SERVER_PORT=9091;DB_CLOSE_DELAY=-1

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false

# spring.datasource.driver-class-name=org.postgresql.Driver
# spring.datasource.url=jdbc:postgresql://localhost:5432/flowableadmin

# spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
# spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=flowableadmin

# spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# spring.datasource.url=jdbc:oracle:thin:@localhost:1521:FLOWABLEADMIN

# spring.datasource.driver-class-name=com.ibm.db2.jcc.DB2Driver
# spring.datasource.url=jdbc:db2://localhost:50000/flowableadmin

spring.datasource.username=root
spring.datasource.password=root

所有项目的配置修改完成后,关闭之前运行的 Tomcat,再重新启动。

2.1.4 访问 Flowable 应用

等待所有应用正常启动后,可以通过以下地址访问:

应用 地址 用户名 密码
Flowable Admin http://127.0.0.1:8080/flowable-admin admin test
Flowable IDM http://127.0.0.1:8080/flowable-idm admin test
Flowable Modeler http://127.0.0.1:8080/flowable-modeler admin test

2.2 Flowable 数据表

Flowable 的数据库表通常以 ACT_ 开头,服务 API 的命名也大体遵循这一规则。

表名前缀 含义 说明
ACT_RE_ Repository 存储流程定义、流程资源(图片、规则等)静态信息
ACT_RU_ Runtime 存储流程实例、用户任务、变量、作业等运行时信息
ACT_HI_ History 存储已完成的流程实例、变量、任务等历史数据
ACT_GE_ General 存储在多处使用的通用数据

Flowable 只在流程实例运行期间保存运行时数据,并在流程实例结束时删除对应运行时记录,从而让运行时表保持较小的数据量和较高的查询速度。

2.2.1 通用数据表(2 个)

  • act_ge_bytearray:二进制数据表,例如流程定义、流程模板和流程图的字节流文件。
  • act_ge_property:Flowable 相关的基础信息,例如各个模块使用的版本信息,日常业务中较少直接使用。

2.2.2 历史表(8 个)

这些表主要由 HistoryService 接口操作:

  • act_hi_actinst:历史节点表,存放流程实例运行经过的各个节点信息,包括开始、结束等非任务节点。
  • act_hi_attachment:历史附件表,存放历史节点上传的附件信息,较少使用。
  • act_hi_comment:历史意见表。
  • act_hi_detail:历史详情表,存储节点运行的部分详细信息,较少使用。
  • act_hi_identitylink:历史流程人员表,存储流程各节点的候选人、候选组和办理人信息,常用于查询某人或某部门的已办任务。
  • act_hi_procinst:历史流程实例表,存储流程实例历史数据,也包含正在运行的流程实例。
  • act_hi_taskinst:历史流程任务表,存储历史任务节点。
  • act_hi_varinst:流程历史变量表,存储流程历史节点的变量信息。

2.2.3 用户相关表(4 个)

这些表主要由 IdentityService 接口操作:

  • act_id_group:用户组信息表,对应节点选择的候选组信息。
  • act_id_info:用户扩展信息表。
  • act_id_membership:用户与用户组关系表。
  • act_id_user:用户信息表,对应节点选择的办理人或候选人信息。

2.2.4 流程定义、流程模板相关表(3 个)

这些表主要由 RepositoryService 接口操作:

  • act_re_deployment:部署信息表,存储流程定义和模板的部署信息。
  • act_re_procdef:流程定义信息表,存储流程定义的描述信息;真正的流程定义内容以字节形式存储在 act_ge_bytearray 表中。
  • act_re_model:流程模板信息表,存储流程模板的描述信息;真正的模板内容以字节形式存储在 act_ge_bytearray 表中。

2.2.5 流程运行时表(6 个)

这些表主要由 RuntimeService 接口操作:

  • act_ru_task:运行时流程任务节点表,存储运行中流程的任务节点信息。该表非常重要,常用于查询人员或部门的待办任务。
  • act_ru_event_subscr:事件监听信息表,较少直接使用。
  • act_ru_execution:运行时流程执行实例表,记录运行中流程各个分支的信息;当没有子流程时,其数据通常与 act_ru_task 表数据一一对应。
  • act_ru_identitylink:运行时流程人员表,常用于查询人员或部门的待办任务。
  • act_ru_job:运行时定时任务数据表,存储流程的定时任务信息。
  • act_ru_variable:运行时流程变量数据表,存储运行中流程各节点的变量信息。

3. Flowable 流程设计器的使用

3.1 新建流程图

新建流程图

注意:流程的 key 最好不要包含中文。后续流程部署、启动和跳转都可能用到它,因此不要定义得过于随意。

3.2 根据业务需求绘制流程图

3.2.1 节点分类

常用节点包括开始节点、用户任务节点和结束节点。

流程节点分类

3.2.2 节点名称

可以直接在“名称”处填写,也可以双击节点输入名称。

配置节点名称

3.2.3 分配用户

该配置只针对用户任务节点。

可以为节点分配审批人。由于 Flowable 自带的组织结构与实际业务系统的组织架构可能存在差异,因此在简单场景中通常先使用固定值。

为节点分配用户

选择节点审批人

3.2.4 任务监听器

任务监听器只针对用户任务节点。

监听器类型:

  • create:任务被创建时执行。
  • assignment:任务被签收或分配时执行。
  • complete:任务完成时执行。
  • delete:任务被删除时执行。

任务监听器类型

常见使用场景:

  • create:直接为用户节点设置审批人,只适用于办理人固定或候选人固定的场景。如果用户节点的办理人不固定,可以通过 create 任务监听器动态设置。
  • complete:当用户任务完成后需要抄送或触发其他业务动作时,可以使用 complete 任务监听器。

不同类型任务监听器的配置方式基本相同。

3.2.5 多实例

Flowable 支持为一个节点创建多个实例。通俗地说,流程图中虽然只画了一个用户节点,但通过多实例配置,可以在流程运行时为该节点创建多个实例,类似于循环,循环次数取决于配置的基数或集合。

多实例支持两种执行方式:

  • 串行(Sequential):多个实例按顺序执行。
  • 并行(Parallel):多个实例同时执行,不强调先后顺序。

默认情况下,两者都需要所有实例完成后,该节点才算结束。

配置多实例

3.2.6 流程线

1. 流条件配置

审批流中最常见的结果是通过和驳回,这时需要为对应的流程线设置流转条件。Flowable 流条件以表达式的方式存在。

配置流程线条件

2. 跳过表达式

跳过表达式的配置方式与流条件类似。流程启动后,当传入的流程变量满足表达式要求时,会自动跳过对应节点。

配置跳过表达式

3.2.7 常用网关

1. 排他网关

通过排他网关的流程线只能有一条被选择。

排他网关支持设置默认出口。流程执行后,如果其他流程线的条件都返回 false,则会进入默认流程线。

2. 并行网关

只有所有需要经过并行网关的任务流都完成后,流程才会进入下一步。

配置排他网关

配置并行网关

3.3 保存流程图

保存流程图

保存后,流程图相关数据会保存在 act_de_model 表中。每次更新并保存后,流程定义的版本号都会加 1。

3.4 下载流程图

下载流程图

4. 在 Spring Boot 中使用 Flowable

4.1 基本用法

4.1.1 引入依赖

在 pom.xml 中加入 Web、JDBC、MySQL 和 Flowable 等相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!-- Web 开发起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>

<!-- Flowable 工作流 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-json-converter</artifactId>
<version>6.5.0</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.1</version>
</dependency>

4.1.2 配置 application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9999

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root

flowable:
# 关闭定时任务 Job
async-executor-activate: false
# 设置为 true 后,如果 Flowable 发现数据库表结构与当前版本不一致,
# 会自动将数据库表结构升级到新版本。
database-schema-update: true

4.1.3 绘制 BPMN 流程图

这里以学生请假流程为例。学生先发起请假申请,再由老师审核。老师审核通过后,根据请假天数进行判断:

  • 请假天数大于 2 天:流转到校长处审核,然后结束流程。
  • 请假天数不大于 2 天:直接结束流程。

部署流程需要一个 .bpmn20.xml 文件,可以手动编辑,也可以借助流程设计工具创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:flowable="http://flowable.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">

<process id="StudentLeave" name="学生请假流程" isExecutable="true">
<startEvent
id="start"
name="开始"
flowable:formFieldValidation="true">
</startEvent>

<userTask
id="apply"
name="请假申请"
flowable:assignee="${studentUser}"
flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler">
<![CDATA[false]]>
</modeler:initiator-can-complete>
</extensionElements>
</userTask>

<userTask
id="teacherPass"
name="老师审批"
flowable:candidateGroups="teacher"
flowable:formFieldValidation="true">
</userTask>

<exclusiveGateway
id="judgeTask"
name="判断是否大于2天">
</exclusiveGateway>

<endEvent id="end" name="结束"></endEvent>

<userTask
id="principalPass"
name="校长审批"
flowable:candidateGroups="principal"
flowable:formFieldValidation="true">
</userTask>

<sequenceFlow
id="principalCheck"
name="通过"
sourceRef="principalPass"
targetRef="end">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${outcome == '通过'}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="principalNotPassFlow"
name="驳回"
sourceRef="principalPass"
targetRef="apply">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${outcome == '驳回'}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="teacherPassFlow"
name="通过"
sourceRef="teacherPass"
targetRef="judgeTask">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${outcome == '通过'}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="teacherNotPassFlow"
name="驳回"
sourceRef="teacherPass"
targetRef="apply">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${outcome == '驳回'}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="startFlow"
sourceRef="start"
name="流程开始"
targetRef="apply">
</sequenceFlow>

<sequenceFlow
id="applyFlow"
sourceRef="apply"
name="申请流程"
targetRef="teacherPass">
</sequenceFlow>

<sequenceFlow
id="judgeLess"
name="小于2天"
sourceRef="judgeTask"
targetRef="end">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${day <= 2}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="judgeMore"
name="大于2天"
sourceRef="judgeTask"
targetRef="principalPass">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${day > 2}]]>
</conditionExpression>
</sequenceFlow>
</process>

<bpmndi:BPMNDiagram id="BPMNDiagram_StudentLeave">
<bpmndi:BPMNPlane
bpmnElement="StudentLeave"
id="BPMNPlane_StudentLeave">

<bpmndi:BPMNShape
bpmnElement="start"
id="BPMNShape_start">
<omgdc:Bounds
height="30.0"
width="30.0"
x="100.0"
y="163.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="apply"
id="BPMNShape_apply">
<omgdc:Bounds
height="80.0"
width="100.00000000000003"
x="229.9708609547486"
y="138.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="teacherPass"
id="BPMNShape_teacherPass">
<omgdc:Bounds
height="80.0"
width="99.99999999999994"
x="436.9446358140222"
y="138.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="judgeTask"
id="BPMNShape_judgeTask">
<omgdc:Bounds
height="40.0"
width="40.0"
x="645.0"
y="158.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="end"
id="BPMNShape_end">
<omgdc:Bounds
height="28.0"
width="28.0"
x="795.0"
y="164.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="principalPass"
id="BPMNShape_principalPass">
<omgdc:Bounds
height="80.0"
width="100.0"
x="615.0"
y="280.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNEdge
bpmnElement="judgeLess"
id="BPMNEdge_judgeLess">
<omgdi:waypoint
x="684.5095911949685"
y="178.43356643356645">
</omgdi:waypoint>
<omgdi:waypoint
x="795.0000829380081"
y="178.04860604497966">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="principalNotPassFlow"
id="BPMNEdge_principalNotPassFlow">
<omgdi:waypoint x="615.0" y="320.0"></omgdi:waypoint>
<omgdi:waypoint
x="279.9708609547486"
y="320.0">
</omgdi:waypoint>
<omgdi:waypoint
x="279.9708609547486"
y="217.95000000000002">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="judgeMore"
id="BPMNEdge_judgeMore">
<omgdi:waypoint
x="665.4326241134752"
y="197.51043586109145">
</omgdi:waypoint>
<omgdi:waypoint
x="665.1411660777385"
y="280.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="teacherNotPassFlow"
id="BPMNEdge_teacherNotPassFlow">
<omgdi:waypoint
x="486.9446358140222"
y="138.0">
</omgdi:waypoint>
<omgdi:waypoint
x="486.9446358140222"
y="98.8874737106014">
</omgdi:waypoint>
<omgdi:waypoint
x="279.9708609547486"
y="98.8874737106014">
</omgdi:waypoint>
<omgdi:waypoint
x="279.9708609547486"
y="138.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="principalCheck"
id="BPMNEdge_principalCheck">
<omgdi:waypoint
x="714.9499999999886"
y="319.64664310954066">
</omgdi:waypoint>
<omgdi:waypoint x="806.5" y="319.0"></omgdi:waypoint>
<omgdi:waypoint
x="808.7518112709728"
y="191.94785201600882">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="applyFlow"
id="BPMNEdge_applyFlow">
<omgdi:waypoint
x="329.9208609546613"
y="178.0">
</omgdi:waypoint>
<omgdi:waypoint
x="436.9446358140222"
y="178.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="teacherPassFlow"
id="BPMNEdge_teacherPassFlow">
<omgdi:waypoint
x="536.8946358140222"
y="178.0">
</omgdi:waypoint>
<omgdi:waypoint x="645.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="startFlow"
id="BPMNEdge_startFlow">
<omgdi:waypoint
x="129.94999932842546"
y="178.0">
</omgdi:waypoint>
<omgdi:waypoint
x="229.970860954748"
y="178.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

学生请假流程图

将 XML 保存为 学生请假流程.bpmn20.xml,并复制到项目的 resources/processes 目录下。如果没有 processes 文件夹,可以自行创建。

Flowable 会自动部署 processes 目录中的流程模型。也可以通过修改 flowable.process-definition-location-prefix 的默认值,重新指定流程定义目录。

项目启动后,可以通过 RepositoryService 查询已部署的模型。查询结果不为空,就表示模型部署成功。如果应用与 Flowable Modeler 使用同一个数据库,也可以在 Flowable Admin 管理平台中部署流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired
private RepositoryService repositoryService;

/**
* 查询流程定义列表。
* 涉及 act_re_procdef 表,部署成功后会新增记录。
*/
@Test
public void testProcessDefinition() {
List<ProcessDefinition> processList =
repositoryService.createProcessDefinitionQuery().list();

for (ProcessDefinition processDefinition : processList) {
log.info(
"ProcessDefinition name = {}, deploymentId = {}",
processDefinition.getName(),
processDefinition.getDeploymentId()
);
}
}

4.1.4 员工请假流程示例

下面再提供一个“员工请假流程”,可用于测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:flowable="http://flowable.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">

<process
id="holidayRequest"
name="Holiday Request"
isExecutable="true">

<startEvent id="startEvent"></startEvent>

<sequenceFlow
id="sequenceFlow-3fa7af74-df9a-4d3a-b0eb-10b20e7dc202"
sourceRef="startEvent"
targetRef="approveTask">
</sequenceFlow>

<userTask
id="approveTask"
name="经理通过或驳回申请">
</userTask>

<sequenceFlow
id="sequenceFlow-d9127dc0-158f-4169-b61b-99226ffe8494"
sourceRef="approveTask"
targetRef="decision">
</sequenceFlow>

<exclusiveGateway id="decision"></exclusiveGateway>

<serviceTask
id="externalSystemCall"
name="系统记录申请天数"
flowable:class="org.flowable.CallExternalSystemDelegate">
</serviceTask>

<sequenceFlow
id="sequenceFlow-6232f578-3975-4d7e-81f2-43b242d33ebd"
sourceRef="externalSystemCall"
targetRef="holidayApprovedTask">
</sequenceFlow>

<userTask
id="holidayApprovedTask"
name="员工查看申请通过">
</userTask>

<sequenceFlow
id="sequenceFlow-29fd3375-9bcb-42f2-bb6f-63ae1f98813a"
sourceRef="holidayApprovedTask"
targetRef="approveEnd">
</sequenceFlow>

<serviceTask
id="sendRejectionMail"
name="发送驳回邮件"
flowable:class="org.flowable.SendRejectionMail">
</serviceTask>

<sequenceFlow
id="sequenceFlow-b390b442-2411-44a9-96eb-3c129d4c8408"
sourceRef="sendRejectionMail"
targetRef="rejectEnd">
</sequenceFlow>

<endEvent id="approveEnd"></endEvent>
<endEvent id="rejectEnd"></endEvent>

<sequenceFlow
id="sequenceFlow-6f21d3e2-2e49-4b00-9aae-0b71d861a177"
name="驳回"
sourceRef="decision"
targetRef="sendRejectionMail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${!approved}]]>
</conditionExpression>
</sequenceFlow>

<sequenceFlow
id="sequenceFlow-2470619b-fa36-440b-a231-b7bd226c23bb"
name="通过"
sourceRef="decision"
targetRef="externalSystemCall">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${approved}]]>
</conditionExpression>
</sequenceFlow>
</process>

<bpmndi:BPMNDiagram id="BPMNDiagram_holidayRequest">
<bpmndi:BPMNPlane
bpmnElement="holidayRequest"
id="BPMNPlane_holidayRequest">

<bpmndi:BPMNShape
bpmnElement="startEvent"
id="BPMNShape_startEvent">
<omgdc:Bounds
height="30.0"
width="30.0"
x="0.0"
y="95.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="approveTask"
id="BPMNShape_approveTask">
<omgdc:Bounds
height="60.0"
width="100.0"
x="80.0"
y="80.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="decision"
id="BPMNShape_decision">
<omgdc:Bounds
height="40.0"
width="40.0"
x="230.0"
y="90.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="externalSystemCall"
id="BPMNShape_externalSystemCall">
<omgdc:Bounds
height="60.0"
width="100.0"
x="320.0"
y="1.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="holidayApprovedTask"
id="BPMNShape_holidayApprovedTask">
<omgdc:Bounds
height="60.0"
width="100.0"
x="470.0"
y="1.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="sendRejectionMail"
id="BPMNShape_sendRejectionMail">
<omgdc:Bounds
height="60.0"
width="100.0"
x="320.0"
y="160.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="approveEnd"
id="BPMNShape_approveEnd">
<omgdc:Bounds
height="28.0"
width="28.0"
x="620.0"
y="16.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNShape
bpmnElement="rejectEnd"
id="BPMNShape_rejectEnd">
<omgdc:Bounds
height="28.0"
width="28.0"
x="505.0"
y="175.0">
</omgdc:Bounds>
</bpmndi:BPMNShape>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-2470619b-fa36-440b-a231-b7bd226c23bb"
id="BPMNEdge_sequenceFlow-2470619b-fa36-440b-a231-b7bd226c23bb">
<omgdi:waypoint x="250.0" y="90.0"></omgdi:waypoint>
<omgdi:waypoint x="250.0" y="31.0"></omgdi:waypoint>
<omgdi:waypoint
x="319.99999999999284"
y="31.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-3fa7af74-df9a-4d3a-b0eb-10b20e7dc202"
id="BPMNEdge_sequenceFlow-3fa7af74-df9a-4d3a-b0eb-10b20e7dc202">
<omgdi:waypoint
x="29.949998618355412"
y="110.0">
</omgdi:waypoint>
<omgdi:waypoint x="80.0" y="110.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-6232f578-3975-4d7e-81f2-43b242d33ebd"
id="BPMNEdge_sequenceFlow-6232f578-3975-4d7e-81f2-43b242d33ebd">
<omgdi:waypoint
x="419.95000000000005"
y="31.0">
</omgdi:waypoint>
<omgdi:waypoint x="470.0" y="31.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-b390b442-2411-44a9-96eb-3c129d4c8408"
id="BPMNEdge_sequenceFlow-b390b442-2411-44a9-96eb-3c129d4c8408">
<omgdi:waypoint
x="419.949999999997"
y="189.66442953020135">
</omgdi:waypoint>
<omgdi:waypoint
x="505.00030595247523"
y="189.0936221647077">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-d9127dc0-158f-4169-b61b-99226ffe8494"
id="BPMNEdge_sequenceFlow-d9127dc0-158f-4169-b61b-99226ffe8494">
<omgdi:waypoint
x="179.9499999999898"
y="110.0">
</omgdi:waypoint>
<omgdi:waypoint x="230.0" y="110.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-29fd3375-9bcb-42f2-bb6f-63ae1f98813a"
id="BPMNEdge_sequenceFlow-29fd3375-9bcb-42f2-bb6f-63ae1f98813a">
<omgdi:waypoint
x="569.9499999999988"
y="31.0">
</omgdi:waypoint>
<omgdi:waypoint x="582.0" y="31.0"></omgdi:waypoint>
<omgdi:waypoint x="582.0" y="31.0"></omgdi:waypoint>
<omgdi:waypoint
x="620.0025321198765"
y="30.268220825690175">
</omgdi:waypoint>
</bpmndi:BPMNEdge>

<bpmndi:BPMNEdge
bpmnElement="sequenceFlow-6f21d3e2-2e49-4b00-9aae-0b71d861a177"
id="BPMNEdge_sequenceFlow-6f21d3e2-2e49-4b00-9aae-0b71d861a177">
<omgdi:waypoint
x="250.0"
y="129.9375468164794">
</omgdi:waypoint>
<omgdi:waypoint x="250.0" y="190.0"></omgdi:waypoint>
<omgdi:waypoint
x="319.99999999997794"
y="190.0">
</omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

4.1.5 测试流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Autowired
private RuntimeService runtimeService;

@Autowired
private TaskService taskService;

@Autowired
private HistoryService historyService;

@Test
public void testFlow() {
// 发起请假
Map<String, Object> map = new HashMap<>();
map.put("day", 2);
map.put("studentUser", "小明");
ProcessInstance studentLeave =
runtimeService.startProcessInstanceByKey("StudentLeave", map);

Task task = taskService.createTaskQuery()
.processInstanceId(studentLeave.getId())
.singleResult();
taskService.complete(task.getId());

// 老师审批
List<Task> teacherTaskList = taskService.createTaskQuery()
.taskCandidateGroup("teacher")
.list();
Map<String, Object> teacherMap = new HashMap<>();
teacherMap.put("outcome", "通过");
for (Task teacherTask : teacherTaskList) {
taskService.complete(teacherTask.getId(), teacherMap);
}

// 校长审批
List<Task> principalTaskList = taskService.createTaskQuery()
.taskCandidateGroup("principal")
.list();
Map<String, Object> principalMap = new HashMap<>();
principalMap.put("outcome", "通过");
for (Task principalTask : principalTaskList) {
taskService.complete(principalTask.getId(), principalMap);
}

// 查看历史
List<HistoricActivityInstance> activities =
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(studentLeave.getId())
.finished()
.orderByHistoricActivityInstanceEndTime()
.asc()
.list();

for (HistoricActivityInstance activity : activities) {
System.out.println(activity.getActivityName());
}
}

当请假天数 day 为 2 时,打印结果如下:

1
2
3
4
5
6
7
8
9
开始
流程开始
请假申请
申请流程
老师审批
通过
判断是否大于2天
小于2天
结束

将请假天数 day 改为 5,打印结果如下:

1
2
3
4
5
6
7
8
9
10
11
开始
流程开始
请假申请
申请流程
老师审批
通过
判断是否大于2天
大于2天
校长审批
通过
结束

4.2 Flowable 常用配置项

以下配置清单基于本文使用的旧版本,主要用于帮助理解各模块的职责。请根据实际启用的引擎选择所需配置,不要不加区分地复制全部内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# ===================================================================
# Common Flowable Spring Boot Properties
# 通用 Flowable Spring Boot 参数
#
# This sample file is provided as a guideline. Do NOT copy it in its
# entirety to your own application.
# 本示例文件仅作为参考,请不要完整复制到自己的应用中。
# ===================================================================

# -------------------------------------------------------------------
# Core (Process) FlowableProperties
# 核心流程配置
# -------------------------------------------------------------------

# 是否自动部署流程定义
flowable.check-process-definitions=true

# 需要添加至引擎的自定义 MyBatis Mapper 的全限定名
flowable.custom-mybatis-mappers=

# 需要添加至引擎的自定义 MyBatis XML Mapper 路径
flowable.custom-mybatis-x-m-l-mappers=

# 如果数据库返回的元数据不正确,可设置用于检测或生成表的 Schema
flowable.database-schema=

# 数据库 Schema 更新策略
flowable.database-schema-update=true

# 是否使用数据库历史记录
flowable.db-history-used=true

# 自动部署名称
flowable.deployment-name=SpringBootAutoDeployment

# 历史记录级别
flowable.history-level=

# 自动部署时查找流程定义的目录
flowable.process-definition-location-prefix=classpath*:/processes/

# 在流程定义目录下扫描的文件后缀
flowable.process-definition-location-suffixes=**.bpmn20.xml,**.bpmn

# -------------------------------------------------------------------
# Process FlowableProcessProperties
# 流程引擎配置
# -------------------------------------------------------------------

# 流程定义缓存中保存流程定义的最大数量,-1 表示缓存所有流程定义
flowable.process.definition-cache-limit=-1

# 解析 BPMN XML 时是否执行额外的安全检查
# 参见:https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml
# 部分旧平台(如 JDK 6、JBoss)不支持 StaxSource,需要禁用此功能
flowable.process.enable-safe-xml=true

# 启动时加载 Process Servlet
flowable.process.servlet.load-on-startup=-1

# Process Servlet 名称
flowable.process.servlet.name=Flowable BPMN Rest API

# Process Servlet 的 Context Path
flowable.process.servlet.path=/process-api

# -------------------------------------------------------------------
# Process Async Executor
# 流程异步执行器
# -------------------------------------------------------------------

# 是否启用异步执行器
flowable.process.async-executor-activate=true

# 异步作业被执行器获取后的锁定时间(毫秒)
# 锁定期间,其他异步执行器不会尝试获取并锁定该任务
flowable.process.async.executor.async-job-lock-time-in-millis=300000

# 异步作业获取线程执行下一次查询前的默认等待时间(毫秒)
# 仅在本次没有取到新作业,或取到的作业很少时生效,默认 10 秒
flowable.process.async.executor.default-async-job-acquire-wait-time-in-millis=10000

# 队列已满时,异步作业获取线程执行下一次查询前的等待时间(毫秒)
# 默认值为 0,用于保持向后兼容
flowable.process.async.executor.default-queue-size-full-wait-time-in-millis=0

# 定时器作业获取线程执行下一次查询前的默认等待时间(毫秒)
# 仅在本次没有取到新作业,或取到的作业很少时生效,默认 10 秒
flowable.process.async.executor.default-timer-job-acquire-wait-time-in-millis=10000

# 单次查询获取的到期异步作业数量
# 默认值为 1,以降低乐观锁异常的可能性
flowable.process.async.executor.max-async-jobs-due-per-acquisition=1

# 异步执行失败后的重试等待时间(毫秒)
flowable.process.async.executor.retry-wait-time-in-millis=500

# 定时器作业被执行器获取后的锁定时间(毫秒)
flowable.process.async.executor.timer-lock-time-in-millis=300000

# -------------------------------------------------------------------
# CMMN FlowableCmmnProperties
# CMMN 配置
# -------------------------------------------------------------------

# 是否部署 CMMN 资源,默认 true
flowable.cmmn.deploy-resources=true

# CMMN 资源部署名称
flowable.cmmn.deployment-name=SpringBootAutoDeployment

# 解析 CMMN XML 时是否执行额外的安全检查
# 参见:https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml
# 部分旧平台不支持 StaxSource,需要禁用此功能
flowable.cmmn.enable-safe-xml=true

# 是否启用 CMMN 引擎
flowable.cmmn.enabled=true

# CMMN 资源目录
flowable.cmmn.resource-location=classpath*:/cases/

# 需要扫描的 CMMN 资源后缀
flowable.cmmn.resource-suffixes=**.cmmn,**.cmmn11,**.cmmn.xml,**.cmmn11.xml

# 启动时加载 CMMN Servlet
flowable.cmmn.servlet.load-on-startup=-1

# CMMN Servlet 名称
flowable.cmmn.servlet.name=Flowable CMMN Rest API

# CMMN Servlet 的 Context Path
flowable.cmmn.servlet.path=/cmmn-api

# -------------------------------------------------------------------
# CMMN Async Executor
# CMMN 异步执行器
# -------------------------------------------------------------------

# 是否启用 CMMN 异步执行器
flowable.cmmn.async-executor-activate=true

# 异步作业被执行器获取后的锁定时间(毫秒)
flowable.cmmn.async.executor.async-job-lock-time-in-millis=300000

# 异步作业获取线程执行下一次查询前的默认等待时间(毫秒)
flowable.cmmn.async.executor.default-async-job-acquire-wait-time-in-millis=10000

# 队列已满时,异步作业获取线程执行下一次查询前的等待时间(毫秒)
flowable.cmmn.async.executor.default-queue-size-full-wait-time-in-millis=0

# 定时器作业获取线程执行下一次查询前的默认等待时间(毫秒)
flowable.cmmn.async.executor.default-timer-job-acquire-wait-time-in-millis=1000

# 单次查询获取的到期异步作业数量
flowable.cmmn.async.executor.max-async-jobs-due-per-acquisition=1

# 异步执行失败后的重试等待时间(毫秒)
flowable.cmmn.async.executor.retry-wait-time-in-millis=500

# 定时器作业被执行器获取后的锁定时间(毫秒)
flowable.cmmn.async.executor.timer-lock-time-in-millis=300000

# -------------------------------------------------------------------
# Content FlowableContentProperties
# Content 引擎配置
# -------------------------------------------------------------------

# 是否启用 Content 引擎
flowable.content.enabled=true

# 启动时加载 Content Servlet
flowable.content.servlet.load-on-startup=-1

# Content Servlet 名称
flowable.content.servlet.name=Flowable Content Rest API

# Content Servlet 的 Context Path
flowable.content.servlet.path=/content-api

# 根目录不存在时是否自动创建
flowable.content.storage.create-root=true

# Content 文件(如任务附件或表单文件)的存储根目录
flowable.content.storage.root-folder=

# -------------------------------------------------------------------
# DMN FlowableDmnProperties
# DMN 引擎配置
# -------------------------------------------------------------------

# 是否部署 DMN 资源,默认 true
flowable.dmn.deploy-resources=true

# DMN 资源部署名称
flowable.dmn.deployment-name=SpringBootAutoDeployment

# 解析 DMN XML 时是否执行额外的安全检查
# 参见:https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml
# 部分旧平台不支持 StaxSource,需要禁用此功能
flowable.dmn.enable-safe-xml=true

# 是否启用 DMN 引擎
flowable.dmn.enabled=true

# 是否启用 DMN 历史记录
flowable.dmn.history-enabled=true

# DMN 资源目录
flowable.dmn.resource-location=classpath*:/dmn/

# 需要扫描的 DMN 资源后缀
flowable.dmn.resource-suffixes=**.dmn,**.dmn.xml,**.dmn11,**.dmn11.xml

# 启动时加载 DMN Servlet
flowable.dmn.servlet.load-on-startup=-1

# DMN Servlet 名称
flowable.dmn.servlet.name=Flowable DMN Rest API

# DMN Servlet 的 Context Path
flowable.dmn.servlet.path=/dmn-api

# 是否启用严格模式
# 设置为 false 可避免决策表命中策略检查导致失败;
# 如果检查发现错误,会返回出错前一刻的中间结果
flowable.dmn.strict-mode=true

# -------------------------------------------------------------------
# Form FlowableFormProperties
# Form 引擎配置
# -------------------------------------------------------------------

# 是否部署 Form 资源,默认 true
flowable.form.deploy-resources=true

# Form 资源部署名称
flowable.form.deployment-name=SpringBootAutoDeployment

# 是否启用 Form 引擎
flowable.form.enabled=true

# Form 资源目录
flowable.form.resource-location=classpath*:/forms/

# 需要扫描的 Form 资源后缀
flowable.form.resource-suffixes=**.form

# 启动时加载 Form Servlet
flowable.form.servlet.load-on-startup=-1

# Form Servlet 名称
flowable.form.servlet.name=Flowable Form Rest API

# Form Servlet 的 Context Path
flowable.form.servlet.path=/form-api

# -------------------------------------------------------------------
# IDM FlowableIdmProperties
# IDM 引擎配置
# -------------------------------------------------------------------

# 是否启用 IDM 引擎
flowable.idm.enabled=true

# 使用的密码编码类型
flowable.idm.password-encoder=

# 启动时加载 IDM Servlet
flowable.idm.servlet.load-on-startup=-1

# IDM Servlet 名称
flowable.idm.servlet.name=Flowable IDM Rest API

# IDM Servlet 的 Context Path
flowable.idm.servlet.path=/idm-api

# -------------------------------------------------------------------
# IDM LDAP FlowableLdapProperties
# IDM LDAP 配置
# -------------------------------------------------------------------

# 用户邮箱的属性名
flowable.idm.ldap.attribute.email=

# 用户名字的属性名
flowable.idm.ldap.attribute.first-name=

# 用户组 ID 的属性名
flowable.idm.ldap.attribute.group-id=

# 用户组名称的属性名
flowable.idm.ldap.attribute.group-name=

# 用户组类型的属性名
flowable.idm.ldap.attribute.group-type=

# 用户姓氏的属性名
flowable.idm.ldap.attribute.last-name=

# 用户 ID 的属性名
flowable.idm.ldap.attribute.user-id=

# 查找用户与组的基础 DN(Distinguished Name)
flowable.idm.ldap.base-dn=

# LDAPGroupCache 大小
# 这是用于缓存用户和组的 LRU 缓存,可避免每次都查询 LDAP 系统
flowable.idm.ldap.cache.group-size=-1

# 没有专用 Setter 的 LDAP 连接参数
# 参见:http://docs.oracle.com/javase/tutorial/jndi/ldap/jndi.html
# 可用于配置连接池、安全设置等
flowable.idm.ldap.custom-connection-parameters=

# 是否启用 LDAP IDM 服务
flowable.idm.ldap.enabled=false

# 查找组的基础 DN
flowable.idm.ldap.group-base-dn=

# 初始化上下文工厂的类名
flowable.idm.ldap.initial-context-factory=com.sun.jndi.ldap.LdapCtxFactory

# 连接 LDAP 系统的密码
flowable.idm.ldap.password=

# LDAP 系统端口
flowable.idm.ldap.port=-1

# 查询所有组使用的语句
flowable.idm.ldap.query.all-groups=

# 查询所有用户使用的语句
flowable.idm.ldap.query.all-users=

# 根据用户查询所属组使用的语句
flowable.idm.ldap.query.groups-for-user=

# 根据全名模糊查询用户使用的语句
flowable.idm.ldap.query.user-by-full-name-like=

# 根据用户 ID 查询用户使用的语句
flowable.idm.ldap.query.user-by-id=

# LDAP 查询超时时间(毫秒),0 表示一直等待
flowable.idm.ldap.search-time-limit=0

# 连接 LDAP 系统所用的 java.naming.security.authentication 参数
flowable.idm.ldap.security-authentication=simple

# LDAP 系统主机名,例如 ldap://localhost
flowable.idm.ldap.server=

# 连接 LDAP 系统的用户 ID
flowable.idm.ldap.user=

# 查找用户的基础 DN
flowable.idm.ldap.user-base-dn=

# -------------------------------------------------------------------
# Flowable Mail FlowableMailProperties
# 邮件服务配置
# -------------------------------------------------------------------

# 默认发件人地址
flowable.mail.server.default-from=flowable@localhost

# 邮件服务器地址
flowable.mail.server.host=localhost

# 邮件服务器登录密码
flowable.mail.server.password=

# 邮件服务器端口
flowable.mail.server.port=1025

# 是否使用 SSL/TLS 加密 SMTP 传输连接
flowable.mail.server.use-ssl=false

# 是否使用 STARTTLS 加密
flowable.mail.server.use-tls=false

# 邮件服务器登录用户名,为空表示不需要登录
flowable.mail.server.username=

# -------------------------------------------------------------------
# Actuator
# -------------------------------------------------------------------

# Flowable 端点响应的最大缓存时间
management.endpoint.flowable.cache.time-to-live=0ms

# 是否启用 Flowable Actuator 端点
management.endpoint.flowable.enabled=true

5. Flowable 常用 API

Flowable 整体通过 ProcessEngine 进行操作。无论使用什么框架接入流程,都需要通过 ProcessEngine 提供的服务来处理,因此可以把它理解为 Flowable 对外公开的门面。

类图如下:

Flowable ProcessEngine 类图

关系图如下:

Flowable 服务关系图

5.1 FormService

API 作用
formService.getStartFormKey() 获取启动表单的 Key
formService.getRenderedStartForm() 查询渲染后的启动表单

5.2 RepositoryService

RepositoryService 提供编辑和发布审批流程的 API,主要负责模型管理与流程定义。

5.2.1 查询模型与流程定义

1
2
3
4
5
6
7
8
9
10
11
// 创建相应类型的条件查询
repositoryService.createXXXQuery();

// 查询模型列表
repositoryService.createModelQuery().list();

// 查询流程定义列表
repositoryService.createProcessDefinitionQuery().list();

// 可以继续通过相应的 Key 条件判断目标是否存在
repositoryService.createXXXXQuery().XXXKey(XXX);

5.2.2 模型与部署操作

API 作用
repositoryService.getModel() 获取模型
repositoryService.saveModel() 保存模型
repositoryService.deleteModel() 删除模型
repositoryService.createDeployment().deploy() 部署模型
repositoryService.getModelEditorSource() 获取模型 JSON 数据的 UTF-8 字节数据
repositoryService.getModelEditorSourceExtra() 获取 PNG 格式的附加图像数据

原文作者在实际测试中发现,getModel() 获取模型时可能存在问题,使用时建议结合具体 Flowable 版本验证。

5.2.3 流程定义操作

API 作用
repositoryService.getProcessDefinition(processDefinitionId) 获取流程定义的详细信息
repositoryService.activateProcessDefinitionById() 激活流程定义
repositoryService.suspendProcessDefinitionById() 挂起流程定义
repositoryService.deleteDeployment() 删除部署及相关流程定义
repositoryService.getProcessDiagram() 获取流程定义图片流
repositoryService.getResourceAsStream() 获取流程定义 XML 流
repositoryService.getBpmnModel(processDefinitionId) 获取 BPMN 模型对象,可用于绘制当前流程图

5.2.4 流程定义授权

API 作用
repositoryService.getIdentityLinksForProcessDefinition() 获取流程定义授权列表
repositoryService.addCandidateStarterGroup() 新增候选启动组授权
repositoryService.addCandidateStarterUser() 新增候选启动用户授权
repositoryService.deleteCandidateStarterGroup() 删除候选启动组授权
repositoryService.deleteCandidateStarterUser() 删除候选启动用户授权

5.3 RuntimeService

RuntimeService 用于处理正在运行的流程实例。

API 作用
runtimeService.createProcessInstanceBuilder().start() 发起流程
runtimeService.deleteProcessInstance() 删除正在运行的流程实例
runtimeService.suspendProcessInstanceById() 挂起流程实例
runtimeService.activateProcessInstanceById() 激活流程实例
runtimeService.getVariables(processInstanceId) 获取流程变量,也就是表单中填写的值
runtimeService.getActiveActivityIds(processInstanceId) 获取当前活动节点 ID,可用于绘制流程当前状态

终止流程可以使用流程状态变更构造器:

1
2
3
runtimeService.createChangeActivityStateBuilder()
.moveExecutionsToSingleActivityId(executionIds, endId)
.changeState();

5.4 HistoryService

用户发起审批后会生成流程实例。HistoryService 用于处理历史流程实例,其中既包括已完成的流程,也包括尚未完成但已经产生历史数据的流程。

API 作用
historyService.createHistoricProcessInstanceQuery().list() 查询历史流程实例列表,包括未完成的流程实例
historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult() 根据 ID 查询单个历史流程实例
historyService.deleteHistoricProcessInstance() 删除历史流程实例
historyService.deleteHistoricTaskInstance(taskId) 删除历史任务实例
historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list() 查询流程实例的历史节点列表,可用于绘制流程执行轨迹

遍历历史流程实例后,可以结合历史变量查询接口获取流程中的表单信息。

如果要处理正在运行的流程实例,请使用 RuntimeService。

5.5 TaskService

TaskService 用于处理流程实例中各个用户任务节点的审批。

5.5.1 任务流转与审批

API 作用
taskService.createTaskQuery().list() 查询待办任务列表
taskService.createTaskQuery().taskId(taskId).singleResult() 查询待办任务详情
taskService.saveTask(task) 修改并保存任务
taskService.setAssignee() 设置审批人
taskService.addComment() 添加审批备注
taskService.complete(taskId) 完成当前任务
taskService.getProcessInstanceComments(processInstanceId) 查看流程实例的审批人与审批意见
taskService.delegateTask(taskId, delegateUserId) 委派任务
taskService.claim(taskId, userId) 认领任务
taskService.unclaim(taskId) 取消认领
taskService.complete(taskId, completeVariables) 携带流程变量完成任务

5.5.2 任务授权

API 作用
taskService.addGroupIdentityLink() 新增用户组任务授权
taskService.addUserIdentityLink() 新增用户任务授权
taskService.deleteGroupIdentityLink() 删除用户组任务授权
taskService.deleteUserIdentityLink() 删除用户任务授权

5.6 ManagementService

ManagementService 主要用于执行自定义命令:

1
managementService.executeCommand(new ClassA());

ClassA 需要实现 Command 接口并重写 execute 方法。在自定义命令中,可以通过以下方式获取 RepositoryService:

1
2
3
4
5
ProcessEngineConfiguration processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);

RepositoryService repositoryService =
processEngineConfiguration.getRepositoryService();

也可以获取流程定义实体管理器:

1
2
3
4
5
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);

ProcessDefinitionEntityManager processDefinitionEntityManager =
processEngineConfiguration.getProcessDefinitionEntityManager();

ProcessDefinitionEntityManager 提供 findById、findLatestProcessDefinitionByKey、findLatestProcessDefinitionByKeyAndTenantId 等方法。

5.7 IdentityService

IdentityService 用于获取和保存身份信息。下面是两个常见查询:

API 作用
identityService.createUserQuery().userId(userId).singleResult() 获取审批用户的详细信息
identityService.createGroupQuery().groupId(groupId).singleResult() 获取审批组的详细信息