工作流介绍
1.1 为什么使用工作流
在开发过程中,我们经常会遇到各类审批流程需求。一个典型的请假审批可能包含“开始 -> 员工申请 -> 领导审批 -> 老板审批 -> 结束”等多个环节。如果单纯使用数据库字段(如 status: 0->开始, 1->申请...)和代码逻辑来硬编码流转过程,虽然能够实现功能,但会导致业务代码异常复杂。如果再考虑驳回到上一节点等操作,维护成本将急剧上升。为了解决代码硬编码带来的高成本和低可维护性问题,工作流引擎应运而生。
1.2 工作流是什么
工作流是对业务步骤与规则进行抽象和概括性描述的一种方法。它使用特定的建模语言为业务流程建模,使其能在计算机上运行,并由计算机进行推动和计算。本质上,工作流是一个复杂版本的状态机。
简单状态
下图展示了工作流退化为简单状态机的例子:角色“路人乙”的状态在“站起来 -> 走起来 -> 跑起来 -> 慢下来 -> 站起来”之间循环。实现这种简单的状态切换,仅需一个字段记录当前状态即可。

然而,当状态维度增加、流转条件变得复杂时,单纯用字段记录状态的方式就会捉襟见肘。
复杂状态
下图描述了“路人甲”发工资后的决策流程,其选择取决于余额大小。这个流程引入了条件判断(网关)和并行任务,已经无法用一个简单的状态字段来清晰描述和管理。

工作流的核心价值在于解耦业务宏观流程与微观逻辑。它允许熟悉业务的人通过可视化工具设计整体流程,而开发人员只需关注单个节点(如一个审批任务或一个自动调用)的具体实现。这就像建造体育场,架构师负责整体蓝图,而工人只需专注于砌好面前的砖。
1.3 工作流不能解决的问题
工作流适用于有明确前后关联和顺序的事务。对于彼此独立、没有流转顺序的任务,工作流并非最佳选择。

例如,“路人丙”每天要完成学习、谈恋爱、玩游戏这三件独立的事。它们之间没有必然的顺序,但每项任务的完成状态共同决定了“今日任务”是否完结。这类场景更适合使用CMMN(案例管理模型和符号)来建模。本文主要介绍基于BPMN协议的工作流。
BPMN2.0协议
2.1 什么是BPMN2.0协议

BPMN(业务流程模型与符号)2.0是一种国际通用的业务流程建模语言标准。它就像业务领域的“普通话”,既能让人直观理解,也能被计算机准确解析和执行,极大地降低了业务与技术之间的沟通成本。
2.2 BPMN2.0协议元素介绍
协议中的元素主要分为四大类:事件(Event)、任务(Task)、连线(Sequence Flow)、网关(Gateway)。一个有效的流程必须包含一个开始事件和至少一个结束事件。网关用于控制流程的分支与合并逻辑,各类任务节点则承担具体的业务操作,所有节点通过连线连接。
下面简要介绍几种核心元素的作用:
网关:
- 互斥网关 (Exclusive Gateway):又称排他网关,其行为类似于
if...else if...else,在多个出口路径中,有且仅有一条满足条件的路径会被执行。
- 并行网关 (Parallel Gateway):其所有出口路径都会同时被执行,类似于开启多个线程并行处理任务,通常需要成对使用以开启和同步并行分支。
- 包容性网关 (Inclusive Gateway):只要满足条件的出口路径都会被执行,类似于多个独立的
if 判断,条件之间并非互斥。
任务:
所有BPMN任务都源自一个抽象任务,其核心行为是:1) 当流程到达该任务时做什么? 2) 当任务收到“信号(Trigger)”时,是否能继续流转?
- 人工任务 (User Task):最常见的任务类型,通常需要人工干预。它包含“签收人(Assignee)”等变量,流程会在此等待指定人员处理完毕并触发完成信号后,才继续向下流转。
- 服务任务 (Service Task):自动执行的任务节点。到达该节点时,引擎会自动执行一段预定义逻辑(如计算、调用RPC或HTTP接口),执行完毕后自动流转。这在构建微服务架构的应用集成时非常有用。
- 接受任务 (Receive Task):一种被动等待的任务。到达该节点后,流程会暂停,不做任何操作,直到接收到一个外部触发信号才会继续。适用于需要人为判断后方可推进的阻塞点。
- 调用活动 (Call Activity):类似于程序中的函数调用,用于引用和执行另一个流程定义,实现流程的模块化和复用。
下图展示了一个覆盖上述节点的手机生产流程模型示例:

2.3 如何使用BPMN2.0协议
从使用者视角,主要关注三点:
- 流程设计:如何将业务逻辑转化为可视化的流程图(需要友好的绘图工具)。
- 流程驱动:如何通过API启动和推动流程实例。
- 任务处理:引擎如何通知我当前需要处理什么任务(需要清晰的事件或查询机制)。
下图概括了流程图从设计到运行的生命周期:

