Activiti详解

Activiti是什么

Activiti 是基于 BPMN2.0 规范、Java 开发的轻量级开源工作流引擎,起源于 jBPM4;Flowable 就是 Activiti 原核心团队 fork 出去的分支。

版本历史

  • Activiti5:最经典、国内老 OA 项目最多,5.23.0,官方早已停止维护;底层有 PVM 流程虚拟机

  • Activiti6:原团队最后一个版本,移除 PVM,简化内核,维护很短,存量不多

  • Activiti7(Activiti Cloud):换了团队,彻底转向云原生、K8s,不再是传统嵌入式引擎,国内很少用,门槛高

新项目一般不推荐 Activiti,优先 Flowable;Activiti 适合老系统维护,新项目选 Flowable

核心概念

  1. 流程定义 ProcessDefinition:bpmn2.0 的 xml 模板,静态模板,部署一次,对应act_re_procdef

  2. 流程实例 ProcessInstance:发起一次单据(报销 / 请假),一次运行实例,对应act_ru_execution

  3. 任务 Task:UserTask 人工审批、ServiceTask 自动任务;运行时待办act_ru_task,完成归档进act_hi_taskinst

  4. 执行实例 Execution:引擎内部游标,并行网关会产生多条 Execution

  5. 流程变量 Variable:全局变量(整个实例共享)、局部变量(仅当前 task)

  6. BPMN2.0 节点:开始 / 结束事件、用户任务、服务任务、排他 / 并行 / 包容网关、多实例、边界事件

网关区分

  • 排他网关:单选分支,满足条件走一条

  • 并行网关:所有分支全部执行、等待全部汇合(会签)

  • 包容网关:满足条件的分支并行执行

五大核心 Service

  1. RepositoryService:部署、查询、删除流程定义、模型

  2. RuntimeService:启动流程实例、设置流程变量、查询执行实例、触发信号

  3. TaskService:认领任务、完成任务、设置 assignee、委托、任务变量

  4. HistoryService:查询历史审批记录、已办、历史实例(hi 表)

  5. ManagementService:引擎运维、查询 job 定时任务、数据库表

核心 25 张表(分 4 大类)

  1. act_re_ 资源库表(静态,流程定义)* act_re_procdef 流程定义、act_re_deployment 部署记录

  2. act_ru_ 运行时表(正在跑的流程)* act_ru_execution 执行实例、act_ru_task 待办任务、act_ru_variable 变量、act_ru_job 定时异步任务

  3. act_hi_ 历史表(流程结束归档,查询审批记录)* act_hi_procinst 历史实例、act_hi_taskinst 历史任务、act_hi_actinst 节点执行记录、act_hi_identitylink 参与人历史

  4. act_id_ 身份表(用户、组,生产一般不用,对接自己的用户体系)

中国式审批能力(Activiti 的短板)

Activiti 原生只提供基础能力,很多能力比 Flowable 弱,原生不支持

Activiti6 移除 PVM 之后,加签实现难度降低,但依然不如 Flowable 方便

  1. 会签 / 或签(多实例 MultiInstance)✅ 支持 UserTask 配置多实例:

    • isSequential=false 并行多实例 = 会签,所有人审批完才往下走

    • completionCondition="${nrOfCompletedInstances >=1}" 或签,任意一人完成即通过 内置变量:nrOfInstances总数量、nrOfCompletedInstances已完成、nrOfActiveInstances活跃

  2. 转办 ✅ 原生支持:TaskService 修改 task 的 assignee

  3. 委托 ✅ 原生支持 delegate 委托,委托完成后回到原人

  4. 加签 ❗ Activiti5 原生没有动态加签能力! 这是最大痛点:Flowable 原生支持动态增减多实例人员;Activiti5 要做加签,只能两种方案:

    • 方案 1:提前在流程定义里预留足够多审批人集合,后端追加人员

    • 方案 2:运行时动态修改流程定义(风险极高,容易脏数据,生产慎用)

  5. 驳回、撤回 ❌ 引擎原生不支持任意节点回退 和 Flowable 一样: 生产稳妥方案:建模提前画好驳回分支网关; 不推荐底层修改 execution 游标,容易破坏引擎状态

  6. 监听器:执行监听器、任务监听器 ✅ 支持,节点进入 / 离开、任务创建完成触发,用来发通知、写日志、赋值

  7. 异步 & 定时任务 ✅ 支持,ServiceTask 开启 async,存入 act_ru_job,由 JobExecutor 线程池执行

