Activiti7_BPMN工作流详解

O泡李华 7

Activiti 7 + BPMN 工作流详解

本章位置:第三阶段 Java 企业项目 + AI 助手
对应课程:Activiti(第一部分)
前置知识:SpringBoot、MyBatis、MySQL、事务、AOP、RESTful
后续衔接:Activiti 第二部分、淘车湾审批流程、流程图高亮
学习目标:从零理解工作流与 BPMN,掌握 Activiti 7 中流程定义、部署、流程实例、任务、候选人、流程变量、网关、历史记录和 SpringBoot 集成的核心思路。


一、先说结论:Activiti 到底是干什么的

Activiti 是一个:

Java 工作流 / BPMN 流程引擎

它适合解决:

请假审批
报销审批
合同审批
采购审批
订单审核
维修审批
服务单审核
入职审批
离职审批

这些业务的共同特点:

不是一次数据库 CRUD 就结束

而是要经过多个步骤

例如:

员工提交请假
↓
组长审批
↓
经理审批
↓
请假完成

如果我们完全手写:

status = 0
status = 1
status = 2
status = 3

业务一复杂:

代码会非常乱

Activiti 的作用就是:

把“业务流程”抽出来
交给流程引擎管理

二、什么是工作流

工作流:

Workflow

简单理解:

一件业务从开始到结束,要按照一定规则依次经过若干节点。

例如请假:

开始
↓
提交申请
↓
部门经理审批
↓
是否通过?
├─ 否 → 驳回
└─ 是 → 人事备案
          ↓
         结束

这就是:

工作流

三、为什么普通 CRUD 不够

假设请假表:

leave_request

字段:

id
user_id
reason
days
status

如果我们自己写:

status=0
待审批

status=1
经理通过

status=2
经理拒绝

status=3
人事通过

刚开始:

还能写

但业务变化:

请假 <= 3 天
只需要组长审批

请假 > 3 天
需要组长 + 经理审批

请假 > 7 天
还需要总经理审批

然后再增加:

撤回
转办
委派
加签
会签
抄送

代码会变成:

大量 if/else
大量 status 判断
大量重复流程代码

四、流程引擎解决什么

流程引擎把:

流程定义

和:

业务代码

分开。

业务代码只做:

提交申请
保存业务数据
完成任务

流程引擎负责:

当前走到哪个节点

下一步是谁审批

条件是否满足

流程是否结束

历史走过哪些节点

五、Activiti 是什么

Activiti 官方把它定位为:

轻量级
Java 中心
开源 BPMN 流程引擎

它支持:

BPMN 2.0

也就是说:

流程不是写死在 Java if/else 中

而是通过 BPMN 流程模型描述

六、BPMN 是什么

BPMN:

Business Process Model and Notation

中文:

业务流程模型与标记

简单理解:

一套标准化的流程图语言

七、普通流程图和 BPMN 的区别

普通流程图:

主要给人看

BPMN:

不仅给人看

还可以被流程引擎解析执行

八、BPMN 文件

通常:

xxx.bpmn20.xml

例如:

leave.bpmn20.xml

本质:

XML 文件

九、一个完整 BPMN 流程的核心元素

最基础:

Start Event
开始事件

User Task
用户任务

Service Task
服务任务

Gateway
网关

Sequence Flow
连线

End Event
结束事件

十、开始事件

表示:

流程开始

图形:

圆形

可以理解:

start

十一、结束事件

表示:

流程结束

十二、用户任务

User Task:

需要某个人处理的人工任务

例如:

经理审批

财务审批

管理员审核

十三、服务任务

Service Task:

系统自动执行

例如:

自动发送通知

自动更新订单状态

调用 Java 服务

十四、Sequence Flow

表示:

节点之间的连接关系

例如:

提交申请
↓
经理审批
↓
结束

十五、Gateway

网关:

控制流程分支

最常见:

Exclusive Gateway
排他网关

Parallel Gateway
并行网关

十六、排他网关

Exclusive Gateway:

多个分支
最终只走一个

类似 Java:

if (...) {

} else {

}

十七、请假案例

经理审批
↓
排他网关
├─ approved=true
│  ↓
│  人事备案
│
└─ approved=false
   ↓
   驳回

十八、并行网关

Parallel Gateway:

同时走多个分支

类似:

同时创建多个任务

例如合同:

法务审核
+
财务审核

两边:

同时进行

都完成后:

继续下一步

十九、Activiti 中最重要的概念

必须先记住:

Process Definition
流程定义

Deployment
部署

Process Instance
流程实例

Task
任务

Variable
流程变量

二十、流程定义

Process Definition:

流程模板

例如:

请假审批流程

定义:

开始
↓
经理审批
↓
结束

这只是:

模板

还不是某一次真实请假。


二十一、流程实例

Process Instance:

某一次真实运行的流程

例如:

张三请假 3 天

启动后:

产生一个流程实例

李四又请假:

又产生另一个流程实例

二十二、流程定义和实例类比

Java:

Class
↓
Object

Activiti:

ProcessDefinition
↓
ProcessInstance

可以类比:

流程定义 ≈ 类

流程实例 ≈ 对象

二十三、Deployment

Deployment:

把 BPMN 流程定义部署到流程引擎

流程引擎才知道:

这个流程存在

二十四、Task

流程走到:

人工审批节点

就产生:

Task

例如:

经理审批任务

二十五、任务是谁的

可以指定:

Assignee
办理人

例如:

manager01

二十六、Candidate User

候选人:

这个任务可以由多个用户中的任意一个领取

例如:

财务组有 3 个人

任务可以由:

finance01
finance02
finance03

任意一个:

claim

二十七、Candidate Group

候选组:

一个角色 / 用户组

例如:

finance

所有 finance 组成员:

都可以看到任务

二十八、Assignee 和 Candidate 区别

Assignee:

任务已经明确属于某个人

Candidate:

任务还没有确定具体办理人

二十九、Claim

候选人领取任务:

claim

领取后:

Candidate Task
↓
Assignee Task

三十、Complete

办理人审批完成:

complete task

流程引擎:

自动向下推进

三十一、流程变量

Process Variable:

流程运行过程中的数据

例如:

days = 5

approved = true

amount = 20000

网关可以根据变量:

决定走哪条路

三十二、请假变量

例如:

days

规则:

days <= 3
→ 部门经理

days > 3
→ 部门经理 + 总经理

三十三、审批结果变量

approved = true

网关:

通过

否则:

驳回

三十四、业务数据和流程变量不是一回事

业务数据:

leave_request 表

保存:

请假原因
请假开始时间
结束时间
申请人

流程变量:

Activiti 流程运行时使用

例如:

approved
days
managerId

三十五、不要把所有业务数据都塞流程变量

不推荐:

整张业务表所有字段
全部作为流程变量

原因:

数据重复
流程表膨胀
查询困难

推荐:

业务数据放业务表

流程变量只放流程判断真正需要的数据

三十六、Business Key

这是流程和业务数据连接的重要字段。

例如:

leave_request.id = 1001

启动流程时:

businessKey = "1001"

这样:

流程实例

和:

请假单

建立关联。


三十七、为什么 Business Key 很重要

以后你查询:

这个订单对应哪个流程?

就可以通过:

businessKey

关联。


三十八、推荐业务关联方式

例如订单:

order.id = 888

启动:

processDefinitionKey = orderApproval

businessKey = 888

三十九、不要用 ProcessInstanceId 代替业务主键

业务表可以保存:

process_instance_id

但业务主键仍应该:

独立存在

四十、Activiti 核心对象 ProcessEngine

ProcessEngine:

流程引擎

可以理解:

Activiti 的总入口

四十一、ProcessEngine 下面的重要 Service

经典核心 API 中经常使用:

RepositoryService

RuntimeService

TaskService

HistoryService

ManagementService

还可能接触:

IdentityService

Activiti 7 还引入了:

Runtime API

等更高层 API。


四十二、RepositoryService

主要负责:

流程定义

流程部署

例如:

部署 BPMN

查询 ProcessDefinition

删除 Deployment

四十三、RuntimeService

主要负责:

运行中的流程

例如:

启动流程实例

查询 ProcessInstance

设置流程变量

四十四、TaskService

主要负责:

用户任务

例如:

查询任务

领取任务

设置办理人

完成任务

四十五、HistoryService

主要负责:

历史数据

例如:

已经结束的流程

已经完成的任务

走过哪些节点

四十六、ManagementService

主要偏:

流程引擎管理

例如:

Job

数据库表

引擎管理

初学:

先了解

四十七、IdentityService

传统 Activiti 中用于:

用户

用户组

但企业项目往往已经有:

自己的用户系统
自己的 RBAC

所以通常不会让 Activiti:

完全接管用户体系

四十八、企业项目常见做法

你的系统已经有:

sys_user

sys_role

流程中:

assignee

可以直接使用:

userId
username

或者业务用户唯一标识。


四十九、Activiti 7 的 API 思路

Activiti 7 在 Core 上提供:

更面向业务应用的 Runtime API

例如:

ProcessRuntime

TaskRuntime

官方 7.x 开发指南会重点介绍:

TaskRuntime
ProcessRuntime

五十、为什么网上教程 API 不一样

你会看到两类教程:

第一类:

RepositoryService
RuntimeService
TaskService
HistoryService

第二类:

ProcessRuntime
TaskRuntime

因为:

Activiti 7 在经典 Engine API 之外
引入了新的 Runtime API

所以不要看到不同写法就认为:

其中一个一定是错的

五十一、学习顺序建议

先掌握:

经典核心概念

因为它最容易理解:

部署
实例
任务
历史

再看:

Activiti 7 Runtime API

五十二、版本兼容提醒

Activiti 7 官方 Core 入门材料仍然以:

Spring Boot 2

示例讲:

activiti-spring-boot-starter

因此如果你的新项目使用:

Spring Boot 3.x

不要直接复制旧教程依赖后:

强行升级版本

五十三、为什么 SpringBoot 3 需要特别注意

SpringBoot 3 大规模迁移到:

Jakarta

旧生态很多库仍基于:

javax
Spring Boot 2

因此:

工作流引擎版本
SpringBoot 版本
JDK 版本

必须:

一起确认兼容

五十四、学习阶段策略

本章重点:

Activiti 核心原理
BPMN
核心 API

真正创建项目时:

先确定 Activiti 版本
再确定兼容的 SpringBoot 版本

不要反过来:

随便 SpringBoot 最新版
再硬塞 Activiti

五十五、Activiti 数据库表

Activiti 启动后会创建很多:

ACT_*

开头的表。


五十六、表名前缀规律

非常重要:

ACT_RE_
流程仓库 Repository

ACT_RU_
运行时 Runtime

ACT_HI_
历史 History

ACT_GE_
通用 General

五十七、ACT_RE_

保存:

流程定义

部署信息

五十八、ACT_RU_

保存:

当前运行中的流程数据

例如:

当前任务

执行实例

变量

五十九、ACT_HI_

保存:

历史数据

例如:

历史流程

历史任务

历史活动节点

六十、ACT_GE_

保存:

通用数据

例如:

资源
属性

六十一、常见核心表

不同 Activiti 7 版本可能略有差异。

学习时重点认识:

ACT_RE_DEPLOYMENT

ACT_RE_PROCDEF

ACT_RU_EXECUTION

ACT_RU_TASK

ACT_RU_VARIABLE

ACT_HI_PROCINST

ACT_HI_TASKINST

ACT_HI_ACTINST

六十二、ACT_RE_DEPLOYMENT

保存:

部署记录

六十三、ACT_RE_PROCDEF

保存:

流程定义

例如:

leave:1

leave:2

leave:3

六十四、流程定义有版本

第一次部署:

version=1

修改 BPMN 后再次部署:

version=2

旧流程实例:

继续按旧版本运行

新流程:

通常启动最新版本

六十五、为什么流程定义版本化很重要

企业流程不能:

改一次 BPMN
把正在审批中的流程直接弄乱

流程引擎通过:

Definition Version

管理。


六十六、ACT_RU_EXECUTION

保存:

流程执行实例

并行流程中可能:

一条流程实例
多个 execution

所以:

ProcessInstance

和:

Execution

概念不完全一样。


六十七、ACT_RU_TASK

当前:

待办用户任务

通常能看到:

task id

task name

assignee

process instance id

六十八、任务完成后去哪

完成 Task 后:

通常从 ACT_RU_TASK 删除

因为它不再:

运行中

历史数据:

进入 ACT_HI_TASKINST

六十九、这就是 Runtime 和 History 的区别

Runtime:

现在正在发生什么

History:

以前发生过什么

七十、ACT_RU_VARIABLE

保存:

运行时流程变量

七十一、ACT_HI_PROCINST

保存:

历史流程实例

包括:

已结束

的流程。


七十二、ACT_HI_ACTINST

非常重要。

它能记录:

流程经过了哪些 BPMN 节点

后面做:

流程图高亮

就会用到类似历史活动数据。


七十三、为什么后面流程图高亮要学历史节点

要知道:

哪些节点已经走过

才能把:

已完成节点

高亮。

还要知道:

当前任务节点

才能高亮当前节点。


七十四、第一个 BPMN:请假流程

流程:

开始
↓
提交申请
↓
经理审批
↓
结束

七十五、简化 BPMN XML

<?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:activiti="http://activiti.org/bpmn"
    targetNamespace="Examples">

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

        <startEvent
            id="start"
            name="开始"
        />

        <userTask
            id="managerApprove"
            name="经理审批"
            activiti:assignee="${managerId}"
        />

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

        <sequenceFlow
            id="flow1"
            sourceRef="start"
            targetRef="managerApprove"
        />

        <sequenceFlow
            id="flow2"
            sourceRef="managerApprove"
            targetRef="end"
        />

    </process>

</definitions>

七十六、process id

id="leaveProcess"

这是:

流程定义 Key

启动流程时:

经常用它

七十七、name

name="请假流程"

主要用于:

显示名称

七十八、isExecutable

isExecutable="true"

表示:

这个流程可执行

七十九、userTask id

id="managerApprove"

这个 ID:

非常重要

后面:

流程判断
节点高亮
节点监听

都会用。


八十、userTask name

name="经理审批"

用于:

界面展示

八十一、activiti:assignee

activiti:assignee="${managerId}"

表示:

办理人来自流程变量 managerId

八十二、启动流程前传 managerId

例如:

Map<String, Object> variables =
        new HashMap<>();

variables.put(
        "managerId",
        "1001"
);

流程到经理节点:

assignee=1001

八十三、为什么不要把审批人写死

不推荐:

activiti:assignee="zhangsan"

真实项目:

审批人通常动态计算

八十四、动态审批人来源

可以来自:

部门负责人

业务负责人

创建人上级

角色用户

流程表单选择

八十五、BPMN 模型工具

实际项目一般不会:

纯手写 XML

而是用:

BPMN Modeler

画图。

工具最终:

生成 BPMN XML

八十六、为什么仍然要看懂 XML

因为调试时:

流程 ID

条件表达式

assignee

candidateGroups

最终都在:

BPMN XML

中。


八十七、流程部署

经典 API 思路:

Deployment deployment =
        repositoryService
                .createDeployment()
                .name(
                        "请假流程"
                )
                .addClasspathResource(
                        "processes/leave.bpmn20.xml"
                )
                .deploy();