Flowable简介
3.1 Flowable是什么
Flowable是一个基于BPMN 2.0协议的、用Java编写的轻量级开源业务流程引擎。它可用于部署BPMN 2.0流程定义,创建和管理流程实例,并访问运行中或历史的数据。Flowable可以嵌入到任何Java应用中,也可以作为独立的服务运行。
3.2 Flowable与Activiti
Flowable项目最初是从Activiti 6.0.0.Beta4分支衍生而来。目前,Flowable修复了Activiti 6中的许多问题,并进行了大量功能和性能优化,可以实现从Activiti到Flowable的低成本迁移。
Flowable实战
假设需要为一个公司内部系统实现员工请假审批流程。我们将使用以下步骤实现:
- 画图:创建SpringBoot项目(
flowable-ui),启动Flowable UI设计器,在线绘制请假流程图并导出XML文件。
- 部署:创建另一个SpringBoot项目(
flowable),将XML文件部署到流程引擎中。
- 启动:启动SpringBoot应用。
- 流转:通过编写代码调用API,实现流程的启动、任务查询与审批。
4.1 本地安装,启动flowable-ui
1. 创建SpringBoot项目
项目结构如下图所示,用于承载Flowable UI设计器。

2. 关键代码与配置
主启动类 JavaSkillPointApplication.java:
package com.weige.javaskillpoint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JavaSkillPointApplication {
public static void main(String[] args) {
SpringApplication.run(JavaSkillPointApplication.class, args);
}
}
配置文件 application.yml:
server:
port: 8088
flowable:
idm:
app:
admin:
# 登录的用户名
user-id: admin
# 登录的密码
password: admin
# 用户的名字
first-name: wei
last-name: kai
spring:
# mysql连接信息
datasource:
# mysql8之后
driver-class-name: com.mysql.cj.jdbc.Driver
# mysql8之前
# driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://43.143.132.109:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
username: root
password: ******
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: true
注意:
&nullCatalogMeansCurrent=true 参数对于Flowable自动建表至关重要。
- 需提前在MySQL中创建名为
flowable 的数据库,项目启动时会自动创建所需表。
- 启动后,通过
http://localhost:8088 访问,使用配置的用户名(admin)和密码(admin)登录。
3. 项目依赖 (pom.xml)
关键依赖包括Flowable UI模块、SpringBoot Web、JPA及MySQL驱动。
<dependencies>
<!-- Springboot Web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- idm依赖提供身份认证 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
<version>6.7.1</version>
</dependency>
<!-- modeler绘制流程图 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
<version>6.7.1</version>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- flowable 引擎 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.1</version>
</dependency>
<!-- 其他依赖:lombok, test等 -->
</dependencies>
4. 启动与设计流程
启动项目后,登录Flowable UI,在“模型器”中创建新的BPMN模型,开始绘制“员工请假流程”。设计界面如下图所示:

请假流程核心节点设计如下:
- 开始事件 -> 请假申请(User Task):申请人变量为
${studentUser}。
- 领导审批(User Task):候选组为
a。
- 互斥网关:判断请假天数
${day} 是否大于2天。
- 老板审批(User Task):候选组为
b (当天数>2时流转至此)。
- 结束事件。
- 各审批节点均有“通过”和“驳回”两条输出连线,驳回时回到“请假申请”节点。
设计完成后,将模型导出为BPMN 2.0 XML文件(例如 leave.bpmn20.xml),供后续步骤使用。
4.2 创建flowable应用项目 部署并启动
1. 创建SpringBoot应用项目
此项目是实际运行流程的业务应用。

