Activiti7第二部分_任务流转_会签_监听器_Timer_流程高亮实战
Activiti 7 第二部分:任务流转、会签、监听器、Timer、流程历史与流程图高亮实战
本章位置:第三阶段 Java 企业项目 + AI 助手
对应课程:Activiti(第二部分)
前置知识:Activiti 7 + BPMN 工作流基础、SpringBoot、MyBatis、事务、RBAC
后续衔接:Git 工具使用、Redis、若依、淘车湾项目中的审批流程与流程图高亮
学习目标:在第一部分“会部署、会启动、会查任务”的基础上,继续掌握任务领取与释放、转办、委派、审批意见、监听器、并行审批、会签、Timer、流程挂起与激活、历史查询、流程图高亮以及前后端审批实战。
一、本章要解决的真实问题
第一部分我们已经掌握:
BPMN
ProcessDefinition
Deployment
ProcessInstance
Task
BusinessKey
流程变量
RepositoryService
RuntimeService
TaskService
HistoryService
但真实审批项目还会遇到:
一个任务很多人都能处理怎么办?
谁先领取任务?
领取后能不能退回?
A 的任务能不能转给 B?
A 临时让 B 代办,之后还能回到 A 吗?
审批意见怎么保存?
两个人必须同时审批怎么办?
10 个人里至少 6 人同意怎么办?
任务超时怎么办?
任务创建时自动发通知怎么办?
流程结束后如何查询完整审批历史?
前端怎么画“已完成、当前、未执行”节点?
流程部署错了能不能停用?
淘车湾里的审批流到底怎么设计?
这就是本章内容。
二、先回顾任务状态
一个 User Task 常见状态变化:
创建
↓
候选任务
↓
领取 Claim
↓
Assignee Task
↓
办理
↓
Complete
↓
进入下一节点
也可能:
Claim
↓
Release / Unclaim
↓
重新回候选池
还可能:
Assignee A
↓
转办
↓
Assignee B
或者:
Assignee A
↓
委派给 B
↓
B 处理
↓
回到 A
三、Claim 是什么
Claim:
领取任务
使用场景:
某个任务不是指定给具体一个人
而是给一组候选人
例如:
财务审核
候选人:
张三
李四
王五
三个人都能看到:
待领取任务
谁先领取:
谁成为 Assignee
四、经典 TaskService Claim
taskService.claim(
taskId,
userId
);
执行后:
ASSIGNEE_
会变为:
userId
五、Activiti 7 TaskRuntime Claim
官方 Activiti 7 Runtime API 也提供:
claim
思路类似:
taskRuntime.claim(
TaskPayloadBuilder
.claim()
.withTaskId(
taskId
)
.build()
);
注意:
具体 Builder 方法名
要以你实际 Activiti 7 版本为准
但核心概念不变:
TaskRuntime.claim()
六、Claim 前必须检查什么
企业项目不能直接:
拿 taskId 就 claim
应该检查:
1. Task 是否存在
2. 当前 Task 是否还未被领取
3. 当前用户是否属于 Candidate
4. 流程是否未挂起
5. 当前用户是否有 workflow:task:claim 权限
七、为什么不能只相信前端
前端请求:
{
"userId": 1001
}
不安全。
当前用户应该:
从 Token / SecurityContext
获取。
不能让前端说:
“我就是 1001”
八、领取接口
推荐:
POST /api/workflow/tasks/{taskId}/claim
不需要:
userId 参数
后端:
CurrentUserContext
取得当前用户。
九、领取 Service
伪代码:
@Transactional
public void claimTask(
String taskId
) {
Long currentUserId =
currentUserService
.getCurrentUserId();
Task task =
taskService
.createTaskQuery()
.taskId(
taskId
)
.singleResult();
if (
task == null
) {
throw new BusinessException(
"任务不存在"
);
}
taskService.claim(
taskId,
String.valueOf(
currentUserId
)
);
}
真实项目还要:
校验 Candidate
十、Release / Unclaim
任务领取后:
不想处理
可以释放:
Release
经典 API 常见思路:
taskService.unclaim(
taskId
);
Activiti 7 Runtime API:
release
十一、释放后发生什么
例如:
Assignee = 张三
释放:
Assignee = null
任务重新成为:
候选任务
其他候选人:
可以领取
十二、Release 接口
POST /api/workflow/tasks/{taskId}/release
后端必须验证:
当前用户是当前 Assignee
十三、不能释放别人的任务
例如:
Task Assignee = 1001
当前用户 = 1002
1002:
不能 release
否则:
403
十四、转办是什么
转办:
Transfer
简单理解:
A 的任务
直接变成 B 的任务
A:
不再是办理人
B:
成为 Assignee
十五、转办实现思路
经典 API 可:
taskService.setAssignee(
taskId,
targetUserId
);
十六、转办前需要检查
Task 存在
当前用户有权操作 Task
目标用户存在
目标用户可作为办理人
流程未结束
流程未挂起
十七、转办接口
POST /api/workflow/tasks/{taskId}/transfer
Body:
{
"targetUserId": 1002,
"reason": "出差,转交处理"
}
十八、为什么要保存 reason
审批系统操作:
必须可审计
所以最好记录:
原办理人
目标办理人
转办原因
时间
十九、转办记录表
可以建立业务表:
CREATE TABLE workflow_task_operation (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
task_id VARCHAR(64) NOT NULL,
process_instance_id VARCHAR(64),
operation_type VARCHAR(32) NOT NULL,
operator_id BIGINT NOT NULL,
target_user_id BIGINT,
comment VARCHAR(1000),
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
二十、operation_type
例如:
CLAIM
RELEASE
TRANSFER
DELEGATE
APPROVE
REJECT
二十一、为什么自己建 workflow_task_operation
Activiti 有自己的:
History
Comment
IdentityLink
但业务系统经常仍建立:
业务操作审计表
原因:
查询简单
字段自己控制
便于业务报表
便于前端展示
二十二、委派是什么
委派:
Delegate
和转办不同。
二十三、转办语义
A
↓
Transfer
↓
B
最终:
任务就是 B 的
A:
通常不再负责
二十四、委派语义
A 是任务负责人
↓
临时 Delegate 给 B
↓
B 帮助处理
↓
Resolve
↓
任务重新回到 A
↓
A 最终 Complete
二十五、为什么委派更适合“代办”
例如经理:
出差
临时让副经理:
处理内容
但是最终责任:
仍属于经理
这时:
Delegate
比 Transfer 更符合业务语义。
二十六、Owner
委派通常会涉及:
Owner
例如:
Owner = A
Assignee = B
B 处理完:
resolve
再回:
A
二十七、经典委派 API
典型思路:
taskService.delegateTask(
taskId,
targetUserId
);
二十八、Resolve
被委派人处理完:
taskService.resolveTask(
taskId
);
注意:
Resolve 不等于 Complete
二十九、Resolve vs Complete
Resolve:
完成“代办”
任务回原 Owner
Complete:
真正完成 BPMN UserTask
流程继续向下
三十、为什么很多初学者把委派和转办写混
因为表面看:
都是换一个人
但业务语义完全不同。
三十一、记忆口诀
Transfer:
责任也转走
Delegate:
只是帮忙处理
责任仍在原负责人
三十二、审批意见怎么保存
审批:
同意
不同意
通常还需要:
审批意见
例如:
同意,预算符合要求
三十三、Activiti Comment
经典 TaskService 可以使用 Comment API。
典型:
taskService.addComment(
taskId,
processInstanceId,
comment
);
三十四、为什么 addComment 前可能设置 Authentication
某些经典用法中会设置:
当前认证用户
使 Comment:
知道是谁提交
但具体 API 和安全集成:
按当前版本确认
三十五、企业项目更稳的做法
可以同时:
Activiti Comment
+
workflow_task_operation
前者:
流程引擎记录
后者:
业务审计展示
三十六、审批记录 VO
前端最终通常需要:
[
{
"nodeName": "部门经理审批",
"operatorName": "张经理",
"action": "APPROVE",
"comment": "同意",
"startTime": "...",
"endTime": "..."
}
]
三十七、审批 API 不要让前端传 operatorId
错误:
{
"operatorId": 1,
"approved": true
}
正确:
{
"approved": true,
"comment": "同意"
}
operator:
后端从登录用户获取
三十八、完整审批事务
@Transactional
public void completeTask(
String taskId,
ApproveTaskDTO dto
) {
Long currentUserId =
currentUserService
.getCurrentUserId();
Task task =
findTask(
taskId
);
checkTaskAssignee(
task,
currentUserId
);
Map<String, Object> variables =
new HashMap<>();
variables.put(
"approved",
dto.getApproved()
);
// 记录审批意见
// 写业务操作日志
taskService.complete(
taskId,
variables
);
}
三十九、为什么 taskService.complete 放在哪里
必须结合:
业务状态
流程状态
审计记录
一起设计。
最好有:
Application Service
统一编排。
四十、审批完成后业务状态什么时候更新
例如:
审批通过后流程结束
业务:
APPROVED
可以通过:
ServiceTask
ExecutionListener
Application Service
更新。
四十一、推荐不要“猜流程是否结束”
审批后:
不要只看 approved=true
就认为:
业务完成
因为可能:
后面还有财务审批
四十二、如何知道流程是否结束
complete 后:
RuntimeService
查询 processInstance
如果:
不存在
再结合:
HistoryService
确认已结束。
四十三、更好的状态同步
可以把:
最终状态更新
放到:
End Event Listener
或:
结束节点对应业务 ServiceTask
语义更明确。
四十四、Task Listener 是什么
Task Listener:
监听 UserTask 生命周期
常见事件:
create
assignment
complete
delete
四十五、create 事件
Task 创建时:
触发
适合:
发送待办通知
记录任务创建
四十六、assignment
任务:
被分配 / 改变 Assignee
时触发。
四十七、complete
Task:
完成
时触发。
四十八、delete
Task:
被删除
时可能触发。
四十九、Task Listener 示例
经典写法:
@Component
public class TodoCreatedListener
implements TaskListener {
@Override
public void notify(
DelegateTask delegateTask
) {
String taskId =
delegateTask.getId();
String assignee =
delegateTask.getAssignee();
// 发通知
}
}
五十、BPMN 引用 Listener
常见:
<activiti:taskListener
event="create"
delegateExpression="${todoCreatedListener}"
/>
五十一、为什么推荐 delegateExpression
因为:
Listener 由 Spring 管理
就能注入:
Service
Mapper
消息服务
五十二、Task Listener 不要做什么
不推荐:
写几百行业务
复杂远程请求
长时间阻塞
因为:
它常处在流程事务中
慢操作可能:
拖慢 complete
五十三、通知更合理的思路
Task create:
发布业务事件
然后:
异步通知服务
去:
短信
邮件
WebSocket
五十四、Execution Listener
Execution Listener:
监听流程执行生命周期
常见:
start
end
take
五十五、流程启动监听
例如:
Process Start
可以:
记录流程启动日志
五十六、流程结束监听
例如:
Process End
可以:
更新业务状态 APPROVED
五十七、Sequence Flow take
流程走过一条连线:
take
可以监听。
但一般:
不要每条线都塞业务逻辑
五十八、Task Listener vs Execution Listener
Task Listener:
围绕 UserTask
Execution Listener:
围绕流程执行节点/连线
五十九、Service Task vs Listener
Service Task:
流程图中明确存在一个自动业务节点
Listener:
流程生命周期旁路逻辑
六十、什么时候用 Service Task
如果业务流程上:
确实有“自动更新结算单”
这个步骤很重要:
应该画在流程图
使用:
Service Task
六十一、什么时候用 Listener
例如:
任务创建后发通知
这不是主要业务节点:
可以 Listener
六十二、不要隐藏核心流程
如果业务真正依赖:
自动扣库存
却藏在:
Listener
流程图看不出来。
这会:
降低可维护性
六十三、Timer 是什么
Timer:
定时事件
用于:
超时
延期
定时启动
定时提醒
六十四、Timer Event 类型
常见:
Timer Start Event
Intermediate Timer Event
Boundary Timer Event
六十五、Timer Start Event
例如:
每天凌晨 2 点
启动某流程
六十六、Intermediate Timer
流程执行到某处:
等待一段时间
再继续。
六十七、Boundary Timer
挂在某任务边界:
任务超过 24 小时没完成
触发:
超时分支
六十八、审批超时案例
经理审批
│
├─ 正常完成
│ ↓
│ 下一节点
│
└─ 24 小时未完成
↓
超时提醒
六十九、中断型 Boundary Timer
如果:
Timer 触发
会中断原任务。
例如:
审批超时
↓
原任务取消
↓
转上级处理
七十、非中断型 Timer
Timer 触发:
原任务仍然继续存在
同时:
走提醒分支
适合:
催办
七十一、催办更适合哪种
通常:
非中断型 Boundary Timer
因为:
只是提醒
不应该取消原审批任务
七十二、Timer 配置时间
BPMN 可以使用:
timeDuration
timeDate
timeCycle
七十三、Duration
例如:
PT24H
表示:
24 小时
七十四、ISO 8601 Duration
常见:
PT30M
30 分钟
PT2H
2 小时
P1D
1 天
具体表达式:
按 BPMN / 引擎支持确认
七十五、Timer 不是 Java Thread.sleep
绝对不要:
Thread.sleep(
86400000
);
工作流 Timer:
会持久化为 Job
应用重启:
仍可恢复
七十六、Job
Timer 通常由:
Job Executor / Async Executor
执行。
七十七、为什么要了解 Job
如果 Timer:
一直不触发
就要排查:
Job Executor 是否运行
Job 是否创建
数据库 Job 表
时间配置
七十八、异步 Service Task
Activiti 还支持:
异步执行节点
让:
当前事务先提交
后面:
Job Executor
再执行。
七十九、为什么异步节点有价值
如果自动任务:
调用远程系统
失败:
可以通过 Job 重试机制
比一个超长本地事务:
更合理
八十、但异步带来什么
最终一致性
重试
重复执行
幂等
问题。
所以:
业务 Service 必须考虑幂等
八十一、会签是什么
会签:
Multi-Instance Approval
例如:
5 个评审人
都要审批
或者:
5 人中 3 人同意即可
八十二、Multi-Instance
BPMN 中一个 UserTask:
可以生成多个实例
例如:
reviewerIds
=
[1001,1002,1003]
流程引擎:
创建 3 个任务实例
八十三、会签两种主要模式
Sequential
串行多实例
Parallel
并行多实例
八十四、串行会签
A 审
↓
B 审
↓
C 审
同一个多实例任务:
按顺序
执行。
八十五、并行会签
A
B
C
同时收到:
审批任务
八十六、真实企业更常见
评审类:
并行
逐级审批:
通常直接多个 UserTask
而不是串行多实例。
八十七、会签核心变量
常见概念:
nrOfInstances
nrOfActiveInstances
nrOfCompletedInstances
八十八、nrOfInstances
总实例数。
例如:
5 人
则:
nrOfInstances = 5
八十九、nrOfCompletedInstances
已经完成几个。
九十、completionCondition
完成条件:
满足条件后
整个多实例活动结束
例如:
3 人完成即可
九十一、全员通过
可以要求:
nrOfCompletedInstances == nrOfInstances
但是:
这只表示全员完成
不代表全员“同意”
九十二、为什么还需要通过票数
每个人可能:
approved=true/false
所以需要:
approvedCount
或其他统计变量。
九十三、会签通过比例
例如:
同意人数 / 总人数 >= 0.6
则:
通过
九十四、会签最容易踩的坑
多个任务同时修改:
approvedCount
可能产生:
并发竞争
九十五、不要简单做
读 approvedCount
+1
写回
并行审批可能:
丢更新
九十六、更稳的思路
可以:
每个审批人记录独立审批结果
然后:
统计历史/业务表
决定是否满足条件。
或者使用引擎:
多实例相关变量机制
九十七、会签审批记录表
例如:
workflow_approval_record
字段:
process_instance_id
task_id
activity_id
user_id
result
comment
create_time
九十八、为什么业务表更适合统计
你可以直接:
COUNT(*)
统计:
APPROVE
REJECT
不依赖:
复杂引擎变量并发修改
九十九、加签是什么
加签:
运行过程中
临时增加审批人
比预先定义会签:
复杂
一百、前加签 / 后加签
业务中可能区分:
当前人审批前增加
当前人审批后增加
一百零一、Activiti 动态加签为什么难
因为流程实例已经:
运行到当前节点
现在要动态修改:
任务实例 / 多实例集合
需要理解:
Execution Tree
Multi-instance
一百零二、当前学习阶段
先掌握:
预定义 Multi-Instance 会签
不要第一版就做:
任意动态加签
一百零三、会签 BPMN 思路
User Task:
reviewTask
Collection:
reviewerList
Element Variable:
reviewer
Assignee:
${reviewer}
一百零四、为什么 Collection 是 List
启动流程:
variables.put(
"reviewerList",
List.of(
"1001",
"1002",
"1003"
)
);
流程:
遍历集合
每个 reviewer:
生成任务
一百零五、并行会签数据库表现
你可能看到:
同一个 processInstanceId
对应:
多个 ACT_RU_TASK
一百零六、为什么这是正常的
因为:
并行多实例
当前同时有:
多个活动任务
一百零七、并行网关和会签区别
并行网关:
流程中有多个不同分支 / 不同 Task
会签:
同一个 UserTask
产生多个实例
一百零八、什么时候并行网关
例如:
法务审批
财务审批
是:
不同角色、不同业务任务
适合:
Parallel Gateway
一百零九、什么时候会签
例如:
评审委员会 5 个人
都执行“专家评审”
适合:
Multi-Instance UserTask
一百一十、不要混淆
Parallel Gateway
复制流程分支
Multi-Instance
复制活动实例
一百一十一、串行审批和顺序多个 Task
例如:
组长
↓
经理
↓
总经理
这不是会签。
应该:
三个 UserTask
一百一十二、流程挂起
流程定义:
Suspend
表示:
暂时不允许新流程正常启动
一百一十三、流程实例挂起
某个具体 ProcessInstance:
Suspend
表示:
当前实例暂时停止推进
一百一十四、挂起场景
例如:
发现合同审批数据异常
管理员先:
挂起流程实例
等待:
人工调查
一百一十五、激活
恢复:
Activate
一百一十六、流程定义挂起 vs 实例挂起
Definition suspend:
针对流程模板
Instance suspend:
针对某一次流程
一百一十七、生产权限
挂起 / 激活:
应该只允许管理员
例如:
workflow:definition:suspend
workflow:instance:suspend
一百一十八、流程删除
Deployment 删除:
危险
Runtime Process 删除:
也危险
一百一十九、删除流程实例不等于驳回
再强调:
Delete
≠
Reject
驳回是:
业务正常流程路径
Delete 通常是:
异常管理操作
一百二十、流程取消
业务可能需要:
申请人撤销
推荐:
建立明确的“取消/撤销”业务语义
不要简单:
deleteProcessInstance()
之后什么都不记录。
一百二十一、如果确实终止流程
也应该:
更新业务 status
记录撤销人
记录撤销原因
记录时间
一百二十二、流程历史体系
Activiti 历史主要让我们知道:
流程从哪里开始
经过哪些节点
谁处理了任务
什么时候完成
流程什么时候结束
一百二十三、HistoricProcessInstance
代表:
历史流程实例
一百二十四、HistoricTaskInstance
代表:
历史用户任务
一百二十五、HistoricActivityInstance
代表:
历史活动节点
一百二十六、HistoricVariableInstance
历史变量:
取决于 History Level
一百二十七、审批历史查询
例如:
historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(
processInstanceId
)
.orderByHistoricTaskInstanceStartTime()
.asc()
.list();
一百二十八、为什么历史 Task 不等于所有节点
流程还可能经过:
StartEvent
Gateway
ServiceTask
EndEvent
这些:
不是 UserTask
需要:
HistoricActivityInstance
一百二十九、完整流程路径
historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(
processInstanceId
)
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
一百三十、流程图高亮核心数据
至少:
1. BPMN XML
2. 已执行 activityId
3. 当前 taskDefinitionKey
一百三十一、已执行节点
从:
HistoricActivityInstance
获取:
activityId
一百三十二、当前节点
从:
Task
获取:
taskDefinitionKey
一百三十三、为什么 activityId 必须稳定
BPMN:
<userTask
id="managerApprove"
...
/>
前端高亮:
managerApprove
所以 BPMN ID:
不要每次随便改
一百三十四、流程图高亮两种常见方式
方案一:
后端直接生成高亮流程图图片
方案二:
后端返回 BPMN XML + 高亮节点数据
前端 BPMN Viewer 自己高亮
一百三十五、方案一优点
前端简单
缺点:
交互弱
图片缩放体验一般
不同 Activiti 版本生成 API 可能变化
一百三十六、方案二优点
交互更好
前端可控制颜色
可点击节点
更适合现代 Vue 项目
一百三十七、方案二推荐数据
后端:
{
"xml": "...",
"completedActivityIds": [
"start",
"submitTask",
"managerApprove"
],
"currentActivityIds": [
"financeApprove"
]
}
一百三十八、前端
使用:
bpmn-js
加载:
BPMN XML
再根据:
activityId
添加 Marker。
一百三十九、为什么前端高亮更适合淘车湾
后面课程:
“我的代码及流程图高亮节点”
很可能需要:
可交互查看流程图
Vue + bpmn-js:
更灵活
一百四十、bpmn-js 是什么
一个常见的:
BPMN 查看/编辑前端库
可以:
Viewer
Modeler
一百四十一、Viewer
只看:
流程图
一百四十二、Modeler
可以:
编辑 BPMN
一百四十三、项目时间有限时
后端管理项目只需要:
Viewer
如果没有需求:
不要强行做在线流程设计器
一百四十四、流程图高亮步骤
GET BPMN XML
↓
Viewer.importXML()
↓
获取 Canvas
↓
遍历 completedActivityIds
↓
addMarker(completed)
↓
遍历 currentActivityIds
↓
addMarker(current)
一百四十五、CSS Marker
例如:
.highlight-completed
.highlight-current
由项目 UI:
定义样式
一百四十六、不要把中文 Name 当高亮 key
应该:
activityId
因为 Name:
可以重复
可以修改
一百四十七、当前节点可能不止一个
并行网关:
同时两个 Task
所以:
currentActivityIds
应该:
List
不是单个 String。
一百四十八、已执行节点也可能重复
循环 / 多实例:
同一个 activityId
执行多次
如果只做节点颜色:
Set 去重
即可。
如果做:
详细轨迹
需要保留:
每一次 HistoricActivityInstance
一百四十九、高亮连线
进阶:
Sequence Flow
也可以高亮。
一百五十、怎么知道走过哪条线
History:
有时可通过 Activity / Sequence Flow 历史
或者:
根据已执行节点顺序和 BPMN 模型推导
具体:
按版本和建模复杂度实现
一百五十一、简单项目优先高亮节点
因为:
足够展示流程状态
实现稳定
后续再:
加连线
一百五十二、审批历史页面
前端推荐:
Timeline
展示。
例如:
申请提交
↓
经理审批:通过
↓
财务审批:通过
↓
结束
一百五十三、Element Plus Timeline
可以:
el-timeline
一百五十四、审批历史 VO
public class ApprovalHistoryVO {
private String activityId;
private String activityName;
private String assignee;
private String assigneeName;
private String action;
private String comment;
private LocalDateTime startTime;
private LocalDateTime endTime;
}
一百五十五、不要让前端自己拼用户姓名
后端可以批量查询:
userId → userName
组装 VO。
一百五十六、避免 N+1
如果历史 30 条:
不要 30 次查 sys_user
应该:
收集 userId
↓
批量查询
↓
Map 组装
一百五十七、我的待办页面
接口:
GET /api/workflow/tasks/todo
参数:
pageNum
pageSize
processDefinitionKey
keyword
一百五十八、TodoTaskVO
public class TodoTaskVO {
private String taskId;
private String taskName;
private String taskDefinitionKey;
private String processInstanceId;
private String processDefinitionKey;
private String businessKey;
private String businessTitle;
private LocalDateTime createTime;
}
一百五十九、为什么 businessTitle 很重要
用户看到:
taskId=08f...
没有意义。
应该:
维修服务单 #WX20260910
一百六十、我的已办页面
GET /api/workflow/tasks/done
来源:
HistoricTaskInstance
一百六十一、已办是否包括当前又回到自己的任务
“已办”语义需要定义。
可以:
只要历史上完成过就算已办
即使:
流程后来又回到自己
仍在:
已办历史
一百六十二、我的发起
常见后台还会有:
我发起的
接口:
GET /api/workflow/processes/my-started
一百六十三、怎么知道谁发起
可以:
流程变量 starterId
或者:
业务表 applicantId
还可以:
Activiti authenticated user
具体按项目统一。
一百六十四、推荐业务系统
业务表本身:
creator_id
applicant_id
最稳定。
一百六十五、流程详情页
推荐 Tabs:
业务详情
审批记录
流程图
一百六十六、为什么这个 UI 很常见
用户既要:
看业务内容
也要:
看谁审批了
还要:
看流程走到哪里
一百六十七、审批操作区
如果当前用户有任务:
显示
同意
拒绝
转办
委派
如果只是查看历史:
不显示操作按钮
一百六十八、前端按钮权限两层判断
例如“同意”:
有 workflow:task:approve
+
当前用户确实拥有这个 Task
一百六十九、不要只靠权限码
管理员可能有:
workflow:task:approve
但不是当前 Task Assignee。
是否允许管理员代审:
要按业务定义
一百七十、完整审批接口设计
POST /api/workflow/tasks/{id}/claim
POST /api/workflow/tasks/{id}/release
POST /api/workflow/tasks/{id}/approve
POST /api/workflow/tasks/{id}/reject
POST /api/workflow/tasks/{id}/transfer
POST /api/workflow/tasks/{id}/delegate
POST /api/workflow/tasks/{id}/resolve
一百七十一、为什么 approve/reject 可以拆接口
相比一个:
complete
业务 API:
语义更清楚
一百七十二、也可以统一 complete
POST /tasks/{id}/complete
Body:
{
"action": "APPROVE",
"comment": "同意"
}
也可以。
一百七十三、两种方案怎么选
简单项目:
complete + action
可以。
强调 REST 业务语义:
approve/reject
也很清晰。
一百七十四、不要用
GET /approve?id=1
因为审批:
会修改系统状态
应该:
POST
一百七十五、审批接口事务
例如:
@Transactional
public void approve(
String taskId,
String comment
) {
// 查任务
// 校验权限
// 记录意见
// complete
// 同步业务
}
一百七十六、complete 后异常怎么办
如果:
同一个 DataSource
同一个 Spring 事务
业务数据库与 Activiti Core:
可以参与同一个本地事务
前提:
集成方式正确
一百七十七、不要 complete 后 catch 掉异常
否则:
流程可能推进
业务状态却没正确处理
一百七十八、事务越短越好
审批事务中不要:
同步调用慢短信 API
上传大文件
sleep
一百七十九、通知怎么做
事务成功后:
发布事件
然后:
异步通知
一百八十、Timer 也不要和业务定时任务混淆
Activiti Timer:
属于流程语义
XXL-Job:
属于通用分布式调度
后面第五阶段还会学:
XXL-Job
一百八十一、什么时候 Activiti Timer
例如:
“这个审批节点超过 2 天自动催办”
和流程节点:
强相关
用 Timer 很合理。
一百八十二、什么时候 XXL-Job
例如:
每天凌晨统计报表
不依赖某个 BPMN 实例:
更适合调度框架
一百八十三、流程定义管理
企业后台通常有:
流程定义列表
字段:
name
key
version
deploymentId
resourceName
suspended
deployTime
一百八十四、流程定义接口
GET /api/workflow/definitions
POST /api/workflow/definitions/deploy
POST /api/workflow/definitions/{id}/suspend
POST /api/workflow/definitions/{id}/activate
一百八十五、上传 BPMN
如果支持上传:
MultipartFile
后端:
RepositoryService
部署。
一百八十六、上传前校验
至少:
文件后缀
文件大小
BPMN 是否能解析
流程 Key 是否符合规范
一百八十七、不要允许任意用户部署流程
权限:
workflow:definition:deploy
通常:
管理员
一百八十八、流程定义版本
前端列表:
leaveApproval v1
leaveApproval v2
leaveApproval v3
一百八十九、显示最新版本
常见页面默认:
只显示最新版本
也可以:
展开查看历史版本
一百九十、流程实例管理
管理员页面:
运行中流程
字段:
instanceId
businessKey
definitionKey
startTime
currentTask
suspended
一百九十一、管理员操作
可:
挂起
激活
查看流程图
查看历史
危险操作:
终止
要:
额外确认 + 审计
一百九十二、流程终止原因
必须保存:
管理员终止
业务撤销
数据异常
重复提交
不能:
什么原因都没有
一百九十三、我的代码 + 流程图高亮
课程后面有:
“我的代码及流程图高亮节点分析及实现”
这部分核心结构可以提前准备:
ProcessDiagramVO
一百九十四、ProcessDiagramVO
public class ProcessDiagramVO {
private String bpmnXml;
private List<String>
completedActivityIds;
private List<String>
currentActivityIds;
}
一百九十五、后端获取 BPMN XML
RepositoryService:
根据 ProcessDefinitionId
读取 BPMN Resource
然后:
InputStream
→ String
返回前端。
一百九十六、为什么根据 ProcessDefinitionId
旧流程实例可能:
跑的是 v1
当前最新:
已经 v3
如果你按 Key 拿最新 BPMN:
高亮图就错了
一百九十七、必须使用实例对应 definitionId
ProcessInstance / HistoricProcessInstance:
有 processDefinitionId
用它获取:
对应版本 BPMN
一百九十八、这是流程版本高亮的关键
实例跑 v1
就显示 v1
实例跑 v3
就显示 v3
一百九十九、已完成 Activity 查询
List<HistoricActivityInstance> history =
historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(
processInstanceId
)
.finished()
.list();
二百、转换 ID
Set<String> completedIds =
history.stream()
.map(
HistoricActivityInstance
::getActivityId
)
.collect(
Collectors.toSet()
);
二百零一、当前 Task
List<Task> currentTasks =
taskService
.createTaskQuery()
.processInstanceId(
processInstanceId
)
.list();
二百零二、当前节点 IDs
List<String> currentIds =
currentTasks.stream()
.map(
Task
::getTaskDefinitionKey
)
.toList();
二百零三、为什么是 List
因为:
并行网关 / 会签
可能有多个当前任务。
二百零四、结束流程
如果当前 Task:
为空
并不一定:
异常
可能:
流程已经结束
也可能当前:
正在 ServiceTask / Timer
所以判断要结合:
ProcessInstance
History
二百零五、历史节点排序
审批历史:
按 startTime
通常适合。
但并行节点:
顺序可能重叠
前端要允许:
同一时间段多个节点
二百零六、并行审批展示
可以:
法务审批
├─ 张三:通过
财务审批
├─ 李四:通过
而不是强行:
一条线顺序
二百零七、会签展示
例如:
专家会签
├─ A:同意
├─ B:同意
├─ C:拒绝
└─ D:同意
最后:
3/4 同意
达到 60%
二百零八、会签前端不要把任务合并错
同一个:
taskDefinitionKey
可能有多个:
taskId
每个 taskId:
代表不同实例
二百零九、Task ID 唯一
审批操作必须使用:
taskId
不是只用:
taskDefinitionKey
二百一十、为什么
会签中:
reviewTask
可能同时有:
taskId A
taskId B
taskId C
二百一十一、TaskDefinitionKey 用于什么
主要:
识别 BPMN 节点类型
而 TaskId:
识别本次具体任务实例
二百一十二、类似类与对象
TaskDefinitionKey
≈ 节点模板标识
TaskId
≈ 某次任务实例唯一 ID
二百一十三、候选组与 RBAC
假设:
FINANCE
是流程候选组。
系统 RBAC:
sys_role.code = FINANCE
可以建立:
角色码 → candidateGroup
映射。
二百一十四、但有一个关键问题
Activiti 自己的 IdentityService:
未必知道你的 sys_user_role
如果直接:
taskCandidateUser(userId)
需要考虑:
引擎怎样获取用户组关系
二百一十五、企业常见两种做法
方案一:
把用户/组同步进 Activiti Identity
方案二:
自己根据 RBAC role codes
查 candidateGroup task
二百一十六、方案二思路
当前用户:
userId=1001
先查业务 RBAC:
roles=[FINANCE,MANAGER]
然后 TaskQuery:
候选组 in [FINANCE,MANAGER]
再加:
assignee=userId
组合待办。
二百一十七、为什么方案二容易和现有系统集成
不需要:
维护两套用户体系
但查询逻辑:
需要自己封装
二百一十八、待办组成
当前用户可能有:
已直接分配给我的 Task
+
我所属候选组的未领取 Task
二百一十九、前端可以分 Tabs
我的任务
待领取任务
或者:
统一待办
二百二十、统一待办要显示状态
例如:
待领取
处理中
二百二十一、Claim Button
只有:
Candidate Task
显示:
领取
二百二十二、Approve Button
只有:
当前用户 Assignee
显示:
审批
二百二十三、Release Button
只有:
当前用户已经领取
并且业务允许:
释放
才显示。
二百二十四、Transfer Button
是否所有人都能转办?
不一定。
可要求:
当前 Assignee
+
workflow:task:transfer
二百二十五、Delegate Button
同理:
当前 Assignee
+
workflow:task:delegate
二百二十六、流程操作权限码建议
workflow:task:list
workflow:task:claim
workflow:task:release
workflow:task:approve
workflow:task:reject
workflow:task:transfer
workflow:task:delegate
workflow:definition:list
workflow:definition:deploy
workflow:definition:suspend
workflow:instance:list
二百二十七、不要权限过细到失控
小项目:
可以适当合并
例如:
workflow:task:handle
包括:
approve/reject
二百二十八、权限设计目标
不是:
权限码越多越高级
而是:
能准确表达角色能力
二百二十九、前端任务详情 Dialog
可以显示:
业务标题
业务信息
当前节点
发起人
申请时间
审批历史
二百三十、审批表单
审批结果
审批意见
二百三十一、结果不要用自由文本
推荐:
APPROVE
REJECT
而不是:
用户手写“同意”
二百三十二、审批结果枚举
Java:
public enum ApprovalAction {
APPROVE,
REJECT,
TRANSFER,
DELEGATE
}
二百三十三、DTO
public class TaskActionDTO {
@NotNull
private ApprovalAction action;
@Size(
max = 1000
)
private String comment;
private Long targetUserId;
}
二百三十四、Action 校验
例如:
TRANSFER
必须:
targetUserId != null
二百三十五、REJECT
可以要求:
comment 必填
二百三十六、为什么拒绝意见建议必填
后续:
申请人需要知道为什么被拒
二百三十七、Service 分发
switch (
dto.getAction()
) {
case APPROVE ->
approve(...);
case REJECT ->
reject(...);
case TRANSFER ->
transfer(...);
case DELEGATE ->
delegate(...);
}
二百三十八、是不是所有流程都能用统一 Action
不一定。
复杂项目:
不同流程有不同业务动作
可以:
通用 WorkflowService
+
业务专用 Service
二百三十九、淘车湾项目建议
不要做一个:
万能审批 Controller
直接控制所有业务。
可以:
WorkflowTaskController
通用待办/历史/流程图
SettlementApprovalService
结算单审批业务
二百四十、为什么
流程引擎是:
通用基础设施
结算单:
有自己的业务规则
二百四十一、淘车湾审批示例
例如:
提交结算单
↓
服务顾问审核
↓
财务审核
↓
经理审批
↓
完成
二百四十二、金额分支
例如:
amount <= 10000
↓
财务通过
↓
结束
amount > 10000
↓
财务
↓
经理
↓
结束
二百四十三、BPMN
可以:
Finance UserTask
↓
Exclusive Gateway
├─ amount <= 10000 → End
└─ amount > 10000 → Manager UserTask
二百四十四、Reject
每个审批 Task:
拒绝
都可进入:
Rejected End Event
第一版最简单。
二百四十五、业务状态
DRAFT
PENDING_APPROVAL
APPROVED
REJECTED
二百四十六、流程变量
只放:
amount
financeUserId
managerUserId
approved
等流程必要数据。
二百四十七、业务详情
仍放:
settlement_order
自己的表。
二百四十八、BusinessKey
settlementOrderId
二百四十九、为什么不用订单号字符串也可以
可以使用:
业务唯一编号
只要:
稳定、唯一
二百五十、建议 BusinessKey
项目内统一:
业务主键 String
最简单。
二百五十一、流程表中的业务状态不同步怎么办
这是很常见的 Bug。
排查:
流程 complete 是否成功
业务状态更新在哪里
事务是否回滚
Listener 是否执行
ServiceTask 是否异常
二百五十二、流程卡住怎么办
排查:
当前 Runtime Task
Runtime Execution
流程变量
Gateway 条件
Job
Suspended 状态
日志异常
二百五十三、Task 没生成
可能:
流程没启动
前一节点没 complete
Gateway 无分支匹配
ServiceTask 异常
流程已结束
二百五十四、Candidate 查不到
检查:
candidateUsers
candidateGroups
用户组映射
Task 是否已被别人 claim
二百五十五、Claim 报错
可能:
任务已经被领取
当前用户不是候选人
任务不存在
二百五十六、Delegate 后直接 Complete
业务上可能不正确。
委派任务通常:
被委派人先 resolve
由 Owner:
最终 complete
具体 API 语义:
按当前版本验证
二百五十七、会签一直不结束
排查:
collection 数量
elementVariable
completionCondition
所有 Task 是否完成
审批统计变量是否正确
二百五十八、Timer 不触发
排查:
Timer 表达式
Job 是否生成
Async/Job Executor 是否开启
系统时间
时区
数据库状态
二百五十九、流程图显示最新版本错图
原因:
拿了 processDefinitionKey 最新版
正确:
拿实例对应 processDefinitionId
二百六十、流程图当前节点不高亮
检查:
Task.getTaskDefinitionKey()
是否和 BPMN:
UserTask id
一致。
二百六十一、流程图已办节点缺失
检查:
History Level
HistoricActivityInstance 查询
二百六十二、审批历史没有意见
说明:
Comment / 业务审批记录
没有保存或没有关联。
二百六十三、历史表不应该手改
同样:
不要 UPDATE ACT_HI_*
来“补历史”。
应该:
通过正确业务流程产生数据
二百六十四、流程历史和日志保留
生产系统可能涉及:
审计合规
所以:
不能随便清历史
二百六十五、性能:ACT_HI 表很大
需要:
索引
归档
History Level
分页
后期考虑。
二百六十六、待办查询必须分页
不要:
.list()
查所有任务。
应该:
listPage
或 Activiti 7:
Pageable
二百六十七、这和官方 TaskRuntime API 一致
Activiti 7 Runtime API:
tasks(Pageable)
就是:
分页查询任务
二百六十八、流程实例也分页
官方 ProcessRuntime:
processInstances(Pageable)
思想一致:
企业数据不能一次全查
二百六十九、经典 API 还是 Runtime API
课程理解:
都要认识
项目真正使用:
选一种主 API 风格
不要一个 Service:
一半 TaskRuntime
一半 TaskService
除非:
确实有明确原因
二百七十、为什么经典 API 仍很重要
大量:
历史查询
底层管理
老项目
网上资料
都基于:
RepositoryService
RuntimeService
TaskService
HistoryService
二百七十一、为什么 Activiti 7 推荐 Runtime API
官方设计目标包括:
更明确的外部 API
安全/身份集成
未来兼容
模块化
所以长期:
值得理解
二百七十二、项目版本一定要锁
Maven:
Activiti BOM
统一:
Activiti 组件版本
不要:
starter 一个版本
engine 另一个版本
api 又一个版本
二百七十三、为什么
会出现:
NoSuchMethodError
ClassNotFoundException
二百七十四、又回到 Maven 高级
排查:
mvn dependency:tree
检查:
Activiti
Spring
Spring Security
Jackson
版本。
二百七十五、SpringBoot 版本兼容
如果项目:
SpringBoot 3.x
旧 Activiti 7 教程:
不要直接复制
原因:
javax → jakarta
Spring API 版本变化
二百七十六、学习课程目标
你现在重点不是:
强行搭最新组合
而是:
掌握工作流原理
以后遇到:
Flowable
Camunda
Activiti
都能迁移。
二百七十七、为什么能迁移
它们大量共享:
BPMN 2.0 思想
Process Definition
Process Instance
User Task
Gateway
Variable
History
二百七十八、Activiti 最终知识树
Activiti
│
├─ BPMN
│ ├─ Event
│ ├─ UserTask
│ ├─ ServiceTask
│ ├─ Gateway
│ ├─ Timer
│ └─ MultiInstance
│
├─ Repository
│ ├─ Deployment
│ └─ ProcessDefinition
│
├─ Runtime
│ ├─ ProcessInstance
│ ├─ Execution
│ └─ Variable
│
├─ Task
│ ├─ Assignee
│ ├─ Candidate
│ ├─ Claim
│ ├─ Release
│ ├─ Transfer
│ ├─ Delegate
│ ├─ Resolve
│ └─ Complete
│
├─ Listener
│ ├─ TaskListener
│ └─ ExecutionListener
│
├─ History
│ ├─ Process
│ ├─ Task
│ └─ Activity
│
└─ Project
├─ RBAC
├─ BusinessKey
├─ Transaction
├─ Todo
├─ Done
└─ Diagram
二百七十九、练习 1:Candidate + Claim
BPMN:
Finance Task
candidateGroup=FINANCE
两个财务用户:
都能看到
A:
claim
确认:
B 不再能当未领取任务处理
二百八十、练习 2:Release
A 领取后:
release
观察:
Assignee 清空
二百八十一、练习 3:Transfer
A:
转办给 B
观察:
Task Assignee
变化。
二百八十二、练习 4:Delegate
A:
delegate 给 B
观察:
Owner
Assignee
区别。
B:
resolve
确认:
任务回 A
二百八十三、练习 5:Comment
审批时保存:
同意,预算合理
查询历史时:
展示意见
二百八十四、练习 6:Task Listener
任务创建:
打印待办通知
二百八十五、练习 7:Execution Listener
流程结束:
更新业务状态
二百八十六、练习 8:Parallel Gateway
法务
财务
同时任务。
观察:
ACT_RU_TASK
出现两条。
二百八十七、练习 9:Multi-Instance
3 个评审人
同时生成:
3 个 reviewTask
二百八十八、练习 10:会签完成条件
例如:
全部完成
再继续下一步。
二百八十九、练习 11:Timer
经理任务:
1 分钟后
触发提醒分支。
学习阶段用:
1 分钟
方便测试。
二百九十、练习 12:挂起
挂起:
Process Instance
尝试:
Complete
观察异常。
二百九十一、练习 13:历史任务
完成 3 个任务。
使用:
HistoryService
按时间查询。
二百九十二、练习 14:历史 Activity
打印:
activityId
activityName
activityType
startTime
endTime
二百九十三、练习 15:高亮数据
返回:
{
"completedActivityIds": [],
"currentActivityIds": []
}
二百九十四、练习 16:Vue BPMN Viewer
加载:
BPMN XML
根据:
activityId
添加高亮。
二百九十五、练习 17:并行当前节点
让流程停在:
法务 + 财务
确认:
currentActivityIds
有两个。
二百九十六、练习 18:审批历史 Timeline
Vue:
el-timeline
展示:
节点
办理人
意见
时间
二百九十七、练习 19:Todo API
返回:
任务 + businessKey + businessTitle
不要只返回:
Task Entity
二百九十八、练习 20:权限
普通用户:
尝试审批别人的 taskId
后端必须:
403
二百九十九、必须掌握任务流转
Claim
Release
Transfer
Delegate
Resolve
Complete
三百、必须掌握任务身份
Owner
Assignee
CandidateUser
CandidateGroup
三百零一、必须掌握 Listener
TaskListener
ExecutionListener
三百零二、必须掌握 Timer
Timer Start
Intermediate Timer
Boundary Timer
Interrupting
Non-Interrupting
三百零三、必须掌握并行和会签
Parallel Gateway
Multi-Instance
Sequential
Parallel
Completion Condition
三百零四、必须掌握 History
HistoricProcessInstance
HistoricTaskInstance
HistoricActivityInstance
三百零五、必须掌握流程图高亮
ProcessDefinitionId
BPMN XML
completedActivityIds
currentActivityIds
bpmn-js
三百零六、面试题 1
问:
Claim 和 Assignee 有什么关系?
答:
候选任务一开始可能没有具体 Assignee。
候选用户 Claim 任务后,
该用户成为任务的实际 Assignee,
之后由该用户继续处理任务。
三百零七、面试题 2
问:
Release 是什么?
答:
Release/Unclaim 表示把已经领取的候选任务释放回候选池。
任务 Assignee 会被清除,
其他符合候选条件的用户可以再次领取。
三百零八、面试题 3
问:
Transfer 和 Delegate 有什么区别?
答:
Transfer 是转办,
任务责任直接从 A 转移给 B。
Delegate 是委派,
A 仍然是 Owner,
B 只是暂时代办,
B Resolve 后任务通常会回到 A,
由 A 继续最终处理。
三百零九、面试题 4
问:
TaskListener 和 ExecutionListener 区别?
答:
TaskListener 主要监听 UserTask 生命周期,
例如 create、assignment、complete。
ExecutionListener 监听流程执行生命周期,
例如节点 start、end 或 SequenceFlow take。
三百一十、面试题 5
问:
ServiceTask 和 Listener 应该怎么选?
答:
如果一个自动操作属于明确的业务流程步骤,
应该优先使用 ServiceTask 体现在 BPMN 中。
如果只是任务创建通知、审计等横切生命周期行为,
可以使用 Listener。
核心业务不要全部隐藏在 Listener 中。
三百一十一、面试题 6
问:
Parallel Gateway 和 Multi-Instance 有什么区别?
答:
Parallel Gateway 用于并行执行多个不同流程分支。
Multi-Instance 用于让同一个活动产生多个实例,
例如一个专家评审任务同时分配给多名评审人。
三百一十二、面试题 7
问:
什么是会签?
答:
会签通常是一个审批活动由多个办理人共同处理。
在 BPMN 中常通过 Multi-Instance UserTask 实现,
可以串行或并行生成多个任务实例,
并通过完成条件决定整个会签节点何时结束。
三百一十三、面试题 8
问:
Timer Boundary Event 有什么作用?
答:
它可以挂在一个任务或活动边界上,
当指定时间到达时触发另一条流程路径。
例如审批超过 24 小时后自动催办或升级处理。
三百一十四、面试题 9
问:
为什么 Timer 不能用 Thread.sleep 代替?
答:
流程 Timer 会把等待状态持久化,
应用重启后仍能恢复。
Thread.sleep 只是阻塞当前线程,
不适合长时间业务等待,
应用重启后也无法保留等待状态。
三百一十五、面试题 10
问:
流程图高亮需要哪些数据?
答:
至少需要流程实例对应版本的 BPMN XML、
已经执行过的 activityId,
以及当前运行中的 taskDefinitionKey/activityId。
前端可使用 bpmn-js 加载 BPMN,
再根据这些 ID 添加不同高亮样式。
三百一十六、面试题 11
问:
为什么获取 BPMN XML 要用实例的 ProcessDefinitionId?
答:
同一个流程 Key 可能已经有多个版本。
一个旧流程实例可能运行在 v1,
而最新流程已经是 v3。
如果直接取最新流程定义,
展示出来的图可能和实例真实路径不一致。
因此应该使用该实例自己的 ProcessDefinitionId。
三百一十七、面试题 12
问:
为什么当前节点应该是 List?
答:
因为并行网关和并行会签可能同时存在多个当前任务。
一个流程实例并不保证任何时刻都只有一个 UserTask,
因此 currentActivityIds 应支持多个节点。
三百一十八、面试题 13
问:
RBAC 和 Workflow Task 权限有什么区别?
答:
RBAC 决定用户是否拥有某类功能,
例如是否拥有审批按钮权限。
Workflow Task 权限决定当前用户是否是
这一条具体任务的 Assignee 或 Candidate。
企业审批接口通常需要同时检查两者。
三百一十九、面试题 14
问:
为什么不建议把流程历史直接当全部业务审计?
答:
Activiti History 主要描述流程引擎发生过的事件。
业务审计还可能需要 IP、业务动作、目标用户、
业务备注、失败原因等自定义字段。
因此很多企业系统还会维护自己的审批/操作记录表。
三百二十、面试题 15
问:
Activiti 7 为什么有 TaskService 和 TaskRuntime 两套风格?
答:
经典 Engine API 包括 TaskService、RuntimeService 等。
Activiti 7 又提供了新的 TaskRuntime、ProcessRuntime API,
目标是提供更清晰的外部接口、安全集成和未来兼容路径。
官方没有简单废弃旧 API,
但长期更推荐理解和使用新的 Runtime API。
三百二十一、Activiti 两个课时总复习
第一部分:
Workflow
BPMN
Deployment
ProcessDefinition
ProcessInstance
Task
Variable
BusinessKey
RepositoryService
RuntimeService
TaskService
HistoryService
第二部分:
Claim
Release
Transfer
Delegate
Resolve
Comment
TaskListener
ExecutionListener
ServiceTask
Timer
Parallel Gateway
Multi-Instance
History
Process Diagram
三百二十二、完整审批系统总图
用户提交业务单
↓
业务表 insert
↓
startProcessInstance
↓
BusinessKey 关联
↓
流程进入 UserTask
↓
Candidate / Assignee
↓
我的待办
↓
Claim
↓
Approve / Reject
↓
Task Complete
↓
Gateway / ServiceTask / Next Task
↓
流程结束
↓
业务状态同步
↓
History
↓
已办 + 审批记录 + 流程图
三百二十三、完整权限总图
Current User
│
├─ RBAC Permission
│ ├─ workflow:task:list
│ ├─ workflow:task:approve
│ └─ workflow:task:transfer
│
└─ Workflow Identity
├─ Candidate
├─ Assignee
└─ Owner
三百二十四、完整流程高亮总图
processInstanceId
↓
HistoricProcessInstance
↓
processDefinitionId
↓
RepositoryService
↓
BPMN XML
processInstanceId
↓
HistoricActivityInstance
↓
completedActivityIds
processInstanceId
↓
TaskService
↓
currentActivityIds
三者
↓
Vue
↓
bpmn-js
↓
流程高亮
三百二十五、淘车湾后续会怎么用到
课程后面:
集成 Activiti 7
↓
审批流程定义页面
↓
流程审核信息
↓
我的待办
↓
我的已办
↓
流程图高亮
现在你已经提前掌握:
这些功能背后的核心原理
三百二十六、学习 Activiti 最容易犯的最大错误
不是:
API 记不住
而是:
没有把“业务状态”和“流程状态”分开
三百二十七、正确理解
业务表:
描述业务是什么
Activiti:
描述业务现在走到流程哪里
三百二十八、另一个最大错误
把:
TaskId
当成:
BusinessId
三百二十九、正确关系
BusinessId
业务主键
BusinessKey
业务与流程关联
ProcessInstanceId
本次流程实例
TaskId
当前某一次用户任务实例
三百三十、四个 ID 必须区分
Business ID
Process Definition ID
Process Instance ID
Task ID
三百三十一、最后再用类比记忆
ProcessDefinition
≈ 类
ProcessInstance
≈ 对象
UserTask Definition
≈ 方法/步骤定义
Task
≈ 本次运行产生的具体待办
BusinessKey
≈ 流程对象关联的业务主键
三百三十二、本章最终总结
Activiti 第二部分最重要的是:
从“能跑流程”
升级为
“能做审批系统”
任务流转:
Claim
Release
Transfer
Delegate
Resolve
Complete
任务身份:
Candidate
Assignee
Owner
自动化:
ServiceTask
TaskListener
ExecutionListener
Timer
复杂审批:
Parallel Gateway
Multi-Instance
会签
历史:
HistoricProcessInstance
HistoricTaskInstance
HistoricActivityInstance
流程图:
实例对应 BPMN XML
+
completedActivityIds
+
currentActivityIds
业务安全:
RBAC
+
Task Instance Permission
业务一致性:
业务表
+
Activiti
+
Spring Transaction
最重要的工程原则:
1. 不要直接改 ACT_* 表
2. 不要信任前端 userId
3. 不要把业务数据全塞流程变量
4. 不要把流程删除当驳回
5. 不要把转办和委派混为一谈
6. 不要把并行网关和会签混为一谈
7. 不要用最新 ProcessDefinition 给旧实例画图
8. 不要只做前端审批权限
9. 不要让 Listener 变成业务垃圾桶
10. 不要一次把所有复杂审批功能全做完
到这里:
第三阶段两个 Activiti 课程单元
已经完成
按照课程表,下一篇进入:
Git 工具使用
将系统整理:
Git 工作区 / 暂存区 / 仓库
init / add / commit
branch
merge
rebase
remote
clone
pull
fetch
push
冲突解决
.gitignore
reset
revert
stash
tag
GitHub / Gitee
团队协作流程
企业分支规范
IDEA Git 操作
Cursor Git 使用
常见误操作恢复
官方参考
Activiti 7 官方开发指南重点说明:
经典 Engine API 仍可使用
新的 Runtime API
包括:
ProcessRuntime
TaskRuntime
TaskRuntime 中可以进行:
task query
create
claim
release
complete
update
delete
因此本章:
领取
释放
完成任务
这些概念和 Activiti 7 Runtime API 是直接对应的。
注意:
不同 7.x 版本具体 Builder / Payload 类名
可能存在差异。
真正写项目时,
必须以当前使用版本 API 为准,
不要只凭旧博客复制代码。