两种集成方式

  1. 嵌入式:和 SpringBoot 项目同库,直接调用 API,老项目最常见

  2. 独立 BPM 服务:微服务,http/feign 调用,Activiti7 主打云原生这套,太重

Activiti 特有坑

  1. Activiti5 的 PVM 虚拟机,底层复杂,修改流程、动态节点极易出 bug

  2. 没有原生加签,做中国式审批(加签、补签)开发工作量很大

  3. Job 只有一张表,定时任务、异步任务混在一起,失败排查、死信处理麻烦

  4. 流程变量存大 JSON,序列化性能差,历史表没有分区,量大之后查询很慢

  5. 身份表 act_id_user,一般业务都不用,自己维护用户、岗位、部门,避免和引擎身份模型冲突

优化方案

  1. 待办不要直接查 act_ru_task,业务库冗余待办表,MQ 回调更新,提升查询速度

  2. 历史 hi 表按月分表归档,减少单表数据量

  3. ServiceTask 异步化,做好重试、失败告警,避免接口超时卡住流程

  4. 业务单据和流程引擎表通过 businessKey 关联,业务数据不要塞进流程变量

  5. 分布式场景:Job 任务自带数据库行锁,防止重复执行,同时业务层做幂等

流程生命周期中的数据流转

一个流程从部署到结束,数据在各表之间的流转路径可以概括为:

  1. 部署:RepositoryService 部署 BPMN → ACT_RE_DEPLOYMENT 新增批次 → ACT_RE_PROCDEF 生成流程定义记录。

  2. 启动:RuntimeService.startProcessInstanceByKey → ACT_RU_EXECUTION 插入执行记录 → 若首节点是用户任务,ACT_RU_TASK 插入待办。

  3. 执行:任务签收更新 ACT_RU_TASK.ASSIGNEE_;完成任务时删除运行时任务,推进执行记录的当前节点;变量写入 ACT_RU_VARIABLE。

  4. 结束:ACT_RU_* 中对应记录被删除,ACT_HI_* 中记录被补全结束时间与结果。

如何实现动态审批人?

常见方式包括:

  • 根据发起人查询部门主管。

  • 根据部门、角色或组织架构确定审批人。

  • 通过流程变量传递审批人信息。

  • 使用任务监听器或业务服务设置任务处理人。

同时要注意,设置了任务处理人,不等于完成了业务权限校验。后端仍需验证当前登录用户是否有权操作任务。

Activiti 引擎内部大致分为六层

|--------|----------------------------------------|------------------------------|
| 层次 | 核心内容 | 职责 |
| 工作流引擎层 | ProcessEngine | 所有接口的总入口,通过它获取全部服务 |
| 部署层 | DeploymentBuilder、BpmnModel | 将 BPMN XML 解析为引擎可识别的 POJO 模型 |
| 业务接口层 | RuntimeService、TaskService 等 | 面向业务暴露生命周期各阶段的操作入口 |
| 命令拦截层 | CommandInterceptor | 责任链模式,统一处理事务开启、上下文创建、日志等横切逻辑 |
| 命令层 | Command 实现类 | 将每项业务逻辑封装为独立命令,是引擎的业务处理层 |
| 行为层 | ActivityBehavior、ActivitiEventListener | 执行和监听流程节点的具体流转动作 |

Activiti demo

1. maven 依赖

复制代码
<!-- activiti5 核心 -->
<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter-basic</artifactId>
    <version>5.23.0</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

2. application.yml

复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/activiti5-demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
activiti:
  database-schema-update: true # 自动建表,生产环境关闭
  check-process-definitions: false