八十八、classpath 路径

文件建议:

src/main/resources/processes

例如:

src/main/resources/processes/leave.bpmn20.xml

八十九、为什么放 resources

和 MyBatis 一样:

资源文件需要进入 classpath

Maven:

src/main/resources

默认会打包。


九十、查询 Deployment

List<Deployment> list =
        repositoryService
                .createDeploymentQuery()
                .list();

九十一、查询 ProcessDefinition

List<ProcessDefinition> list =
        repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionKey(
                        "leaveProcess"
                )
                .orderByProcessDefinitionVersion()
                .desc()
                .list();

九十二、ProcessDefinitionKey

leaveProcess

通常:

跨版本不变

九十三、ProcessDefinitionId

可能类似:

leaveProcess:3:2504

它是:

某个具体版本定义 ID

九十四、Key 和 Id 区别

Key:

流程业务标识

Id:

某个具体部署版本的唯一标识

九十五、启动流程

ProcessInstance processInstance =
        runtimeService
                .startProcessInstanceByKey(
                        "leaveProcess"
                );

九十六、带 Business Key 启动

ProcessInstance processInstance =
        runtimeService
                .startProcessInstanceByKey(
                        "leaveProcess",
                        String.valueOf(
                                leaveId
                        )
                );

九十七、带变量启动

Map<String, Object> variables =
        new HashMap<>();

variables.put(
        "managerId",
        "1001"
);

variables.put(
        "days",
        5
);

ProcessInstance processInstance =
        runtimeService
                .startProcessInstanceByKey(
                        "leaveProcess",
                        String.valueOf(
                                leaveId
                        ),
                        variables
                );

九十八、启动后发生什么

引擎:

读取 ProcessDefinition
↓
创建 ProcessInstance
↓
进入 Start Event
↓
沿 Sequence Flow
↓
到 UserTask
↓
创建 ACT_RU_TASK

九十九、查询某人的任务

List<Task> tasks =
        taskService
                .createTaskQuery()
                .taskAssignee(
                        "1001"
                )
                .list();

一百、查询任务可以加条件

例如:

assignee

processDefinitionKey

processInstanceId

taskName

一百零一、待办列表

企业页面常有:

我的待办

本质:

查询当前用户对应的 Task

一百零二、我的已办

本质:

HistoryService

查询:

HistoricTaskInstance

一百零三、任务详情

拿:

taskId

查询:

Task

ProcessInstance

Business Key

然后去:

业务表

查询真实业务数据。


一百零四、为什么 Task 表不应该存完整业务表单

流程引擎:

管理流程状态

你的业务数据库:

管理业务实体

应该:

职责分离

一百零五、审批通过

Map<String, Object> variables =
        new HashMap<>();

variables.put(
        "approved",
        true
);

taskService.complete(
        taskId,
        variables
);

一百零六、complete 后

流程引擎:

当前 Task 完成
↓
按 BPMN 往下走
↓
生成下一 Task
或
结束流程

一百零七、任务完成后当前 task 为什么查不到

因为:

它已经不是 Runtime Task

所以:

ACT_RU_TASK

中删除。

历史:

HistoryService

里仍能查。


一百零八、排他网关

BPMN:

经理审批
↓
Exclusive Gateway
├─ approved=true → 通过
└─ approved=false → 驳回

一百零九、条件 Sequence Flow

例如:

<sequenceFlow
    id="approvedFlow"
    sourceRef="gateway"
    targetRef="hrTask">

    <conditionExpression
        xsi:type="tFormalExpression">
        <![CDATA[
            ${approved == true}
        ]]>
    </conditionExpression>

</sequenceFlow>

一百一十、驳回条件

<conditionExpression
    xsi:type="tFormalExpression">
    <![CDATA[
        ${approved == false}
    ]]>
</conditionExpression>

一百一十一、CDATA 为什么出现

XML 中一些表达式字符:

<
>
&

可能和 XML 语法冲突。

CDATA 可以:

减少转义问题

一百一十二、网关条件来自流程变量

approved

所以审批 complete 时:

必须设置变量

一百一十三、如果变量不存在怎么办

条件表达式可能:

报错

或者:

没有分支匹配

所以必须保证:

流程变量命名统一

一百一十四、流程变量命名不要随便写

数据库/Java/BPMN:

approved

必须一致。

不要:

approve
approved
isApproved

混用。


一百一十五、流程变量作用域

可以有:

Process Variable

Local Variable

一百一十六、Process Variable

作用于:

整个流程实例

后续节点:

都可以访问

一百一十七、Local Variable

作用范围更小:

某 Task / Execution

初学:

优先理解 Process Variable

一百一十八、设置变量

RuntimeService:

runtimeService.setVariable(
        processInstanceId,
        "amount",
        10000
);

一百一十九、任务变量

taskService.setVariable(
        taskId,
        "approved",
        true
);

一百二十、一次设置多个

Map<String, Object> variables =
        new HashMap<>();

variables.put(
        "approved",
        true
);

variables.put(
        "comment",
        "同意"
);

taskService.complete(
        taskId,
        variables
);

一百二十一、审批意见是不是流程变量

可以,

但更常见还会使用:

Comment

或者:

业务审批记录表

保存。


一百二十二、为什么审批意见最好有专门记录

因为我们通常需要:

审批人
审批时间
审批结果
审批意见
节点名称

用于:

审批历史页面

一百二十三、Activiti Comment

经典 API 可以:

为任务/流程添加评论

但企业项目仍可能建立:

自己的 approval_record 表

方便:

业务查询

一百二十四、不要完全依赖流程引擎表做业务报表

Activiti 表:

属于引擎内部数据结构

业务报表:

最好通过业务表

或者:

专门查询服务

一百二十五、候选人任务

BPMN:

<userTask
    id="financeApprove"
    name="财务审批"
    activiti:candidateUsers="1001,1002,1003"
/>

一百二十六、候选组

activiti:candidateGroups="FINANCE"

一百二十七、企业项目如何和 RBAC 对接

系统角色:

FINANCE
MANAGER
ADMIN

流程 Candidate Group:

FINANCE

前端待办:

当前用户 roles

后端:

根据角色查询 Candidate Group Task

一百二十八、但不要简单把 RBAC Role 和流程 Group 完全等同

有时流程审批人:

部门经理

不是一个全局角色。

它可能是:

某个业务部门动态负责人

所以:

审批人分配策略

要按业务设计。


一百二十九、候选任务查询

经典 API 思路:

taskService
    .createTaskQuery()
    .taskCandidateUser(
        userId
    )
    .list();

具体是否能结合 Group:

取决于 Identity / 用户组集成方式

一百三十、Claim

taskService.claim(
        taskId,
        userId
);

一百三十一、Claim 之后

任务:

assignee = userId

其他候选人:

不能再作为未领取任务处理

一百三十二、Unclaim

部分场景:

领取后退回候选池

可以:

unclaim

经典思路就是:

assignee 重新为空

一百三十三、SetAssignee

管理员:

直接指定办理人

例如:

taskService.setAssignee(
        taskId,
        userId
);

一百三十四、Claim 和 SetAssignee 区别

Claim:

通常是候选人主动领取

SetAssignee:

直接指定

一百三十五、转办

业务上:

A 的任务
转给 B

可以通过:

修改 Assignee

实现基础转办思想。


一百三十六、委派

Delegate:

任务暂时交给别人处理

与简单转办:

语义不同

高级任务操作在下一阶段再深入。


一百三十七、Service Task

例如流程:

经理审批
↓
自动更新业务状态
↓
结束

自动节点可以:

Service Task

一百三十八、JavaDelegate

经典 Activiti 中可以编写:

@Component
public class UpdateStatusDelegate
        implements JavaDelegate {

    @Override
    public void execute(
            DelegateExecution execution
    ) {

        String businessKey =
                execution
                        .getProcessInstanceBusinessKey();

        // 调用业务 Service
    }
}

一百三十九、BPMN Service Task

可以配置:

Java Class

Delegate Expression

Expression

一百四十、推荐 Spring 项目怎么引用 Bean

通常更希望:

由 Spring 管理

而不是流程引擎:

自己 new JavaDelegate

因此会使用:

delegateExpression

等方式引用 Spring Bean。


一百四十一、为什么

这样可以使用:

@Service

Mapper

依赖注入

事务

一百四十二、Service Task 里不要写很长业务

JavaDelegate:

负责流程节点适配

真正业务:

放 Service

例如:

userService.updateStatus(...)

一百四十三、流程监听器

Activiti 支持:

Execution Listener

在流程:

start
end
take

等生命周期执行逻辑。


一百四十四、Task Listener

针对:

User Task

生命周期。

例如:

create
assignment
complete
delete

一百四十五、Task Listener 能做什么

例如:

任务创建时
发送待办通知

任务分配时
记录操作

任务完成时
同步某些数据

一百四十六、监听器不要滥用

如果所有业务都藏:

Listener

主流程代码很难看懂。

推荐:

真正属于流程生命周期的公共行为
再用 Listener

一百四十七、并行网关案例

合同审核:

提交合同
↓
Parallel Gateway
├─ 法务审批
└─ 财务审批
↓
Parallel Gateway
↓
总经理审批
↓
结束

一百四十八、并行网关分叉

它不会:

根据条件选择

而是:

所有分支都走

一百四十九、并行网关汇聚

通常等待:

对应并行分支全部到达

再继续。


一百五十、排他网关 vs 并行网关

排他:

多个分支选一个

并行:

多个分支全部走

一百五十一、Inclusive Gateway

包容网关:

多个条件分支
可能同时走一个或多个

比排他:

更复杂

初学先:

知道即可

一百五十二、流程实例查询

ProcessInstance instance =
        runtimeService
                .createProcessInstanceQuery()
                .processInstanceId(
                        processInstanceId
                )
                .singleResult();

一百五十三、如果返回 null

可能:

流程不存在

或者流程已经结束

因为 RuntimeService:

主要查运行中

一百五十四、结束的流程去哪查

HistoryService

一百五十五、流程是否结束

常见判断:

Runtime 查不到
+
History 能查到 endTime

一百五十六、HistoricProcessInstance

查询:

历史流程实例

可以看到:

startTime
endTime
duration
businessKey

一百五十七、HistoricTaskInstance

查询:

历史任务

一百五十八、HistoricActivityInstance

查询:

走过的活动节点

例如:

StartEvent
UserTask
Gateway
EndEvent

一百五十九、已办任务

当前用户过去办理过哪些任务

就可以查:

HistoricTaskInstance

一百六十、审批记录展示

页面可以:

提交申请
2026-09-10 10:00

经理审批
张三
2026-09-10 11:20
同意

财务审批
李四
...

一百六十一、流程图高亮思路预览

后面项目会做:

流程图高亮

核心需要:

ProcessDefinition BPMN

HistoricActivityInstance

Current Task

一百六十二、已完成节点

通过:

历史活动记录

获取。


一百六十三、当前节点

通过:

当前 Task

获取。


一百六十四、未执行节点

BPMN 里有,

但:

历史记录没有

一百六十五、前端高亮

前端 BPMN Viewer:

根据 activityId

加不同样式。

例如:

已完成
绿色

当前
蓝色

未执行
灰色

具体颜色:

按项目 UI 决定

一百六十六、删除 Deployment

repositoryService.deleteDeployment(
        deploymentId
);

一百六十七、级联删除

某些 API 支持:

cascade=true

表示:

连流程实例和历史一起删除

这是:

高风险操作

一百六十八、生产不要随便删除流程历史

审批系统:

历史记录

往往属于:

审计数据

不能随意删除。


一百六十九、Suspend

流程定义 / 实例可以:

挂起

挂起后:

不能继续正常执行某些操作

一百七十、Activate

恢复:

激活

一百七十一、为什么要挂起流程定义

例如:

某审批流程暂时停用

但不想:

删除定义

可以:

suspend

一百七十二、业务状态和流程状态

请假业务表可以有:

DRAFT
PROCESSING
APPROVED
REJECTED

Activiti:

运行流程

一百七十三、为什么业务表仍要 status

页面列表查询:

通常应该查业务表

不应该每一行都:

JOIN Activiti 内部表

一百七十四、流程推进时同步业务状态

例如启动:

business.status = PROCESSING

审批通过结束:

business.status = APPROVED

审批驳回:

business.status = REJECTED

一百七十五、同步状态在哪里做

可以:

Service
JavaDelegate
Listener

要根据:

业务职责

统一设计。


一百七十六、不要多处同时维护 status

如果:

Controller 改一次

Listener 改一次

Delegate 又改一次

很容易:

状态混乱

一百七十七、推荐明确唯一入口

例如:

流程关键节点
→ Application Service
→ 更新业务状态

一百七十八、Activiti 和事务

启动流程:

Activiti 写 ACT_* 表

业务 Service:

写业务表

如果同一个:

DataSource
Spring Transaction

可以考虑:

放在一个本地事务

保证:

业务数据 + 流程启动

一致。


一百七十九、启动请假事务

@Transactional
public Long submitLeave(
        LeaveCreateDTO dto
) {

    LeaveRequest leave =
            new LeaveRequest();

    leaveMapper.insert(
            leave
    );

    ProcessInstance instance =
            runtimeService
                    .startProcessInstanceByKey(
                            "leaveProcess",
                            String.valueOf(
                                    leave.getId()
                            ),
                            variables
                    );

    leaveMapper
            .updateProcessInstanceId(
                    leave.getId(),
                    instance.getId()
            );

    return leave.getId();
}

一百八十、为什么需要事务

如果:

业务表插入成功

但:

流程启动失败

会出现:

一张“处理中”的业务单
却没有流程实例

一百八十一、前提

想依赖一个 Spring 本地事务:

业务表
Activiti 表

通常要:

使用同一事务资源 / DataSource

一百八十二、如果流程引擎在另一个服务

例如:

Activiti Cloud

跨服务:

不能靠一个普通 @Transactional
保证全部一致

这属于:

分布式一致性

一百八十三、Activiti Core vs Activiti Cloud

Activiti Core:

流程引擎作为库
嵌入 Java/SpringBoot 应用

一百八十四、Activiti Cloud

提供:

分布式
云原生
Runtime Bundle
Query
Audit
Connector

等组件。


一百八十五、课程先学哪个

你当前 Java 企业项目阶段:

优先 Activiti Core 思想

因为更容易:

理解流程引擎本体

淘车湾审批:

也更贴近嵌入式流程调用

一百八十六、SpringBoot 集成基本依赖思路

Activiti 7 官方 Core 入门示例使用:

<dependency>
    <groupId>
        org.activiti
    </groupId>

    <artifactId>
        activiti-spring-boot-starter
    </artifactId>
</dependency>

并使用:

Activiti BOM

统一版本。


一百八十七、为什么这里不固定写死版本

因为:

Activiti 版本
SpringBoot 版本
JDK 版本

兼容关系需要:

按项目实际版本确认

尤其:

SpringBoot 2 和 SpringBoot 3

不能随意混。


一百八十八、学习项目配置思路

application.yml:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/activiti_demo
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

Activiti 具体配置键:

按所用 7.x 版本文档确认

一百八十九、数据库建议

单独建:

activiti_demo

学习时:

方便观察 ACT_* 表

一百九十、第一次启动后观察什么

