Skip to content

Activiti 从零到精通验收清单

Activiti 不能只学成“画 BPMN 图 + 调 taskService.complete”。商业审批系统真正难的是:流程图如何驱动业务状态,待办已办如何准确查询,审批权限怎么校验,驳回撤回怎么不破坏轨迹,流程版本怎么兼容,业务表和流程表怎么保持一致,线上任务卡住怎么排查。

本页按“是什么 -> 为什么 -> 怎么工作 -> 不这样会怎样 -> Demo -> 商业场景 -> 排查 -> 面试”的方式,把工作流知识串成一套可验收的学习清单。

最终学习目标

学完 Activiti 模块,至少要能做到:

能力合格标准不合格表现
工作流选型能区分工作流、状态机、任务编排所有状态流转都想上 Activiti
BPMN 建模能解释开始、结束、用户任务、网关、连线、变量只会拖组件,不懂运行含义
引擎对象能讲清流程定义、实例、执行实例、任务、变量、历史流程定义和流程实例混淆
表结构能区分 ACT_REACT_RUACT_HI、业务表待办已办查错表
任务流转能写启动、待办、认领、完成、已办、轨迹 Demo只会完成任务,不做权限
网关能解释排他、并行、包容网关和卡住原因条件写错不知道查变量
业务一致性能设计 businessKey、processInstanceId、业务状态、审批记录流程走完业务状态没变
扩展动作能设计驳回、撤回、转办、委派、加签、会签随意跳节点破坏轨迹
版本管理能解释旧实例走旧定义、新实例走新定义直接删除旧流程定义
生产排查能排查无待办、卡网关、历史缺失、变量丢失、状态不一致只能重启或改库

总学习路线

mermaid
flowchart TD
    A["判断是否需要工作流"] --> B["学习 BPMN 基础元素"]
    B --> C["理解流程定义和流程实例"]
    C --> D["理解运行时表和历史表"]
    D --> E["启动流程和关联业务表"]
    E --> F["待办、认领、完成、已办"]
    F --> G["网关、变量、条件表达式"]
    G --> H["驳回、撤回、转办、加签、会签"]
    H --> I["流程版本和部署"]
    I --> J["业务一致性和事务边界"]
    J --> K["生产排查和面试闭环"]

阶段一:工作流到底解决什么

是什么

工作流引擎把流程规则从业务代码中抽出来,用 BPMN 描述节点、处理人、条件分支、并行分支和结束条件,再由引擎在运行时生成任务、推进流程、记录历史。

为什么需要

如果不用工作流,复杂审批通常会变成大量硬编码:

java
if (amount <= 10000) {
    status = "MANAGER_APPROVE";
} else if (amount <= 50000) {
    status = "DIRECTOR_APPROVE";
} else {
    status = "CEO_APPROVE";
}

这种写法的问题:

  1. 流程规则散落在代码里,业务方看不懂。
  2. 加一个节点要改代码、测试、发布。
  3. 待办、已办、轨迹、驳回、撤回都要自己实现。
  4. 多版本流程并存困难。
  5. 审计留痕容易漏。

工作流和状态机怎么选

场景更适合原因
订单待支付、已支付、已取消状态机状态少、转换规则固定
请假、报销、合同审批工作流多角色、多节点、待办、留痕
医疗数据资产发布审批工作流数据质量、安全、负责人多级审核
微服务补偿事务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、连线条件、候选人表达式。

mermaid
flowchart TD
    A["BPMN 设计器画图"] --> B["生成 BPMN XML"]
    B --> C["部署到 Activiti"]
    C --> D["解析为流程定义"]
    D --> E["运行时按节点和连线执行"]

如果节点 ID、流程 key、变量名随便改,代码里的查询、启动和条件表达式都会受影响。

阶段三:核心对象和表结构

对象关系

mermaid
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,通用数据二进制资源、属性

运行时和历史表流转

mermaid
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业务展示字段更灵活
附件、签名流程变量不适合大对象

最小业务表

sql
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

mermaid
flowchart TD
    A["业务单据 id=1001"] --> B["startProcessInstanceByKey"]
    B --> C["businessKey=1001"]
    C --> D["流程实例"]
    D --> E["从流程反查业务单"]
    A --> F["保存 processInstanceId"]
    F --> G["从业务单查流程轨迹"]

没有 businessKey,后续从任务、流程实例反查业务单会很麻烦,容易靠流程变量硬凑。

阶段五:启动流程全过程

执行链路

mermaid
flowchart TD
    A["校验业务单据"] --> B["准备流程变量"]
    B --> C["启动流程实例"]
    C --> D["传入 businessKey"]
    D --> E["引擎创建运行时数据"]
    E --> F["生成第一个用户任务"]
    F --> G["业务表保存 processInstanceId"]
    G --> H["业务状态改为审批中"]