3. bpmn 文件(leave.bpmn,请假单,会签多实例示例)

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100501/BPMN20"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:activiti="http://activiti.org/bpmn"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100501/BPMN20 http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd"
             id="Definitions_1"
             targetNamespace="http://activiti.org/bpmn">

    <process id="leaveProcess" name="请假审批流程" isExecutable="true">
        <startEvent id="startEvent1"/>
        <sequenceFlow id="flow1" sourceRef="startEvent1" targetRef="multiUserTask"/>

        <!-- 并行多实例 会签任务 -->
        <userTask id="multiUserTask" name="部门领导会签">
            <multiInstanceLoopCharacteristics isSequential="false"
                                             activiti:collection="approveUserList"
                                             activiti:elementVariable="approveUser">
                <!-- 会签:全部完成才通过 -->
                <completionCondition>${nrOfCompletedInstances >= nrOfInstances}</completionCondition>
            </multiInstanceLoopCharacteristics>
            <humanPerformer>
                <resourceAssignmentExpression>
                    <formalExpression>${approveUser}</formalExpression>
                </resourceAssignmentExpression>
            </humanPerformer>
        </userTask>
        <sequenceFlow id="flow2" sourceRef="multiUserTask" targetRef="endEvent1"/>

        <endEvent id="endEvent1"/>
    </process>
</definitions>

改成或签只需要修改完成条件: <completionCondition>${nrOfCompletedInstances >=1}</completionCondition>

4. Java 核心代码

复制代码
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ActivitiDemoService {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;

    /**
     * 部署流程
     */
    public void deploy() {
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("leave.bpmn")
                .name("请假审批流程")
                .deploy();
        System.out.println("部署ID:" + deployment.getId());
    }

    /**
     * 启动流程实例(会签,传入审批人集合)
     */
    public String startProcess(String businessKey, List<String> approveUserList) {
        Map<String, Object> vars = new HashMap<>();
        vars.put("approveUserList", approveUserList);
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("leaveProcess", businessKey, vars);
        return instance.getId();
    }

    /**
     * 查询某人待办
     */
    public List<Task> getTodoTask(String assignee) {
        return taskService.createTaskQuery()
                .taskAssignee(assignee)
                .list();
    }

    /**
     * 完成任务
     */
    public void completeTask(String taskId, Map<String, Object> vars) {
        taskService.complete(taskId, vars);
    }

    /**
     * 转办:修改任务审批人
     */
    public void transferTask(String taskId, String newAssignee) {
        taskService.setAssignee(taskId, newAssignee);
    }

    /**
     * 委托代办
     */
    public void delegateTask(String taskId, String delegateUser) {
        taskService.delegateTask(taskId, delegateUser);
    }
}

⚠️ :Activiti5 无法动态追加多实例审批人(加签),这个是硬伤! 上面代码是提前把approveUserList一次性传好;如果中途要加人,这套写法做不到。

5. 测试 Controller

复制代码
@RestController
@RequestMapping("/activiti")
public class TestController {
    @Autowired
    private ActivitiDemoService demoService;

    @GetMapping("/deploy")
    public String deploy(){
        demoService.deploy();
        return "部署成功";
    }

    @GetMapping("/start")
    public String start(){
        List<String> users = Arrays.asList("zhangsan","lisi");
        String instanceId = demoService.startProcess("BUS20261027001", users);
        return "流程实例ID:"+instanceId;
    }
}
相关推荐
她的男孩8 小时前
Spring Boot 表单挂 Flowable 审批:设计器绑模型后,业务侧用注解把单子跑起来
spring boot·低代码·工作流引擎
她的男孩2 天前
超时配置了自动驳回,任务状态却全变「已取消」:Forge Admin 流程超时终态与 Flowable ENTITY_DELETED
spring boot·低代码·工作流引擎
jonyleek5 天前
四阶责任锚定法:用技术机制重建目标→计划→任务→日历的穿透链(附可落地实现逻辑)
工作流引擎·责任链·系统集成·任务管理·jvs企业计划·okr落地·日历同步
她的男孩5 天前
条件分支两边审批一样,却发成两份待办:Forge Admin 审批设计器的公共节点合并
spring boot·低代码·工作流引擎
PingCAP9 天前
从一句需求到一套可追溯机制:我们这样推进电子病历改造
人工智能·agent·工作流引擎
PingCAP9 天前
平凯星辰刘松:稳定交付严肃任务正成为企业级智能体的重要考验
人工智能·agent·工作流引擎
她的男孩15 天前
升级 Spring Boot 4 后流程起不来、登录却报缺租户:Forge Admin 补上 applicationTaskExecutor 与旧 Sa-Tok
spring boot·安全·工作流引擎