主启动类 JavaSkillPointApplication.java (同上,略)。
2. 部署流程定义
在 resources/ 目录下创建 processes/ 文件夹,将上一步导出的 leave.bpmn20.xml 文件放入其中。当应用启动时,SpringBoot会自动扫描并部署该文件夹下的所有流程定义。
3. 应用配置
配置文件 application.yml:
server:
port: 8081
spring:
datasource:
# 根据你的MySQL版本选择驱动
driver-class-name: com.mysql.jdbc.Driver # MySQL 5
# driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8
url: jdbc:mysql://43.143.132.109:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: ******
注意:数据库连接信息应与 flowable-ui 项目一致,确保它们操作同一数据库。
4. 编写流程操作测试代码
以下测试类展示了流程的完整操作API:
package com.weige.javaskillpoint;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@SpringBootTest
class JavaSkillPointApplicationTests {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
// 员工提交请假申请
@Test
void employeeApply() {
Map<String, Object> variables = new HashMap<>();
variables.put("day", 5); // 请假天数
variables.put("studentUser", "小明"); // 申请人
// 使用流程定义的Key“leave”启动一个流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", variables);
// 查询并完成申请人的第一个任务(提交申请)
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
taskService.complete(task.getId());
}
// 查询领导待审批任务
@Test
void queryLeaderTask() {
// 查询候选组为‘a’(领导组)的所有任务
List<Task> leaderTasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
for (Task task : leaderTasks) {
Map<String, Object> variables = taskService.getVariables(task.getId());
System.out.println("请假天数:" + variables.get("day") + ", 申请人:" + variables.get("studentUser"));
}
}
// 领导审批(通过)
@Test
void leaderApproval() {
List<Task> leaderTasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
Map<String, Object> approvalResult = new HashMap<>();
approvalResult.put("outcome", "通过"); // 审批结果变量,对应流程图中‘通过’连线的条件
for (Task task : leaderTasks) {
taskService.complete(task.getId(), approvalResult);
}
}
// 查询老板待审批任务
@Test
void queryBossTask() {
List<Task> bossTasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
for (Task task : bossTasks) {
Map<String, Object> variables = taskService.getVariables(task.getId());
System.out.println("请假天数:" + variables.get("day") + ", 申请人:" + variables.get("studentUser"));
}
}
// 老板审批(通过)
@Test
void bossApproval() {
List<Task> bossTasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
Map<String, Object> approvalResult = new HashMap<>();
approvalResult.put("outcome", "通过");
for (Task task : bossTasks) {
taskService.complete(task.getId(), approvalResult);
}
}
// 查询流程历史活动
@Test
void queryHistory() {
List<ProcessInstance> instances = runtimeService.createProcessInstanceQuery()
.processDefinitionKey("leave")
.orderByStartTime().desc().list();
if (CollectionUtils.isEmpty(instances)) {
System.out.println("暂无运行中的流程实例");
return;
}
// 获取最近一个流程实例的历史活动
List<HistoricActivityInstance> activities = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(instances.get(0).getId())
.finished()
.orderByHistoricActivityInstanceEndTime().desc()
.list();
activities.forEach(a -> System.out.println(
"活动:" + a.getActivityName() + ", 耗时:" + a.getDurationInMillis() + "ms"));
}
// 完整流程测试:请假2天(无需老板审批)
@Test
void testShortLeave() {
// 1. 员工申请
Map<String, Object> vars = new HashMap<>();
vars.put("day", 2);
vars.put("studentUser", "小明");
ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave", vars);
taskService.complete(taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult().getId());
// 2. 领导审批
Map<String, Object> outcome = new HashMap<>();
outcome.put("outcome", "通过");
taskService.createTaskQuery().taskCandidateGroup("a").list()
.forEach(task -> taskService.complete(task.getId(), outcome));
// 3. 查询历史(此时流程应已结束,因为天数<=2,直接走向结束)
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(pi.getId())
.finished()
.orderByHistoricActivityInstanceEndTime().asc()
.list()
.forEach(activity -> System.out.println(activity.getActivityName()));
}
}
4.3 启动流程代码详解
- 流程实例:部署后的流程定义相当于“蓝图”,
runtimeService.startProcessInstanceByKey("leave", variables) 则根据此蓝图创建一个具体的流程实例。variables 中的参数(如 day, studentUser)会传递给流程。
- 关键流程变量:
${studentUser}: 在“请假申请”节点中,通过 flowable:assignee="${studentUser}" 指定任务的办理人。
${day}: 用于互斥网关判断请假天数。
${outcome}: 用于审批节点的输出连线条件(“通过”或“驳回”)。
- 任务查询与完成:
taskService 提供了丰富的API来查询任务(如按候选用户组查询),以及 complete 方法来提交任务,推动流程向下一个节点流转。
4.4 Flowable数据库表说明
Flowable引擎运行时需要数据库支持,所有表名以 ACT_ 开头,第二部分是两字符标识符,表明表的用途:
- *`ACTRE
** (REpository): 存储静态信息,如流程定义(ACT_RE_PROCDEF)、部署单元(ACT_RE_DEPLOYMENT`)。
- *`ACTRU
** (RUntime): 存储运行时数据,如执行实例(ACT_RU_EXECUTION)、任务(ACT_RU_TASK)、变量(ACT_RU_VARIABLE`)。流程实例结束后,这些表的相应记录会被删除,以保持表体积小、性能高。
- *`ACTHI
** (HIstory): 存储历史数据,如已完成的任务(ACT_HI_TASKINST)、流程实例(ACT_HI_PROCINST)、变量(ACT_HI_VARINST`)。
- *`ACTID
** (IDentity): 存储身份信息,如用户(ACT_ID_USER)、组(ACT_ID_GROUP)、关系(ACT_ID_MEMBERSHIP`)。
- *`ACTGE
** (General): 通用数据,如字节数组资源(ACT_GE_BYTEARRAY)、属性(ACT_GE_PROPERTY`)。
启动 flowable-ui 和业务应用后,连接对应的MySQL数据库,即可查看这些自动创建的表结构及其中存储的数据。