Activiti 从零到精通验收清单
Activiti 不能只学成“画 BPMN 图 + 调 taskService.complete”。商业审批系统真正难的是:流程图如何驱动业务状态,待办已办如何准确查询,审批权限怎么校验,驳回撤回怎么不破坏轨迹,流程版本怎么兼容,业务表和流程表怎么保持一致,线上任务卡住怎么排查。
本页按“是什么 -> 为什么 -> 怎么工作 -> 不这样会怎样 -> Demo -> 商业场景 -> 排查 -> 面试”的方式,把工作流知识串成一套可验收的学习清单。
最终学习目标
学完 Activiti 模块,至少要能做到:
| 能力 | 合格标准 | 不合格表现 |
|---|---|---|
| 工作流选型 | 能区分工作流、状态机、任务编排 | 所有状态流转都想上 Activiti |
| BPMN 建模 | 能解释开始、结束、用户任务、网关、连线、变量 | 只会拖组件,不懂运行含义 |
| 引擎对象 | 能讲清流程定义、实例、执行实例、任务、变量、历史 | 流程定义和流程实例混淆 |
| 表结构 | 能区分 ACT_RE、ACT_RU、ACT_HI、业务表 | 待办已办查错表 |
| 任务流转 | 能写启动、待办、认领、完成、已办、轨迹 Demo | 只会完成任务,不做权限 |
| 网关 | 能解释排他、并行、包容网关和卡住原因 | 条件写错不知道查变量 |
| 业务一致性 | 能设计 businessKey、processInstanceId、业务状态、审批记录 | 流程走完业务状态没变 |
| 扩展动作 | 能设计驳回、撤回、转办、委派、加签、会签 | 随意跳节点破坏轨迹 |
| 版本管理 | 能解释旧实例走旧定义、新实例走新定义 | 直接删除旧流程定义 |
| 生产排查 | 能排查无待办、卡网关、历史缺失、变量丢失、状态不一致 | 只能重启或改库 |
总学习路线
flowchart TD
A["判断是否需要工作流"] --> B["学习 BPMN 基础元素"]
B --> C["理解流程定义和流程实例"]
C --> D["理解运行时表和历史表"]
D --> E["启动流程和关联业务表"]
E --> F["待办、认领、完成、已办"]
F --> G["网关、变量、条件表达式"]
G --> H["驳回、撤回、转办、加签、会签"]
H --> I["流程版本和部署"]
I --> J["业务一致性和事务边界"]
J --> K["生产排查和面试闭环"]阶段一:工作流到底解决什么
是什么
工作流引擎把流程规则从业务代码中抽出来,用 BPMN 描述节点、处理人、条件分支、并行分支和结束条件,再由引擎在运行时生成任务、推进流程、记录历史。
为什么需要
如果不用工作流,复杂审批通常会变成大量硬编码:
if (amount <= 10000) {
status = "MANAGER_APPROVE";
} else if (amount <= 50000) {
status = "DIRECTOR_APPROVE";
} else {
status = "CEO_APPROVE";
}这种写法的问题:
- 流程规则散落在代码里,业务方看不懂。
- 加一个节点要改代码、测试、发布。
- 待办、已办、轨迹、驳回、撤回都要自己实现。
- 多版本流程并存困难。
- 审计留痕容易漏。
工作流和状态机怎么选
| 场景 | 更适合 | 原因 |
|---|---|---|
| 订单待支付、已支付、已取消 | 状态机 | 状态少、转换规则固定 |
| 请假、报销、合同审批 | 工作流 | 多角色、多节点、待办、留痕 |
| 医疗数据资产发布审批 | 工作流 | 数据质量、安全、负责人多级审核 |
| 微服务补偿事务 | Saga/消息 | 关注服务事务补偿,不是人工审批 |
| 高频交易状态 | 状态机 | 工作流引擎开销较重 |
阶段二:BPMN 元素怎么工作
核心元素
| 元素 | 含义 | 运行时发生什么 |
|---|---|---|
| Start Event | 流程开始 | 创建流程实例 |
| End Event | 流程结束 | 流程实例结束,运行时数据清理 |
| User Task | 人工任务 | 在 ACT_RU_TASK 生成待办 |
| Sequence Flow | 连线 | 控制执行方向 |
| Exclusive Gateway | 排他网关 | 多条路只走一条 |
| Parallel Gateway | 并行网关 | 多条路都走,汇聚时等待全部到达 |
| Inclusive Gateway | 包容网关 | 满足条件的多条路都走 |
| Process Variable | 流程变量 | 用于网关判断和上下文 |
BPMN 不是图片
BPMN 文件本质是 XML。图只是可视化,真正部署的是 XML 中的流程定义、节点 ID、连线条件、候选人表达式。
flowchart TD
A["BPMN 设计器画图"] --> B["生成 BPMN XML"]
B --> C["部署到 Activiti"]
C --> D["解析为流程定义"]
D --> E["运行时按节点和连线执行"]如果节点 ID、流程 key、变量名随便改,代码里的查询、启动和条件表达式都会受影响。
阶段三:核心对象和表结构
对象关系
flowchart TD
A["Deployment 部署"] --> B["ProcessDefinition 流程定义"]
B --> C["ProcessInstance 流程实例"]
C --> D["Execution 执行实例"]
C --> E["Task 用户任务"]
C --> F["Variable 流程变量"]
E --> G["HistoricTaskInstance 历史任务"]
C --> H["HistoricProcessInstance 历史流程"]
I["业务表 businessKey"] --> C类比理解
| 概念 | 类比 | 例子 |
|---|---|---|
| 流程定义 | 类 | 请假流程模板 |
| 流程实例 | 对象 | 张三这一次请假申请 |
| 执行实例 | 当前执行路径 | 流程走到主管审批 |
| 任务 | 待办事项 | 主管需要审批 |
| 变量 | 上下文 | 请假天数、是否通过 |
| 历史 | 审计记录 | 谁何时处理了什么 |
表前缀
| 前缀 | 含义 | 常见表 |
|---|---|---|
ACT_RE_* | Repository,流程定义和资源 | 部署、BPMN、流程定义 |
ACT_RU_* | Runtime,运行中数据 | 任务、执行实例、变量 |
ACT_HI_* | History,历史数据 | 已办、轨迹、历史变量 |
ACT_ID_* | Identity,用户组织 | 用户、组 |
ACT_GE_* | General,通用数据 | 二进制资源、属性 |
运行时和历史表流转
flowchart TD
A["启动流程"] --> B["写 ACT_RU_EXECUTION"]
B --> C["生成用户任务 ACT_RU_TASK"]
C --> D["完成任务"]
D --> E["写 ACT_HI_TASKINST"]
D --> F["写 ACT_HI_ACTINST"]
D --> G{"流程是否结束"}
G -- "否" --> C
G -- "是" --> H["清理运行时数据"]
H --> I["历史数据保留"]待办查运行时任务,已办查历史任务。查错表会导致“任务明明处理过但查不到”或“已办列表只显示当前待办”。
阶段四:业务表和流程表边界
Activiti 管流程运行,不替代业务系统。
| 数据 | 放业务表 | 放流程表 | 原因 |
|---|---|---|---|
| 申请标题、金额、原因 | 是 | 否 | 业务查询和报表依赖 |
| 业务状态 | 是 | 可辅助 | 页面和业务逻辑最终看业务表 |
| 当前流程节点 | 可冗余 | 是 | 业务列表展示需要 |
| 网关判断变量 | 可冗余 | 是 | 引擎执行需要 |
| 审批意见 | 是 | 可写 comment | 业务展示字段更灵活 |
| 附件、签名 | 是 | 否 | 流程变量不适合大对象 |
最小业务表
create table asset_publish_apply (
id bigint primary key,
dataset_id bigint not null,
apply_user_id bigint not null,
status varchar(32) not null,
process_instance_id varchar(64),
current_node_name varchar(128),
sensitive_level varchar(32),
created_at datetime not null,
updated_at datetime not null
);
create table asset_approve_record (
id bigint primary key,
apply_id bigint not null,
task_id varchar(64) not null,
approver_id bigint not null,
action varchar(32) not null,
comment varchar(500),
created_at datetime not null
);为什么要保存 businessKey
flowchart TD
A["业务单据 id=1001"] --> B["startProcessInstanceByKey"]
B --> C["businessKey=1001"]
C --> D["流程实例"]
D --> E["从流程反查业务单"]
A --> F["保存 processInstanceId"]
F --> G["从业务单查流程轨迹"]没有 businessKey,后续从任务、流程实例反查业务单会很麻烦,容易靠流程变量硬凑。
阶段五:启动流程全过程
执行链路
flowchart TD
A["校验业务单据"] --> B["准备流程变量"]
B --> C["启动流程实例"]
C --> D["传入 businessKey"]
D --> E["引擎创建运行时数据"]
E --> F["生成第一个用户任务"]
F --> G["业务表保存 processInstanceId"]
G --> H["业务状态改为审批中"]Demo:启动数据资产发布审批
@Service
public class AssetWorkflowService {
private final RuntimeService runtimeService;
private final AssetApplyMapper assetApplyMapper;
@Transactional
public String startPublishApply(Long applyId, Long userId, String sensitiveLevel) {
AssetPublishApply apply = assetApplyMapper.selectById(applyId);
if (apply == null) {
throw new IllegalArgumentException("申请单不存在");
}
if (!"DRAFT".equals(apply.getStatus())) {
throw new IllegalStateException("只有草稿状态可以发起审批");
}
Map<String, Object> variables = new HashMap<>();
variables.put("applyUserId", userId.toString());
variables.put("sensitiveLevel", sensitiveLevel);
variables.put("needSecurityReview", "HIGH".equals(sensitiveLevel));
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"asset_publish_process",
applyId.toString(),
variables
);
apply.setProcessInstanceId(instance.getProcessInstanceId());
apply.setStatus("APPROVING");
apply.setCurrentNodeName("数据管理员审核");
assetApplyMapper.updateById(apply);
return instance.getProcessInstanceId();
}
}不这样会怎样
| 问题 | 后果 |
|---|---|
| 启动流程和更新业务表不在同一事务 | 流程启动了但业务仍是草稿,或业务审批中但无流程 |
| 不传 businessKey | 后续任务无法稳定关联业务单 |
| 变量名和 BPMN 条件不一致 | 网关不走预期分支 |
| 把完整业务对象放变量 | 变量表膨胀,序列化兼容风险 |
阶段六:待办、认领、完成、已办
任务类型
| 类型 | 含义 | 操作 |
|---|---|---|
| assignee | 明确处理人 | 可直接完成 |
| candidate user | 候选用户 | 通常先 claim |
| candidate group | 候选组 | 组内用户可认领 |
待办查询 Demo
public List<TaskDTO> todoList(Long userId, List<String> roleCodes) {
List<Task> assigned = taskService.createTaskQuery()
.taskAssignee(userId.toString())
.active()
.list();
List<Task> candidate = taskService.createTaskQuery()
.taskCandidateGroupIn(roleCodes)
.active()
.list();
return Stream.concat(assigned.stream(), candidate.stream())
.map(task -> new TaskDTO(
task.getId(),
task.getName(),
task.getProcessInstanceId(),
task.getCreateTime()
))
.collect(Collectors.toList());
}认领任务 Demo
@Transactional
public void claimTask(String taskId, Long userId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new IllegalArgumentException("任务不存在");
}
if (task.getAssignee() != null) {
throw new IllegalStateException("任务已被认领");
}
taskService.claim(taskId, userId.toString());
}完成任务 Demo
@Transactional
public void completeTask(String taskId, Long approverId, boolean approved, String comment) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new IllegalArgumentException("任务不存在或已处理");
}
checkApprovePermission(task, approverId);
String processInstanceId = task.getProcessInstanceId();
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
Long applyId = Long.valueOf(instance.getBusinessKey());
taskService.addComment(taskId, processInstanceId, comment);
Map<String, Object> variables = new HashMap<>();
variables.put("approved", approved);
variables.put("approverId", approverId.toString());
taskService.complete(taskId, variables);
approveRecordMapper.insert(buildRecord(applyId, taskId, approverId, approved, comment));
syncBusinessStatus(applyId, processInstanceId, approved);
}完成任务前必须校验权限。前端隐藏按钮不是安全控制,用户可以直接调用接口。
已办查询
public List<HistoricTaskInstance> doneList(Long userId) {
return historyService.createHistoricTaskInstanceQuery()
.taskAssignee(userId.toString())
.finished()
.orderByHistoricTaskInstanceEndTime()
.desc()
.list();
}阶段七:网关和变量
排他网关
多条路只走一条,适合“金额不同走不同审批人”。
flowchart TD
A["主管审批"] --> B{"金额"}
B -- "amount <= 10000" --> C["流程结束"]
B -- "amount > 10000" --> D["总监审批"]
D --> E["流程结束"]并行网关
多条路都走,并在汇聚点等待全部完成。
flowchart TD
A["提交申请"] --> B["并行拆分"]
B --> C["数据质量审核"]
B --> D["安全审核"]
C --> E["并行汇聚"]
D --> E
E --> F["负责人终审"]包容网关
满足条件的多条路都走,条件不满足的分支不走。它比并行网关灵活,也更难排查。
常见问题
| 问题 | 后果 | 排查 |
|---|---|---|
| 变量名写错 | 条件不匹配 | 查 ACT_RU_VARIABLE |
| 类型不一致 | 表达式比较失败 | 确认 String、Integer、Boolean |
| 排他条件重叠 | 走了非预期分支 | 条件互斥,增加默认流 |
| 并行分支缺汇聚 | 流程提前或卡住 | 检查并行网关成对使用 |
| 包容网关条件复杂 | 线上排查困难 | 简化建模或拆流程 |
阶段八:驳回、撤回、转办、委派、加签、会签
能力边界
| 能力 | 含义 | 推荐设计 |
|---|---|---|
| 驳回 | 审批不通过 | 简单场景走拒绝分支,复杂场景记录业务动作 |
| 撤回 | 发起人撤回 | 校验当前节点、发起人、流程未结束,再终止或回退 |
| 转办 | 把任务交给别人处理 | 改 assignee 并记录操作 |
| 委派 | 请别人协助,完成后回到委派人 | 明确委派和完成语义 |
| 加签 | 临时增加审批人 | 简单用业务审批记录,复杂才动态节点 |
| 会签 | 多人共同审批 | 多实例任务,定义通过比例和否决规则 |
驳回流程建议
flowchart TD
A["审批人处理"] --> B{"是否通过"}
B -- "通过" --> C["进入下一节点"]
B -- "拒绝" --> D["记录拒绝意见"]
D --> E["业务状态改为已驳回"]
E --> F["通知发起人修改"]不要轻易开放“任意跳转到任意节点”。任意跳转会破坏 BPMN 语义,历史轨迹也很难解释。
撤回流程建议
flowchart TD
A["发起人申请撤回"] --> B["校验是否发起人"]
B --> C["校验流程未结束"]
C --> D["校验当前节点允许撤回"]
D --> E["终止或回退流程"]
E --> F["业务状态改为已撤回"]
F --> G["记录撤回日志"]阶段九:流程部署和版本
版本原理
同一个流程 key 每次部署会生成一个新版本。旧流程实例默认继续按旧定义执行,新实例通常使用最新定义。
flowchart TD
A["asset_publish_process v1"] --> B["旧实例 1001"]
C["重新部署 BPMN"] --> D["asset_publish_process v2"]
D --> E["新实例 2001"]
B --> F["继续按 v1 走"]
E --> G["按 v2 走"]生产注意
| 问题 | 正确做法 |
|---|---|
| 旧实例未结束 | 不要删除旧定义 |
| 表单字段变化 | 新旧表单兼容 |
| 变量名变化 | 旧流程条件仍要可用 |
| 节点 ID 变化 | 历史轨迹和跳转逻辑要评估 |
| 大量未完成实例 | 评估是否迁移,不能默认强迁 |
阶段十:业务一致性和事务边界
为什么会不一致
flowchart TD
A["完成任务"] --> B["Activiti 推进流程"]
B --> C["业务表更新状态"]
C --> D{"是否同一事务"}
D -- "否" --> E["可能流程成功业务失败"]
D -- "是" --> F["一起提交或一起回滚"]典型不一致:
- 流程实例启动成功,业务单仍是草稿。
- 业务单显示审批中,但没有流程实例。
- 任务完成了,业务状态仍显示待审核。
- 流程结束了,业务没有发布或归档。
设计原则
| 原则 | 说明 |
|---|---|
| 流程动作和业务动作同事务 | 启动、完成、终止都要考虑 |
| 业务表保存最终状态 | 页面和业务判断不直接依赖流程表 |
| 流程变量只保存小字段 | 网关判断需要什么存什么 |
| 审批记录业务自有 | 不完全依赖引擎历史表 |
| 异常要可补偿 | 有对账和修复脚本 |
商业常用场景
医疗数据资产发布审批
flowchart TD
A["科室提交数据集"] --> B["数据管理员审核"]
B --> C{"数据质量通过?"}
C -- "否" --> D["驳回修改"]
C -- "是" --> E["安全审核"]
E --> F{"包含敏感字段?"}
F -- "是" --> G["脱敏方案确认"]
F -- "否" --> H["负责人终审"]
G --> H
H --> I{"终审通过?"}
I -- "是" --> J["发布资产目录"]
I -- "否" --> D业务设计:
- 数据集表保存数据集基础信息。
- 申请表保存流程实例、状态、当前节点。
- 审批记录表保存每次处理动作。
- 流程变量保存数据等级、是否敏感、是否通过。
- 发布资产目录是业务动作,不要只依赖流程结束事件。
- 敏感数据审批要结合数据权限和审计。
合同审批
合同审批常见条件是金额、合同类型、供应商风险等级。大金额走法务和财务,小金额简化流程。流程变量只保存金额、类型、风险等级,合同正文和附件放业务文件表。
工单流转
工单适合用工作流管理多角色流转,例如客服受理、技术处理、质检回访。注意 SLA 超时提醒可以用定时任务或事件,不要把所有提醒逻辑硬塞进流程图。
生产排查流程
没有待办
flowchart TD
A["没有待办"] --> B["查 ACT_RU_TASK 是否有任务"]
B --> C{"有任务?"}
C -- "否" --> D["流程可能没到用户任务或已结束"]
C -- "是" --> E["查 assignee/candidate"]
E --> F["查用户角色是否匹配"]
F --> G["查任务是否被别人认领"]流程卡住
flowchart TD
A["流程卡住"] --> B["查流程实例是否运行中"]
B --> C["查当前 execution 活动节点"]
C --> D["查是否在网关"]
D --> E["查变量名和变量类型"]
E --> F["查并行分支是否全部到达"]
F --> G["查是否有异步任务失败"]业务状态和流程状态不一致
flowchart TD
A["状态不一致"] --> B["查业务表 status"]
B --> C["查 processInstanceId"]
C --> D["查流程运行时或历史"]
D --> E["查最近审批记录"]
E --> F["查事务日志和异常日志"]
F --> G["执行补偿或人工修复"]常用排查证据
| 现象 | 查什么 |
|---|---|
| 待办没有 | ACT_RU_TASK、assignee、candidate、角色 |
| 网关不走 | ACT_RU_VARIABLE、BPMN 条件表达式 |
| 已办没有 | ACT_HI_TASKINST、history level |
| 流程结束但业务未更新 | 业务表、审批记录、事务日志 |
| 新流程不生效 | 流程 key、版本、部署记录 |
| 任务被别人处理 | 历史任务、审批记录、操作日志 |
面试标准回答
Activiti 是什么
Activiti 是基于 BPMN 的工作流引擎。BPMN 部署后形成流程定义,业务发起时创建流程实例,运行时根据流程定义生成用户任务、保存变量、判断网关并记录历史。它适合审批、工单、合同审核、数据资产发布这类多节点、多角色、需要留痕的业务。业务系统仍然负责业务表、权限、表单、通知和最终业务状态。流程定义和流程实例区别
流程定义是部署后的流程模板,类似类;流程实例是某一次真实业务发起后的运行过程,类似对象。一个流程定义可以创建多个流程实例。流程定义有版本,新部署的定义通常影响新发起流程,旧实例默认继续按旧版本执行。业务表和流程表怎么关联
业务表保存业务主键、业务状态、当前节点和 processInstanceId。启动流程时把业务主键作为 businessKey 传给流程实例。这样既能从业务单据查流程轨迹,也能从流程实例反查业务数据。流程变量只保存网关判断需要的小字段,不把完整业务对象放进流程变量。待办和已办怎么查
待办查运行时任务表,对应当前还没有完成的任务,通常按 assignee、candidate user、candidate group 过滤。已办查历史任务表,对应用户已经处理过的任务。待办和已办的数据来源不同,流程结束后运行时任务会清理,但历史任务会保留。流程卡住怎么排查
我会先查流程实例是否还在运行,再查 ACT_RU_TASK 是否有当前用户任务。如果没有任务,就查 execution 当前卡在哪个活动,重点看是否卡在网关、并行汇聚或异步任务。网关问题重点查变量名、变量类型和条件表达式;并行问题看分支是否全部到达;业务状态不一致则查流程操作和业务表更新是否在同一事务中。学懂验收问题
下面问题答不上来,说明还没有真正学懂:
- 工作流和状态机怎么选?
- BPMN 图为什么不是普通图片?
- 流程定义和流程实例有什么区别?
- Execution 和 Task 是什么关系?
- 待办和已办分别查哪些表?
- 为什么业务数据不能都放流程变量?
- businessKey 和 processInstanceId 分别解决什么?
- assignee、candidate user、candidate group 有什么区别?
- 排他网关、并行网关、包容网关分别怎么工作?
- 并行网关为什么会卡住?
- 驳回和撤回为什么不能随意跳节点?
- 流程版本升级后旧实例怎么办?
- 流程完成但业务状态没变怎么排查?
- Activiti 能不能替代权限系统?
- 医疗数据资产审批如何设计业务表和流程变量?
关联知识点跳转
| 主题 | 继续学习 |
|---|---|
| 工作流总览 | 工作流 |
| Activiti 总览 | 从零学习路线与工作原理总览 |
| 生产路线 | Activiti 从零到生产级掌握 |
| 商业场景 | Activiti 商业场景训练营 |
| 流程设计 | BPMN可执行模型、事件、任务、子流程与会签 |
| 网关 | Execution、分支选择与汇聚原理 |
| 任务 API | 任务生命周期、认领、委派、并发与查询 |
| 业务一致性 | 本地事务、跨库、Outbox、补偿与对账 |
| 部署与版本 | 部署、版本、缓存与生产发布 |
| 面试 | Activiti 面试题 |
| Spring 事务 | Spring 事务 |
| 分布式事务 | 分布式事务 |
