🧩 工作流 Workflow —— 将复杂业务流程自动化的强大引擎

​ 在软件系统中,当我们需要管理对象的状态变化时,状态机是一种十分有效的建模方式:它把“状态 + 事件 → 新状态”的规则写得清清楚楚,让大量 if-else 逻辑变得可控、可维护。

​ 但随着业务规模扩大、流程参与者增多、规则不断细化,仅靠状态机已经难以覆盖所有场景。此时,一种能够描述业务步骤、条件分支、人工审批、系统任务,并支持可视化配置的机制显得尤为重要——这就是 工作流(Workflow)

​ 工作流的出现,正是为了解决复杂业务流程的可视化建模、规范流转与自动执行问题。它能够让系统根据业务规则像“流水线”一样自动推进,使流程标准化、透明化与可控化。

一、为什么需要工作流?🤔

在一个公司中,随处可见这样的流程:

  • 请假需要依次经过多级领导审批
  • 报销/采购可能涉及预算审核、合同审核、财务支付
  • 订单处理包含风控校验、库存锁定、拣货、发货、签收等步骤
  • 内容审核经过自动检测、人工审核、复审、发布等环节

这些流程的共同特点是:

  1. 参与者多样:既有系统任务,也有人为审批
  2. 流程具有条件分支:根据金额、类型、权限走不同路径
  3. 可能存在并行执行:例如多人会签
  4. 涉及超时、催办、撤销 等操作
  5. 流程需要动态调整:不能每改一次流程就改代码、发版

如果尝试用代码硬写 if-else 或状态机来实现,不仅容易混乱、难以扩展,也不利于频繁变更。因此,一种能够 通过“图”建模流程并自动执行 的机制成为必然选择。

二、什么是工作流?🔍

工作流(Workflow) 是一种用于 描述、执行和管理业务流程 的模型与引擎。

它的目标是把业务中的每一步抽象为清晰的节点,通过规则驱动节点之间的流转,使复杂流程能够自动化、结构化地执行。

一个典型的工作流由以下元素组成:

要素 描述
节点(Node) 流程中的一个步骤,可能是人工任务、系统任务、开始/结束节点等
流转(Flow) 节点之间的连接关系,决定“下一步去哪”
条件(Condition) 用于判断流程分支,如“天数 > 3 走经理审批”
参与者(Assignee) 指定负责某步骤的人或角色
定时器(Timer) 处理超时事件,如提醒或自动流转
表单与上下文(Form & Context) 流程相关的数据载体
历史记录(History) 用于审计与流程追踪

工作流的标准建模语言是 BPMN 2.0,几乎所有成熟引擎都支持,例如:

  • Flowable
  • Activiti
  • Camunda
  • 各种基于 BPMN 的企业级工作流产品

三、工作流能解决哪些状态机难以处理的需求?🆚

状态机非常适合处理“对象生命周期”,例如:

  • 订单状态:CREATED → PAID → SHIPPED → DELIVERED
  • 账号状态:NEW → ACTIVE → SUSPENDED → CLOSED

一旦需要处理以下这些需求,状态机就开始吃力:

1. 多人审批流程

  • 请假、报销、采购、合同审核等
  • 需要多级审批,有时还会加签、会签、转交

状态机本身不关心“谁”来操作某个状态,而工作流天然支持“参与者”这一概念。


2. 动态变化的流程

  • 运营说:以后金额超过 3000 就要多加一级审批
  • 法务说:合同类型 A 走流程 X,合同类型 B 走流程 Y

如果所有规则写死在代码里,一次流程变更就是一次开发+测试+发版;
而在工作流系统中,只需在流程设计器中调整图即可,无需改业务代码。


3. 并行任务

  • 内容上线前需要 内容团队法务团队 同时审核
  • 招聘流程中背景调查与笔试可并行进行

状态机本质结构偏线性,不擅长表达“同时做两件事”;
而工作流有原生的并行网关(Parallel Gateway)来表示“并发执行”。


4. 超时处理与催办

  • 48 小时内不审批自动提醒
  • 7 天未处理自动驳回或升级

状态机没有时间维度,超时往往只能额外写定时任务;
工作流引擎通常直接内置定时器节点(Timer),可以在流程图中表现出来。


5. 可视化与可追踪性

业务人员不看代码,他们需要:

  • 清晰的流程图
  • 当前流程所处位置
  • 历史审批记录
  • 流程变更的版本管理

这些是工作流系统的基础能力,也是状态机难以直接提供的。