数据库:

SHOW TABLES;

观察:

ACT_RE_*
ACT_RU_*
ACT_HI_*
ACT_GE_*

一百九十一、IDEA / Cursor 项目目录

src/main/java
└─ com.example.workflow
   ├─ controller
   ├─ service
   ├─ workflow
   ├─ delegate
   └─ listener

src/main/resources
├─ application.yml
└─ processes
   └─ leave.bpmn20.xml

一百九十二、为什么 BPMN 放 processes

这是常见约定目录。

不同 Starter 自动部署规则:

可能按版本不同

如果自动部署不工作:

可以用 RepositoryService 手工部署

来确认基础流程。


一百九十三、流程部署 Service

@Service
public class ProcessDefinitionService {

    private final RepositoryService
            repositoryService;

    public ProcessDefinitionService(
            RepositoryService repositoryService
    ) {

        this.repositoryService =
                repositoryService;
    }

    public String deployLeaveProcess() {

        Deployment deployment =
                repositoryService
                        .createDeployment()
                        .name(
                                "请假审批流程"
                        )
                        .addClasspathResource(
                                "processes/leave.bpmn20.xml"
                        )
                        .deploy();

        return deployment.getId();
    }
}

一百九十四、启动流程 Service

@Service
public class LeaveWorkflowService {

    private final RuntimeService
            runtimeService;

    private final TaskService
            taskService;

    public LeaveWorkflowService(
            RuntimeService runtimeService,
            TaskService taskService
    ) {

        this.runtimeService =
                runtimeService;

        this.taskService =
                taskService;
    }
}

一百九十五、启动方法

public String start(
        Long leaveId,
        String managerId,
        Integer days
) {

    Map<String, Object> variables =
            new HashMap<>();

    variables.put(
            "managerId",
            managerId
    );

    variables.put(
            "days",
            days
    );

    ProcessInstance instance =
            runtimeService
                    .startProcessInstanceByKey(
                            "leaveProcess",
                            String.valueOf(
                                    leaveId
                            ),
                            variables
                    );

    return instance.getId();
}

一百九十六、查询我的待办

public List<Task> myTasks(
        String userId
) {

    return taskService
            .createTaskQuery()
            .taskAssignee(
                    userId
            )
            .orderByTaskCreateTime()
            .desc()
            .list();
}

一百九十七、不要直接把 Task Entity 返回前端

更推荐:

TaskVO

一百九十八、TaskVO

public class TaskVO {

    private String taskId;

    private String taskName;

    private String assignee;

    private String processInstanceId;

    private String businessKey;

    private Date createTime;

}

一百九十九、为什么还需要 Business Key

待办页面点击:

详情

前端需要知道:

对应哪张业务单

二百、Task 本身可能没有直接业务详情

可以通过:

ProcessInstance

拿:

businessKey

再查:

leave_request

二百零一、审批 DTO

public class ApproveTaskDTO {

    private Boolean approved;

    private String comment;

}

二百零二、审批接口

POST /api/workflow/tasks/{taskId}/complete

Body:

{
    "approved": true,
    "comment": "同意"
}

二百零三、后端完成任务

public void complete(
        String taskId,
        ApproveTaskDTO dto
) {

    Map<String, Object> variables =
            new HashMap<>();

    variables.put(
            "approved",
            dto.getApproved()
    );

    taskService.complete(
            taskId,
            variables
    );
}

二百零四、这里少了什么

还没有检查:

当前登录用户
是不是这个 Task 的办理人

这是企业项目必须补的。


二百零五、为什么不能前端传 assignee 就信

错误接口:

{
    "assignee": "admin",
    "approved": true
}

用户可以:

伪造 assignee

二百零六、正确做法

当前用户:

从 Token / SecurityContext

获取。

后端查询 Task:

task.assignee

然后比较:

当前用户是否允许办理

二百零七、任务权限检查

Task 不存在
→ 404

Task assignee 不是当前用户
→ 403

流程已结束
→ 业务错误

合法
→ complete

二百零八、候选任务更复杂

如果:

尚未 claim

需要判断:

当前用户是否为候选人

再允许:

claim

二百零九、流程审批接口也要 RBAC 吗

可以有两层:

功能权限
workflow:task:approve

+
任务实例权限
当前用户是否是该任务办理人

二百一十、为什么两层都需要

RBAC:

决定有没有“审批功能”

Task 权限:

决定能不能审批这一条任务

二百一十一、类似之前学过的数据权限

Permission
解决功能权限

Task Assignee
解决实例数据权限

二百一十二、流程撤回

用户提交后:

想撤回

这不是 Activiti 最简单 CRUD。

要先定义业务规则:

当前走到哪里才能撤回?

审批人已经处理能不能撤回?

撤回后回草稿还是结束流程?

二百一十三、不要看到“撤回”就直接删流程实例

删除实例:

不等于业务撤回

撤回是:

业务语义

需要设计:

状态
历史
审计

二百一十四、驳回也有多种语义

可能:

流程直接结束

退回申请人修改

退回上一节点

退回任意节点

这些不是同一个功能。


二百一十五、初学先做哪种

第一版:

同意
→ 下一节点

拒绝
→ Reject End Event

最清楚。


二百一十六、退回上一节点为什么复杂

因为可能存在:

并行
会签
网关
子流程

不能简单:

taskDefinitionKey = 上一个

二百一十七、因此流程引擎项目最重要

先:

把业务规则画清楚

再:

写代码

二百一十八、BPMN 图就是业务协议

开发前:

产品
业务
开发
测试

都可以围绕:

同一张流程图

讨论。


二百一十九、流程定义 Key 命名

推荐:

leaveApproval

orderApproval

repairApproval

不要:

process1
test2
abc

二百二十、Task Definition Key

例如:

managerApprove
financeApprove
generalManagerApprove

不要全靠中文 Name。


二百二十一、为什么 Key 要稳定

后端代码:

可能需要根据 TaskDefinitionKey
判断业务节点

流程高亮:

也可能按 key

二百二十二、Name 用于显示

例如:

经理审批

Key:

managerApprove

这样:

程序稳定
页面友好

二百二十三、流程版本升级

比如原来:

经理审批

后来增加:

财务审批

重新部署:

version + 1

二百二十四、旧实例怎么办

通常:

继续跑旧流程定义版本

新实例:

使用新版本

二百二十五、不要直接删除旧定义

因为:

可能还有旧实例引用

二百二十六、流程定义部署管理页面

企业后台可以做:

流程名称

Key

版本

部署时间

状态

操作

二百二十七、操作

可能:

查看 BPMN

挂起

激活

部署新版本

二百二十八、不要把流程设计器作为第一阶段重点

课程后面真正需要的是:

能部署
能启动
能审批
能查历史
能高亮

复杂在线流程设计器:

以后再做

二百二十九、历史级别

Activiti 有:

History Level

决定:

历史记录保存多少

二百三十、为什么历史级别重要

如果历史级别太低:

可能查不到完整变量 / 活动信息

如果太高:

历史表数据增长更快

二百三十一、学习项目

为了:

审批历史
流程图高亮

需要确保:

历史活动数据可查询

具体配置:

按当前 Activiti 版本确认

二百三十二、流程引擎表会越来越大

尤其:

ACT_HI_*

长期系统:

历史数据量非常大

二百三十三、生产需要考虑

归档
清理策略
索引
历史级别

学习阶段:

知道即可

二百三十四、Job

Activiti 还会涉及:

Job Executor

处理:

Timer
Async Continuation

二百三十五、Timer Event

例如:

任务 3 天没审批
↓
自动提醒

可以使用:

Timer

二百三十六、定时边界事件

例如:

经理审批节点
挂一个 24 小时 Timer

超时:

通知管理员