Demo:启动数据资产发布审批

java
@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

java
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

java
@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

java
@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);
}

完成任务前必须校验权限。前端隐藏按钮不是安全控制,用户可以直接调用接口。

已办查询

java
public List<HistoricTaskInstance> doneList(Long userId) {
    return historyService.createHistoricTaskInstanceQuery()
        .taskAssignee(userId.toString())
        .finished()
        .orderByHistoricTaskInstanceEndTime()
        .desc()
        .list();
}

阶段七:网关和变量

排他网关

多条路只走一条,适合“金额不同走不同审批人”。

mermaid
flowchart TD
    A["主管审批"] --> B{"金额"}
    B -- "amount <= 10000" --> C["流程结束"]
    B -- "amount > 10000" --> D["总监审批"]
    D --> E["流程结束"]

并行网关

多条路都走,并在汇聚点等待全部完成。

mermaid
flowchart TD
    A["提交申请"] --> B["并行拆分"]
    B --> C["数据质量审核"]
    B --> D["安全审核"]
    C --> E["并行汇聚"]
    D --> E
    E --> F["负责人终审"]

包容网关

满足条件的多条路都走,条件不满足的分支不走。它比并行网关灵活,也更难排查。

常见问题

问题后果排查
变量名写错条件不匹配ACT_RU_VARIABLE
类型不一致表达式比较失败确认 String、Integer、Boolean
排他条件重叠走了非预期分支条件互斥,增加默认流
并行分支缺汇聚流程提前或卡住检查并行网关成对使用
包容网关条件复杂线上排查困难简化建模或拆流程

阶段八:驳回、撤回、转办、委派、加签、会签

能力边界

能力含义推荐设计
驳回审批不通过简单场景走拒绝分支,复杂场景记录业务动作
撤回发起人撤回校验当前节点、发起人、流程未结束,再终止或回退
转办把任务交给别人处理改 assignee 并记录操作
委派请别人协助,完成后回到委派人明确委派和完成语义
加签临时增加审批人简单用业务审批记录,复杂才动态节点
会签多人共同审批多实例任务,定义通过比例和否决规则

驳回流程建议

mermaid
flowchart TD
    A["审批人处理"] --> B{"是否通过"}
    B -- "通过" --> C["进入下一节点"]
    B -- "拒绝" --> D["记录拒绝意见"]
    D --> E["业务状态改为已驳回"]
    E --> F["通知发起人修改"]

不要轻易开放“任意跳转到任意节点”。任意跳转会破坏 BPMN 语义,历史轨迹也很难解释。

撤回流程建议

mermaid
flowchart TD
    A["发起人申请撤回"] --> B["校验是否发起人"]
    B --> C["校验流程未结束"]
    C --> D["校验当前节点允许撤回"]
    D --> E["终止或回退流程"]
    E --> F["业务状态改为已撤回"]
    F --> G["记录撤回日志"]

阶段九:流程部署和版本

版本原理

同一个流程 key 每次部署会生成一个新版本。旧流程实例默认继续按旧定义执行,新实例通常使用最新定义。

mermaid
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 变化历史轨迹和跳转逻辑要评估
大量未完成实例评估是否迁移,不能默认强迁

阶段十:业务一致性和事务边界

为什么会不一致

mermaid
flowchart TD
    A["完成任务"] --> B["Activiti 推进流程"]
    B --> C["业务表更新状态"]
    C --> D{"是否同一事务"}
    D -- "否" --> E["可能流程成功业务失败"]
    D -- "是" --> F["一起提交或一起回滚"]

典型不一致:

  1. 流程实例启动成功,业务单仍是草稿。
  2. 业务单显示审批中,但没有流程实例。
  3. 任务完成了,业务状态仍显示待审核。
  4. 流程结束了,业务没有发布或归档。

设计原则

原则说明
流程动作和业务动作同事务启动、完成、终止都要考虑
业务表保存最终状态页面和业务判断不直接依赖流程表
流程变量只保存小字段网关判断需要什么存什么
审批记录业务自有不完全依赖引擎历史表
异常要可补偿有对账和修复脚本

商业常用场景

医疗数据资产发布审批

mermaid
flowchart TD
    A["科室提交数据集"] --> B["数据管理员审核"]
    B --> C{"数据质量通过?"}
    C -- "否" --> D["驳回修改"]
    C -- "是" --> E["安全审核"]
    E --> F{"包含敏感字段?"}
    F -- "是" --> G["脱敏方案确认"]
    F -- "否" --> H["负责人终审"]
    G --> H
    H --> I{"终审通过?"}
    I -- "是" --> J["发布资产目录"]
    I -- "否" --> D