状态机非常适合处理“对象生命周期”,例如:

  • 订单状态:CREATED → PAID → SHIPPED → DELIVERED
  • 账号状态:NEW → ACTIVE → SUSPENDED → CLOSED

一旦需要处理以下这些需求,状态机就开始吃力:

1. 多人审批流程

  • 请假、报销、采购、合同审核等
  • 需要多级审批,有时还会加签、会签、转交

状态机本身不关心“谁”来操作某个状态,而工作流天然支持“参与者”这一概念。


2. 动态变化的流程

  • 运营说:以后金额超过 3000 就要多加一级审批
  • 法务说:合同类型 A 走流程 X,合同类型 B 走流程 Y

如果所有规则写死在代码里,一次流程变更就是一次开发+测试+发版;
而在工作流系统中,只需在流程设计器中调整图即可,无需改业务代码。


3. 并行任务

  • 内容上线前需要 内容团队法务团队 同时审核
  • 招聘流程中背景调查与笔试可并行进行

状态机本质结构偏线性,不擅长表达“同时做两件事”;
而工作流有原生的并行网关(Parallel Gateway)来表示“并发执行”。


4. 超时处理与催办

  • 48 小时内不审批自动提醒
  • 7 天未处理自动驳回或升级

状态机没有时间维度,超时往往只能额外写定时任务;
工作流引擎通常直接内置定时器节点(Timer),可以在流程图中表现出来。


5. 可视化与可追踪性

业务人员不看代码,他们需要:

  • 清晰的流程图
  • 当前流程所处位置
  • 历史审批记录
  • 流程变更的版本管理

这些是工作流系统的基础能力,也是状态机难以直接提供的。

四、一个典型的工作流流程图示例:请假审批🌿

先看一下一个简单的请假审批流程:

pZuZKpT.png

可以看到,这个流程包含:

  • 开始 / 结束 节点
  • 用户任务(员工提交、领导审批、经理审批、HR 备案)
  • 条件网关(根据请假天数选择不同路径)

在 BPMN 里,这会对应一张带多种节点的流程图:

  • 圆形:开始事件 / 结束事件
  • 矩形:任务节点(User Task)
  • 菱形:网关(条件判断)

五、BPMN 2.0 简单示例(XML 片段)

下面是一个高度简化的 BPMN 2.0 流程定义(仅示意结构,实际项目可按引擎要求完善):

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
<?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"
targetNamespace="http://example.com/workflow">

<process id="leave_process" name="请假流程" isExecutable="true">

<!-- 开始事件 -->
<startEvent id="startEvent" name="开始">
<outgoing>flow_start_to_apply</outgoing>
</startEvent>

<!-- 员工填写申请 -->
<userTask id="task_apply" name="填写请假单" activiti:assignee="${applicant}">
<incoming>flow_start_to_apply</incoming>
<outgoing>flow_apply_to_leader</outgoing>
</userTask>

<!-- 直属领导审批 -->
<userTask id="task_leader_approve" name="直属领导审批" activiti:assignee="${leader}">
<incoming>flow_apply_to_leader</incoming>
<outgoing>flow_leader_to_gateway</outgoing>
</userTask>

<!-- 判断请假天数是否 > 3 -->
<exclusiveGateway id="gateway_days_check" name="天数判断">
<incoming>flow_leader_to_gateway</incoming>
<outgoing>flow_gateway_to_manager</outgoing>
<outgoing>flow_gateway_to_hr</outgoing>
</exclusiveGateway>

<!-- 部门经理审批 -->
<userTask id="task_manager_approve" name="部门经理审批"
activiti:assignee="${manager}">
<incoming>flow_gateway_to_manager</incoming>
<outgoing>flow_manager_to_hr</outgoing>
</userTask>

<!-- HR 备案 -->
<userTask id="task_hr_record" name="HR 备案" activiti:assignee="hr">
<incoming>flow_gateway_to_hr</incoming>
<incoming>flow_manager_to_hr</incoming>
<outgoing>flow_hr_to_end</outgoing>
</userTask>

<!-- 结束事件 -->
<endEvent id="endEvent" name="结束">
<incoming>flow_hr_to_end</incoming>
</endEvent>

