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
核心概念
-
流程定义 ProcessDefinition:bpmn2.0 的 xml 模板,静态模板,部署一次,对应act_re_procdef
-
流程实例 ProcessInstance:发起一次单据(报销 / 请假),一次运行实例,对应act_ru_execution
-
任务 Task:UserTask 人工审批、ServiceTask 自动任务;运行时待办act_ru_task,完成归档进act_hi_taskinst
-
执行实例 Execution:引擎内部游标,并行网关会产生多条 Execution
-
流程变量 Variable:全局变量(整个实例共享)、局部变量(仅当前 task)
-
BPMN2.0 节点:开始 / 结束事件、用户任务、服务任务、排他 / 并行 / 包容网关、多实例、边界事件
网关区分
-
排他网关:单选分支,满足条件走一条
-
并行网关:所有分支全部执行、等待全部汇合(会签)
-
包容网关:满足条件的分支并行执行
五大核心 Service
-
RepositoryService:部署、查询、删除流程定义、模型
-
RuntimeService:启动流程实例、设置流程变量、查询执行实例、触发信号
-
TaskService:认领任务、完成任务、设置 assignee、委托、任务变量
-
HistoryService:查询历史审批记录、已办、历史实例(hi 表)
-
ManagementService:引擎运维、查询 job 定时任务、数据库表
核心 25 张表(分 4 大类)
-
act_re_ 资源库表(静态,流程定义)* act_re_procdef 流程定义、act_re_deployment 部署记录
-
act_ru_ 运行时表(正在跑的流程)* act_ru_execution 执行实例、act_ru_task 待办任务、act_ru_variable 变量、act_ru_job 定时异步任务
-
act_hi_ 历史表(流程结束归档,查询审批记录)* act_hi_procinst 历史实例、act_hi_taskinst 历史任务、act_hi_actinst 节点执行记录、act_hi_identitylink 参与人历史
-
act_id_ 身份表(用户、组,生产一般不用,对接自己的用户体系)
中国式审批能力(Activiti 的短板)
Activiti 原生只提供基础能力,很多能力比 Flowable 弱,原生不支持
Activiti6 移除 PVM 之后,加签实现难度降低,但依然不如 Flowable 方便
-
会签 / 或签(多实例 MultiInstance)✅ 支持 UserTask 配置多实例:
-
isSequential=false 并行多实例 = 会签,所有人审批完才往下走
-
completionCondition="${nrOfCompletedInstances >=1}" 或签,任意一人完成即通过 内置变量:nrOfInstances总数量、nrOfCompletedInstances已完成、nrOfActiveInstances活跃
-
-
转办 ✅ 原生支持:TaskService 修改 task 的 assignee
-
委托 ✅ 原生支持 delegate 委托,委托完成后回到原人
-
加签 ❗ Activiti5 原生没有动态加签能力! 这是最大痛点:Flowable 原生支持动态增减多实例人员;Activiti5 要做加签,只能两种方案:
-
方案 1:提前在流程定义里预留足够多审批人集合,后端追加人员
-
方案 2:运行时动态修改流程定义(风险极高,容易脏数据,生产慎用)
-
-
驳回、撤回 ❌ 引擎原生不支持任意节点回退 和 Flowable 一样: 生产稳妥方案:建模提前画好驳回分支网关; 不推荐底层修改 execution 游标,容易破坏引擎状态
-
监听器:执行监听器、任务监听器 ✅ 支持,节点进入 / 离开、任务创建完成触发,用来发通知、写日志、赋值
-
异步 & 定时任务 ✅ 支持,ServiceTask 开启 async,存入 act_ru_job,由 JobExecutor 线程池执行
两种集成方式
-
嵌入式:和 SpringBoot 项目同库,直接调用 API,老项目最常见
-
独立 BPM 服务:微服务,http/feign 调用,Activiti7 主打云原生这套,太重
Activiti 特有坑
-
Activiti5 的 PVM 虚拟机,底层复杂,修改流程、动态节点极易出 bug
-
没有原生加签,做中国式审批(加签、补签)开发工作量很大
-
Job 只有一张表,定时任务、异步任务混在一起,失败排查、死信处理麻烦
-
流程变量存大 JSON,序列化性能差,历史表没有分区,量大之后查询很慢
-
身份表 act_id_user,一般业务都不用,自己维护用户、岗位、部门,避免和引擎身份模型冲突
优化方案
-
待办不要直接查 act_ru_task,业务库冗余待办表,MQ 回调更新,提升查询速度
-
历史 hi 表按月分表归档,减少单表数据量
-
ServiceTask 异步化,做好重试、失败告警,避免接口超时卡住流程
-
业务单据和流程引擎表通过 businessKey 关联,业务数据不要塞进流程变量
-
分布式场景:Job 任务自带数据库行锁,防止重复执行,同时业务层做幂等
流程生命周期中的数据流转
一个流程从部署到结束,数据在各表之间的流转路径可以概括为:
-
部署:RepositoryService 部署 BPMN → ACT_RE_DEPLOYMENT 新增批次 → ACT_RE_PROCDEF 生成流程定义记录。
-
启动:RuntimeService.startProcessInstanceByKey → ACT_RU_EXECUTION 插入执行记录 → 若首节点是用户任务,ACT_RU_TASK 插入待办。
-
执行:任务签收更新 ACT_RU_TASK.ASSIGNEE_;完成任务时删除运行时任务,推进执行记录的当前节点;变量写入 ACT_RU_VARIABLE。
-
结束: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; } }