二百三十七、当前先不深入 Job

这一部分先掌握:

人工审批主链路

下一阶段再扩展。


二百三十八、流程监听和 Spring Event 区别

Activiti Listener:

围绕流程生命周期

Spring Event:

围绕应用业务事件

二百三十九、不要混淆

例如:

任务创建

适合:

Task Listener

用户注册:

Spring Event

更自然。


二百四十、流程引擎和状态机区别

状态机:

关注状态和状态转换

工作流:

更关注任务、人员、流程节点、审批历史

二百四十一、什么时候用 Activiti

适合:

审批节点多

规则经常变化

需要待办/已办

需要流程历史

需要人员分配

需要 BPMN 可视化

二百四十二、什么时候没必要 Activiti

例如:

一个简单订单:
待付款
已付款
已发货

如果状态很固定:

简单状态机 / enum

可能更简单。


二百四十三、不要为了“高级”强行上工作流

工作流引擎也带来:

学习成本

数据库表

部署管理

流程版本

流程数据一致性

二百四十四、课程为什么要学

因为后面的:

淘车湾审批

确实需要:

流程定义
审批任务
流程历史
高亮

所以是:

真实需求

二百四十五、请假案例完整数据表

业务表:

CREATE TABLE leave_request (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    applicant_id BIGINT NOT NULL,
    reason VARCHAR(500) NOT NULL,
    days INT NOT NULL,
    status VARCHAR(32) NOT NULL,
    process_instance_id VARCHAR(64),
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

二百四十六、审批状态

例如:

DRAFT

PROCESSING

APPROVED

REJECTED

二百四十七、提交接口

POST /api/leaves/{id}/submit

流程:

检查业务单
↓
检查当前用户是申请人
↓
检查 status=DRAFT
↓
启动 Activiti
↓
更新 processInstanceId
↓
status=PROCESSING

二百四十八、审批接口

POST /api/workflow/tasks/{taskId}/complete

二百四十九、我的待办

GET /api/workflow/tasks/todo

当前用户:

从登录上下文获取

二百五十、我的已办

GET /api/workflow/tasks/done

二百五十一、流程历史

GET /api/workflow/processes/{processInstanceId}/history

二百五十二、流程图

GET /api/workflow/processes/{processInstanceId}/diagram

或:

BPMN XML + 高亮节点数据

具体前后端方式后面再做。


二百五十三、不要直接暴露 ACT_* 表 CRUD API

例如:

/api/act-ru-task/delete

这是非常错误的设计。

业务系统应该:

通过 Activiti Service API

操作流程。


二百五十四、为什么不能直接改 ACT_RU_TASK

Activiti 内部表之间:

有复杂关联

手工 UPDATE:

可能破坏流程引擎状态

二百五十五、牢记

ACT_* 表
可以查询理解

不要把它当普通业务表
随便 CRUD

二百五十六、流程 API Service 分层

推荐:

WorkflowController

WorkflowService

Activiti Engine

业务:

LeaveController

LeaveService

不要所有流程代码:

散落 Controller

二百五十七、WorkflowService 职责

例如:

startProcess

getTodoTasks

claimTask

completeTask

getHistory

getCurrentActivity

二百五十八、业务 Service 职责

例如 LeaveService:

创建请假单

校验申请

同步业务状态

读取请假详情

二百五十九、为什么分层

流程引擎:

是一种基础设施

业务代码:

不要和 Activiti API 强耦合到每个角落

二百六十、完整流程架构

Vue
↓
SpringBoot Controller
↓
Application Service
├─ Business Service
│  └─ MyBatis
│
└─ Workflow Service
   └─ Activiti
      ├─ Runtime
      ├─ Task
      ├─ Repository
      └─ History

二百六十一、前端待办页面

表格:

任务名称

业务标题

申请人

创建时间

当前节点

操作

操作:

查看

同意

拒绝

二百六十二、不要只显示 Task ID

普通用户看:

8f3d...

没有意义。

应该结合业务表:

采购申请 #20260910001

二百六十三、待办查询 N+1 问题

如果查 20 个 Task:

每个 Task 再查一次业务表

可能:

N+1

二百六十四、企业优化思路

可以:

批量收集 businessKey
↓
一次查询业务数据
↓
Java Map 组装

二百六十五、又回到 MyBatis 知识

工作流并不会替代:

MyBatis

它只是管理:

流程

业务列表仍要:

SQL

二百六十六、流程分页

待办很多:

不要 list() 全查

Activiti Query 通常提供:

分页查询

例如:

listPage(firstResult, maxResults)

二百六十七、为什么分页

和数据库一样:

防止一次加载大量 Task

二百六十八、流程引擎查询条件

通常:

ProcessDefinitionKey

ProcessInstanceId

BusinessKey

Assignee

CandidateUser

TaskDefinitionKey

二百六十九、查询流程定义最新版本

通常:

processDefinitionKey
latestVersion()

之类 API。

启动 ByKey:

通常会选择合适的最新定义

二百七十、部署重复问题

应用每次启动:

如果重复手工 deploy

可能:

不断生成新流程版本

二百七十一、正确做法

学习测试:

可以手工部署

企业项目:

需要明确流程发布管理机制

不要:

每次请求都 deploy

二百七十二、流程部署和流程启动区别

部署:

发布模板

启动:

创建一次实例

二百七十三、类比

部署流程
≈ 发布 Class

启动流程
≈ new Object

二百七十四、流程定义和部署关系

一个 Deployment 可以:

包含多个 BPMN / 资源

一个 BPMN:

可以产生 ProcessDefinition

二百七十五、RepositoryService Query

用于:

查看流程模板

不是:

查看当前任务

二百七十六、RuntimeService Query

用于:

查看运行中的流程实例

不是:

查看已结束流程

二百七十七、TaskService Query

用于:

查看当前任务

二百七十八、HistoryService Query

用于:

查看历史

二百七十九、四大 Service 记忆口诀

Repository
管模板

Runtime
管实例

Task
管待办

History
管历史

二百八十、Activiti 7 Runtime API

如果使用新的:

ProcessRuntime
TaskRuntime

核心概念仍然:

启动流程

查询流程

查询任务

完成任务

只是:

API 风格不同

二百八十一、为什么先别死背方法名

不同:

Activiti 版本
Core API
Runtime API

方法可能不同。

但你一定要知道:

我要部署什么
我要启动什么
我要查任务什么
我要查历史什么

二百八十二、真正重要的是模型

Definition
↓
Instance
↓
Execution
↓
Task
↓
History

二百八十三、BPMN 变量表达式

常见:

${approved}
${days > 3}
${amount >= 10000}

二百八十四、表达式不要写复杂业务

不推荐:

在 BPMN 表达式里写大量复杂判断

复杂业务:

Java Service

计算后:

放结果变量

二百八十五、例如

Java:

approvalLevel = 3

BPMN:

${approvalLevel == 3}

比:

在 XML 中塞复杂业务算法

容易维护。


二百八十六、流程节点权限

BPMN 指定:

assignee

只是一部分。

后端 Complete Task 时仍需要:

再次验证当前用户

二百八十七、不要信任前端 Task 状态

前端可能显示:

待审批

但在用户点击时:

任务可能已经被别人处理

后端必须:

重新查询 Task

二百八十八、并发审批

两个浏览器同时打开一个任务:

A 点击同意

B 也点击同意

只有一个:

应该成功

二百八十九、流程引擎会维护 Task 生命周期

第二个请求可能:

Task 不存在

或者产生:

并发相关异常

前端:

刷新待办

二百九十、不要前端认为成功就成功

以后端:

complete 返回结果

为准。


二百九十一、流程异常统一处理

例如:

TASK_NOT_FOUND

PROCESS_NOT_FOUND

TASK_FORBIDDEN

PROCESS_SUSPENDED

INVALID_PROCESS_STATE

可以纳入:

GlobalExceptionHandler

二百九十二、和 RBAC 的 ErrorCode 一样

例如:

WORKFLOW_TASK_NOT_FOUND

WORKFLOW_TASK_FORBIDDEN

WORKFLOW_PROCESS_NOT_FOUND

二百九十三、流程日志

不要只记录:

System.out.println

应该使用:

SLF4J

二百九十四、重要审计操作

例如:

启动流程

领取任务

完成任务

驳回

撤回

转办

可以记录:

业务操作日志

二百九十五、Activiti 历史 ≠ 业务审计全部

历史表告诉:

流程发生了什么

业务操作日志还可以告诉:

谁从哪个 IP 操作

请求结果

业务备注

二者互补。


二百九十六、流程任务和普通 RBAC 权限

用户有:

workflow:approve

但如果 Task 属于别人:

仍不能审批

二百九十七、必须记住

RBAC
控制功能权限

Workflow Task
控制流程实例办理权限

二百九十八、常见错误 1:BPMN 不自动部署

检查:

文件路径

文件名

Starter 版本

自动部署配置

必要时:

RepositoryService 手工部署

验证。


二百九十九、常见错误 2:流程 Key 写错

BPMN:

leaveProcess

Java:

leaveApproval

启动:

找不到 ProcessDefinition

三百、常见错误 3:变量不存在

网关:

${approved == true}

complete:

没传 approved

流程:

无法正确选择分支

三百零一、常见错误 4:Task 查询不到

可能:

流程已经结束

Assignee 不对

Task 已被完成

查询条件写错

三百零二、常见错误 5:完成任务后又去 Runtime 查任务

任务已经:

完成

要去:

History

查。


三百零三、常见错误 6:直接操作 ACT_* 表

不推荐。

流程状态:

可能直接损坏

三百零四、常见错误 7:流程变量存大对象

例如:

整个订单对象
大 JSON
附件

都塞变量。

会:

增加流程表压力

三百零五、常见错误 8:业务数据只存在流程变量

以后:

查询业务列表
统计
报表

很痛苦。

业务数据必须:

有自己的业务表

三百零六、常见错误 9:流程和业务没有 Business Key

后面:

无法快速知道任务对应哪条业务

三百零七、常见错误 10:审批人写死 BPMN

上线后人员变化:

流程要改 XML

更合理:

动态变量 / 规则计算

三百零八、常见错误 11:SpringBoot 3 直接套旧 Activiti 7 教程

可能:

依赖冲突

javax/jakarta 冲突

Spring API 不兼容

正确:

先确认官方/项目版本兼容矩阵

三百零九、常见错误 12:重复部署产生大量版本

不要每次:

接口调用就 deploy

三百一十、常见错误 13:审批接口信任 userId 参数

用户可以:

伪造 userId

当前用户必须:

从认证上下文获取

三百一十一、常见错误 14:只做前端按钮权限

直接 Postman:

仍可调用

后端必须:

验证 Task Assignee

三百一十二、常见错误 15:流程实例结束后 Runtime 查不到就认为不存在

要:

HistoryService

确认。


三百一十三、常见错误 16:驳回就是 complete(false) 但 BPMN 没有对应条件

变量传了:

false

流程图没有:

reject 分支

仍无法实现真正驳回。


三百一十四、流程逻辑首先由 BPMN 决定

Java:

不能凭空创建 BPMN 中不存在的路径

三百一十五、常见错误 17:Listener 里面塞全部业务

以后:

没人知道代码在哪里执行

三百一十六、常见错误 18:事务方法内部吞异常

又回到:

@Transactional

异常被吞:

业务数据和流程状态可能不一致

三百一十七、常见错误 19:BPMN XML 特殊字符

例如条件:

days < 3

XML:

<

可能需要:

CDATA

三百一十八、常见错误 20:流程 Key 使用中文

可以显示中文:

name

但 Key 建议:

英文稳定标识

三百一十九、IDEA 练习项目建议

项目:

activiti-demo

包:

com.example.activiti

三百二十、第一步

创建数据库:

CREATE DATABASE activiti_demo
DEFAULT CHARACTER SET utf8mb4;

三百二十一、第二步

准备:

兼容版本 SpringBoot

Activiti Starter

MySQL Driver

三百二十二、第三步

配置:

DataSource

三百二十三、第四步

启动应用。

查看:

ACT_* 表

三百二十四、第五步

创建:

leave.bpmn20.xml

三百二十五、第六步

部署:

leaveProcess

三百二十六、第七步

启动流程:

businessKey=leaveId

三百二十七、第八步

查询:

managerId

的待办。


三百二十八、第九步

完成审批:

approved=true

三百二十九、第十步

查询:

历史流程
历史任务
历史活动

三百三十、第十一步

增加:

Exclusive Gateway

支持:

同意
拒绝

三百三十一、第十二步

增加:

days > 3

分支。


三百三十二、第十三步

增加:

candidate group

体验:

claim

三百三十三、第十四步

增加:

Service Task

自动更新业务状态。


三百三十四、第十五步

前端:

我的待办
我的已办
流程历史

三百三十五、练习 1

画:

请假审批

要求:

开始
↓
经理审批
↓
结束

三百三十六、练习 2

部署流程。

打印:

deploymentId

三百三十七、练习 3

查询:

ProcessDefinition

打印:

id
key
name
version

三百三十八、练习 4

启动两次:

leaveProcess

观察:

两个 ProcessInstance

三百三十九、练习 5

分别传:

businessKey=1001
1002

三百四十、练习 6

查询:

managerId=1001

的待办。


三百四十一、练习 7

完成 Task。

再查询:

ACT_RU_TASK

观察变化。


三百四十二、练习 8

History 查询已完成 Task。


三百四十三、练习 9

增加:

approved

排他网关。


三百四十四、练习 10

approved:

true

走:

通过

三百四十五、练习 11

approved:

false

走:

拒绝

三百四十六、练习 12

查询:

HistoricActivityInstance

按:

startTime

排序。


三百四十七、练习 13

根据:

activityId

打印完整流程路径。


三百四十八、练习 14

业务表保存:

processInstanceId

三百四十九、练习 15

根据 Task:

找到 ProcessInstance
↓
找到 businessKey
↓
查业务表

三百五十、练习 16

实现:

GET /todo

返回:

TaskVO
+
业务标题

三百五十一、练习 17

实现:

POST /tasks/{taskId}/complete

并验证:

当前登录用户

三百五十二、练习 18

实现:

GET /done

通过历史任务查询。


三百五十三、练习 19

创建:

candidateUsers

任务。

测试:

claim

三百五十四、练习 20

创建:

Parallel Gateway

法务:

一条任务

财务:

一条任务

观察:

ACT_RU_TASK
同时出现两个任务

三百五十五、必须掌握 BPMN

Start Event

End Event

User Task

Service Task

Sequence Flow

Exclusive Gateway

Parallel Gateway

三百五十六、必须掌握核心对象

ProcessDefinition

Deployment

ProcessInstance

Execution

Task

ProcessVariable

BusinessKey

三百五十七、必须掌握四大 Service

RepositoryService

RuntimeService

TaskService

HistoryService

记忆:

模板
实例
任务
历史

三百五十八、必须掌握 Task

Assignee

CandidateUser

CandidateGroup

Claim

Complete

三百五十九、必须掌握流程变量

setVariable

variables Map

Gateway Condition

三百六十、必须掌握数据库前缀

ACT_RE_
仓库

ACT_RU_
运行时

ACT_HI_
历史

ACT_GE_
通用

三百六十一、必须掌握企业集成

业务表

Business Key

Process Instance ID

业务状态

RBAC

任务权限

事务

三百六十二、面试题 1

问:

Activiti 是什么?

答:

Activiti 是 Java 生态中的 BPMN 工作流引擎。

它可以通过 BPMN 流程定义管理审批流程,
负责流程实例、用户任务、流程变量、
网关和历史记录等流程运行状态。

三百六十三、面试题 2

问:

为什么要使用工作流引擎?

答:

复杂审批业务如果全部通过 status
和大量 if/else 手写,
流程一变化就会导致代码难维护。

流程引擎把流程定义从业务代码中抽离,
通过 BPMN 管理节点、任务和流转规则。

三百六十四、面试题 3

问:

流程定义和流程实例有什么区别?

答:

流程定义是流程模板,
例如“请假审批流程”。

流程实例是模板的一次真实运行,
例如“张三本次请假申请”。

可以类比 Java 中 Class 和 Object。

三百六十五、面试题 4

问:

Deployment 是什么?

答:

Deployment 表示流程部署。

BPMN 文件部署到流程引擎后,
引擎才会生成和管理对应 ProcessDefinition,
之后才能根据流程定义启动流程实例。

三百六十六、面试题 5

问:

RepositoryService、RuntimeService、TaskService、HistoryService 分别做什么?

答:

RepositoryService 管理流程定义和部署。

RuntimeService 管理运行中的流程实例和变量。

TaskService 管理当前用户任务。

HistoryService 查询已经发生过的流程和任务历史。

三百六十七、面试题 6

问:

ACT_RE、ACT_RU、ACT_HI 表有什么区别?

答:

ACT_RE 主要保存部署和流程定义。

ACT_RU 保存当前运行中的实例、任务和变量。

ACT_HI 保存流程运行历史。

流程任务完成后,
通常会从 Runtime 数据中消失,
但可以从 History 数据中继续查询。

三百六十八、面试题 7

问:

Business Key 有什么作用?

答:

Business Key 用于把流程实例
和业务系统中的真实业务记录建立关联。

例如订单 id=1001,
启动流程时 businessKey=1001,
以后就可以通过流程找到订单,
也可以通过订单定位对应流程。

三百六十九、面试题 8

问:

Assignee 和 Candidate 有什么区别?

答:

Assignee 表示任务已经明确分配给某个办理人。

Candidate 表示一个或多个候选用户/用户组
都有资格处理任务,
通常需要先 Claim 领取,
再成为实际 Assignee。

三百七十、面试题 9

问:

Exclusive Gateway 和 Parallel Gateway 区别?

答:

Exclusive Gateway 是排他网关,
多个条件分支中通常只选择一条执行。

Parallel Gateway 是并行网关,
分叉时所有分支同时执行,
汇聚时通常等待并行分支全部完成。

三百七十一、面试题 10

问:

流程变量和业务表有什么区别?

答:

业务表保存真正业务数据,
用于业务查询、报表和持久化。

流程变量主要服务流程运行和条件判断。

不应该把所有业务数据都塞进流程变量,
应该只保存流程真正需要的数据。

三百七十二、面试题 11

问:

任务完成后为什么 TaskService 查不到?

答:

TaskService 主要查询当前运行中的任务。

任务完成后已经不属于 Runtime Task,
因此通常需要通过 HistoryService
查询 HistoricTaskInstance。

三百七十三、面试题 12

问:

Activiti 如何和 RBAC 配合?

答:

RBAC 可以控制用户是否拥有审批功能,
例如 workflow:task:approve。

Activiti Task 的 Assignee 或 Candidate
则控制用户是否有资格办理具体这一条流程任务。

两者一个解决功能权限,
一个解决流程实例级办理权限。

三百七十四、面试题 13

问:

为什么不能直接修改 ACT_* 表?

答:

Activiti 内部表之间存在复杂的流程状态关系。

直接修改单张表可能破坏执行树、任务、变量和历史的一致性。

流程操作应该通过 Activiti 提供的 Service/API 完成。

三百七十五、面试题 14

问:

为什么业务表还要保存 status?

答:

流程引擎负责流程状态,
但业务列表、统计和报表通常应该以业务表为主。

因此业务表仍然需要明确业务状态,
并在关键流程节点同步更新。

三百七十六、面试题 15

问:

SpringBoot 3 使用 Activiti 7 为什么要注意?

答:

Activiti 7 的很多官方 Core 示例基于 Spring Boot 2。

Spring Boot 3 迁移到了 Jakarta 生态,
旧版本 Activiti 依赖可能和 Spring Boot 3
存在 API 或 javax/jakarta 兼容问题。

因此不能只复制旧教程,
应该先确认 Activiti、SpringBoot 和 JDK 的兼容版本。

三百七十七、核心流程图

BPMN
↓
Deployment
↓
ProcessDefinition
↓
startProcess
↓
ProcessInstance
↓
UserTask
↓
Task
↓
Assignee / Candidate
↓
Complete
↓
Gateway
↓
Next Task
↓
End
↓
History

三百七十八、业务系统集成图

Vue
↓
SpringBoot
↓
Business Service
├─ MyBatis
│  └─ Business Tables
│
└─ Workflow Service
   └─ Activiti
      ├─ ACT_RE
      ├─ ACT_RU
      └─ ACT_HI

三百七十九、业务和流程关联图

leave_request.id
        │
        │ businessKey
        ↓
ProcessInstance
        │
        ↓
Task
        │
        ↓
审批人

业务表还可保存:

process_instance_id

形成:

双向快速关联

三百八十、审批权限图

当前登录用户
│
├─ RBAC
│  └─ workflow:task:approve
│
└─ Activiti
   └─ Task Assignee/Candidate

必须:

两边都合法

才允许审批。


三百八十一、课程阶段总结

本章第一部分必须真正理解的不是:

几十个 API 方法名

而是:

工作流是什么

BPMN 怎么描述流程

流程定义和流程实例的区别

流程怎么部署

Task 怎么产生

任务怎么完成

变量怎么控制网关

历史怎么查询

流程如何和业务表关联

核心四大 Service:

Repository
模板

Runtime
实例

Task
待办

History
历史

核心 BPMN:

Start
UserTask
ServiceTask
Gateway
SequenceFlow
End

核心业务关联:

BusinessKey
+
ProcessInstanceId

核心权限:

RBAC 功能权限
+
Task 实例权限

核心数据表:

ACT_RE
ACT_RU
ACT_HI
ACT_GE

核心原则:

不要直接修改 ACT_* 表

不要把所有业务数据塞进流程变量

不要把审批人写死

不要只做前端权限

不要忽略流程和业务事务一致性

三百八十二、下一篇 Activiti 第二部分

下一篇会继续深入:

Activiti 7 实战进阶

候选用户与候选组

Claim / Unclaim

转办 / 委派

审批意见

流程变量作用域

任务监听器

执行监听器

Service Task

并行网关

会签基础

Timer

流程挂起 / 激活

流程历史

流程图高亮原理

流程部署管理

SpringBoot RESTful API 设计

Vue 待办 / 已办页面

淘车湾审批流程衔接

完成第二部分后,就能够直接为后面的:

淘车湾项目
集成 Activiti 7
审批页面
我的待办
我的已办
流程图高亮

做准备。


官方参考

Activiti 官网:

https://www.activiti.org/

Activiti 7 Developer Guide:

https://activiti.gitbook.io/activiti-7-developers-guide/

Activiti GitHub:

https://github.com/Activiti/Activiti

注意:

Activiti 7 的官方 Core 入门文档主要展示 Spring Boot 2 集成方式。

真正创建项目时,
不要只看某篇旧博客里的版本号,
必须确认:

Activiti
+
Spring Boot
+
JDK

三者是否兼容。