<!-- 顺序流 -->
<sequenceFlow id="flow_start_to_apply" sourceRef="startEvent" targetRef="task_apply"/>
<sequenceFlow id="flow_apply_to_leader" sourceRef="task_apply" targetRef="task_leader_approve"/>
<sequenceFlow id="flow_leader_to_gateway" sourceRef="task_leader_approve" targetRef="gateway_days_check"/>
<sequenceFlow id="flow_gateway_to_manager" sourceRef="gateway_days_check" targetRef="task_manager_approve">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days > 3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow_gateway_to_hr" sourceRef="gateway_days_check" targetRef="task_hr_record">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <= 3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow_manager_to_hr" sourceRef="task_manager_approve" targetRef="task_hr_record"/>
<sequenceFlow id="flow_hr_to_end" sourceRef="task_hr_record" targetRef="endEvent"/>

</process>
</definitions>

实际使用时,可在流程设计器(如 Flowable Modeler)里画图,它会自动帮你生成相应的 BPMN XML。

六、Java + Flowable 工作流引擎快速接入示例 🛠

下面以 Spring Boot + Flowable 为例,演示一个极简工作流接入流程,基于上面的请假审批场景。

1.引入 Maven 依赖

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
<dependencies>
<!-- Flowable Spring Boot 启动器 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>6.8.0</version>
</dependency>

<!-- Spring Web / Data / 其他常规依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- 数据库驱动(以 H2/MySQL 为例) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

版本号可根据实际使用的 Flowable/Spring Boot 版本调整。

2.放置流程定义文件

将刚才的 leave_process 流程定义保存为 leave_process.bpmn20.xml,放到:

1
src/main/resources/processes/leave_process.bpmn20.xml

Flowable 默认会扫描 resources/processes 目录并自动部署 BPMN 流程。

3.配置 application.yml(可选)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
datasource:
url: jdbc:h2:mem:flowable-db;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true

flowable:
check-process-definitions: true
database-schema-update: true
async-executor-activate: false

4.启动流程实例(发起请假)

Service 代码示例:

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
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class LeaveWorkflowService {

private final RuntimeService runtimeService;

public LeaveWorkflowService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}

/**
* 发起请假流程
*/
public String startLeaveProcess(String applicant,
String leader,
String manager,
int days) {

Map<String, Object> vars = new HashMap<>();
vars.put("applicant", applicant);
vars.put("leader", leader);
vars.put("manager", manager);
vars.put("days", days);

// leave_process 对应 BPMN 中的 process id
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("leave_process", vars);

return processInstance.getId();
}
}

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
import org.flowable.engine.TaskService;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TaskQueryService {

private final TaskService taskService;

public TaskQueryService(TaskService taskService) {
this.taskService = taskService;
}

/**
* 查询某个用户的待办任务
*/
public List<Task> getTodoTasks(String assignee) {
return taskService.createTaskQuery()
.taskAssignee(assignee)
.orderByTaskCreateTime()
.desc()
.list();
}
}

6.完成任务(审批通过 / 拒绝)

审批人处理任务时,只需调用 complete 即可,让流程继续往下流转:

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
import org.flowable.engine.TaskService;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class ApproveService {

private final TaskService taskService;

public ApproveService(TaskService taskService) {
this.taskService = taskService;
}

/**
* 审批任务:同意 / 拒绝
*/
public void approve(String taskId, boolean approved) {
Map<String, Object> vars = new HashMap<>();
vars.put("approved", approved);
taskService.complete(taskId, vars);
}
}

在 BPMN 流程中,可以在顺序流或网关处使用 ${approved} 条件表达式, 决定接下来是“流程继续”还是“流程结束/驳回”。

七、工作流与状态机的关系:互补,而非替代🌉

总结一下两者的核心区别与配合方式:

维度 状态机 工作流
关注点 对象状态变化 业务流程流转
适用场景 生命周期管理,如订单状态 人机协作、审批、复杂编排
可视化 较弱 强(BPMN 流程图)
动态变更 一般需要改代码 通常改流程图即可
是否包含参与者概念 通常没有 原生支持

一个成熟系统往往会 同时使用两者

  • 用状态机控制实体的“状态是否合法变化”
  • 用工作流编排“围绕该实体的完整业务流程”

八、结语🧠

工作流的本质不是为了“炫技”,而是把复杂、混乱、隐性的业务流程,沉淀为清晰、可视、可维护的模型:

  • 让业务人员能看得懂整个流程
  • 让开发能快速定位问题与扩展节点
  • 让运维可以通过配置而不是代码来调整规则

当你面对的是 固定的状态演进,选择状态机;
当你面对的是 复杂且动态的业务流程,选择工作流。

好的流程设计,不仅能减少错误,更能成为业务高效运转的一部分基础设施。