Skip to content

Activiti 从零到精通验收清单 ​

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

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

最终学习目标 ​

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

能力合格标准不合格表现
工作流选型能区分工作流、状态机、任务编排所有状态流转都想上 Activiti
BPMN 建模能解释开始、结束、用户任务、网关、连线、变量只会拖组件,不懂运行含义
引擎对象能讲清流程定义、实例、执行实例、任务、变量、历史流程定义和流程实例混淆
表结构能区分 ACT_RE、ACT_RU、ACT_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 事务
分布式事务分布式事务