业务设计:

  1. 数据集表保存数据集基础信息。
  2. 申请表保存流程实例、状态、当前节点。
  3. 审批记录表保存每次处理动作。
  4. 流程变量保存数据等级、是否敏感、是否通过。
  5. 发布资产目录是业务动作,不要只依赖流程结束事件。
  6. 敏感数据审批要结合数据权限和审计。

合同审批

合同审批常见条件是金额、合同类型、供应商风险等级。大金额走法务和财务,小金额简化流程。流程变量只保存金额、类型、风险等级,合同正文和附件放业务文件表。

工单流转

工单适合用工作流管理多角色流转,例如客服受理、技术处理、质检回访。注意 SLA 超时提醒可以用定时任务或事件,不要把所有提醒逻辑硬塞进流程图。

生产排查流程

没有待办

mermaid
flowchart TD
    A["没有待办"] --> B["查 ACT_RU_TASK 是否有任务"]
    B --> C{"有任务?"}
    C -- "否" --> D["流程可能没到用户任务或已结束"]
    C -- "是" --> E["查 assignee/candidate"]
    E --> F["查用户角色是否匹配"]
    F --> G["查任务是否被别人认领"]

流程卡住

mermaid
flowchart TD
    A["流程卡住"] --> B["查流程实例是否运行中"]
    B --> C["查当前 execution 活动节点"]
    C --> D["查是否在网关"]
    D --> E["查变量名和变量类型"]
    E --> F["查并行分支是否全部到达"]
    F --> G["查是否有异步任务失败"]

业务状态和流程状态不一致

mermaid
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 是什么

text
Activiti 是基于 BPMN 的工作流引擎。BPMN 部署后形成流程定义,业务发起时创建流程实例,运行时根据流程定义生成用户任务、保存变量、判断网关并记录历史。它适合审批、工单、合同审核、数据资产发布这类多节点、多角色、需要留痕的业务。业务系统仍然负责业务表、权限、表单、通知和最终业务状态。

流程定义和流程实例区别

text
流程定义是部署后的流程模板,类似类;流程实例是某一次真实业务发起后的运行过程,类似对象。一个流程定义可以创建多个流程实例。流程定义有版本,新部署的定义通常影响新发起流程,旧实例默认继续按旧版本执行。

业务表和流程表怎么关联

text
业务表保存业务主键、业务状态、当前节点和 processInstanceId。启动流程时把业务主键作为 businessKey 传给流程实例。这样既能从业务单据查流程轨迹,也能从流程实例反查业务数据。流程变量只保存网关判断需要的小字段,不把完整业务对象放进流程变量。

待办和已办怎么查

text
待办查运行时任务表,对应当前还没有完成的任务,通常按 assignee、candidate user、candidate group 过滤。已办查历史任务表,对应用户已经处理过的任务。待办和已办的数据来源不同,流程结束后运行时任务会清理,但历史任务会保留。

流程卡住怎么排查

text
我会先查流程实例是否还在运行,再查 ACT_RU_TASK 是否有当前用户任务。如果没有任务,就查 execution 当前卡在哪个活动,重点看是否卡在网关、并行汇聚或异步任务。网关问题重点查变量名、变量类型和条件表达式;并行问题看分支是否全部到达;业务状态不一致则查流程操作和业务表更新是否在同一事务中。

学懂验收问题

下面问题答不上来,说明还没有真正学懂:

  1. 工作流和状态机怎么选?
  2. BPMN 图为什么不是普通图片?
  3. 流程定义和流程实例有什么区别?
  4. Execution 和 Task 是什么关系?
  5. 待办和已办分别查哪些表?
  6. 为什么业务数据不能都放流程变量?
  7. businessKey 和 processInstanceId 分别解决什么?
  8. assignee、candidate user、candidate group 有什么区别?
  9. 排他网关、并行网关、包容网关分别怎么工作?
  10. 并行网关为什么会卡住?
  11. 驳回和撤回为什么不能随意跳节点?
  12. 流程版本升级后旧实例怎么办?
  13. 流程完成但业务状态没变怎么排查?
  14. Activiti 能不能替代权限系统?
  15. 医疗数据资产审批如何设计业务表和流程变量?

关联知识点跳转

主题继续学习
工作流总览工作流
Activiti 总览从零学习路线与工作原理总览
生产路线Activiti 从零到生产级掌握
商业场景Activiti 商业场景训练营
流程设计BPMN可执行模型、事件、任务、子流程与会签
网关Execution、分支选择与汇聚原理
任务 API任务生命周期、认领、委派、并发与查询
业务一致性本地事务、跨库、Outbox、补偿与对账
部署与版本部署、版本、缓存与生产发布
面试Activiti 面试题
Spring 事务Spring 事务
分布式事务分布式事务