Activiti7第二部分_任务流转_会签_监听器_Timer_流程高亮实战

O泡李华 8

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 为准,
不要只凭旧博客复制代码。