☰
Activiti6.0工作流引擎落地实战:从核心模型到避坑指南
2026/10/10 2:06:03 网站建设 项目流程

简介:这份《Activiti6.0工作流使用说明文档v1.0.pdf》面向正在学习或落地工作流引擎的Java开发者、架构师与业务流程设计人员,帮助读者系统掌握Activiti 6.0的建模与任务配置方法。文档以模型设计器为主线,逐一讲解用户任务、服务任务、脚本任务、业务规则任务、接收任务、手动任务、邮件任务、Camel任务、Mule任务与决策任务等十类任务的适用场景与配置要点,并延伸至子流程、事件子流程、泳道列表,以及排他、并行、包容、事件等分支结构,目录层次清晰,便于按模块查阅。资源包内仅含1个PDF文件,大小约1.72MB,轻量易读,适合作为日常开发中的速查手册。目前已有1166人学习下载,可作为工作流入门与流程设计实践的参考材料。

1. 从一份 Activiti6.0 工作流使用说明文档说起:为什么流程引擎落地总在“最后一公里”翻车

很多团队第一次接触 Activiti6.0,都是被一份《Activiti6.0工作流使用说明文档v1.0.pdf》带进门的。文档里 BPMN 图、表结构、API 示例一应俱全,照着搭个请假审批 Demo 半天就能跑通,于是信心满满地往真实业务里塞。结果上线两周,流程实例卡在某个节点不动、候选人查不到、驳回后回退到错误分支、历史数据查出来是空的——这些都不是文档会写的东西。这份使用说明文档真正要解决的,不是“怎么画流程图”,而是“怎么让流程引擎在业务系统里稳定跑起来”。它适合两类人:一是刚接手 Activiti6.0 的后端工程师,需要一份能照着复现的落地路径;二是已经踩过坑、想把流程模块做扎实的架构同学。下面我按自己趟过的路,把这份文档背后的东西拆开讲清楚。

2. Activiti6.0 的核心模型与选型:先搞懂它到底在管什么

2.1 流程定义、流程实例、任务三者的关系

Activiti6.0 的世界里,最核心的三个概念是流程定义(ProcessDefinition)、流程实例(ProcessInstance)和任务(Task)。流程定义是 BPMN 文件部署后生成的“模板”,每次启动流程就是基于模板创建一个流程实例,实例流转到用户任务节点时生成一条待办任务。理解这三层关系,是排查一切问题的起点。

我一般用一张表来对照业务语义和引擎概念,避免团队里各说各话:

业务说法Activiti 概念对应表(MySQL)
流程模板ProcessDefinitionACT_RE_PROCDEF
一次审批单ProcessInstanceACT_RU_EXECUTION
待办事项TaskACT_RU_TASK
审批记录HistoricActivityInstanceACT_HI_ACTINST
流程变量VariableACT_RU_VARIABLE

这张表的价值在于:当流程卡住时,你能立刻知道该去查哪张表。比如任务查不到,先看 ACT_RU_TASK 有没有记录;实例状态不对,看 ACT_RU_EXECUTION 的 IS_ACTIVE 和 SUSPENSION_STATE。

2.2 为什么很多团队选 Activiti6.0 而不是自研状态机

自研状态机在流程简单时确实轻,但一旦出现会签、并行网关、子流程、驳回回退、动态加签,状态机会迅速膨胀成一张无法维护的网。Activiti6.0 的价值在于它把 BPMN2.0 规范落成了可执行的引擎,网关、边界事件、多实例这些语义是现成的。

选型时要盯住几个点:一是团队是否接受 BPMN 建模,二是数据库表结构能否接受(Activiti 自带 20 多张表),三是是否需要和历史系统集成。Activiti6.0 相比 5.x 最大的变化是全面拥抱 Spring Boot 和 Spring Security,如果你是新项目,直接上 6.0 的 starter 会比 5.x 省很多配置功夫。

2.3 最小可运行环境搭建

落地第一步是把引擎跑起来。我一般用 Spring Boot 加 activiti-spring-boot-starter-basic,数据库用 MySQL,先把自动建表打开。

# application.yml spring: datasource: url: jdbc:mysql://127.0.0.1:3306/activiti_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver activiti: # 首次启动自动建表,生产环境务必改为 false 并手工执行 DDL database-schema-update: true # 开启历史级别,audit 足够大多数审批场景 history-level: audit # 关闭异步执行器的自动激活,按需手动控制 async-executor-activate: false db-identity-used: true

这段配置里三个参数最关键。database-schema-update在开发期设 true 会自动创建 ACT_ 开头的表,但生产环境一定要关掉,否则每次启动都去比对表结构,既慢又危险。history-level设成 audit 会记录流程实例和活动节点,设成 full 还会记录变量明细,数据量会翻好几倍,审批类业务 audit 基本够用。async-executor-activate控制异步作业执行器,如果流程里用了定时边界事件或异步任务,必须打开,否则作业永远不执行。

启动后如果报“Table ‘activiti_demo.ACT_GE_PROPERTY’ doesn‘t exist”,说明自动建表没生效,检查数据库连接和database-schema-update是否被其他配置覆盖。这一步跑通,后面才有得谈。

3. 用 BPMN 和 Java API 跑通一条真实审批流

3.1 画一条带条件分支和会签的流程图

光跑通线性流程没意义,真实审批一定有分支和会签。假设一个报销流程:金额小于 500 直接主管审批,大于等于 500 需要主管和财务会签。BPMN 里用排他网关做金额判断,用多实例用户任务做会签。

关键节点配置如下:排他网关的两条出线分别设条件${amount < 500}和${amount >= 500};会签节点设置multiInstanceLoopCharacteristics,isSequential为 false 表示并行会签,collection指定参与人集合变量,completionCondition设为${nrOfCompletedInstances/nrOfInstances >= 1}表示全部完成才通过。

这里有个容易翻车的点:条件表达式里的变量名必须和启动流程时传入的变量名完全一致,大小写敏感。我见过有人写amount传参写Amount,流程直接卡在网关报Unknown property used in expression。

3.2 部署流程定义并启动实例

BPMN 文件画好后,通过 RepositoryService 部署,再通过 RuntimeService 启动。

@Autowired private RepositoryService repositoryService; @Autowired private RuntimeService runtimeService; // 部署 BPMN 文件,key 用于后续按流程定义 key 启动 Deployment deployment = repositoryService.createDeployment() .name("报销流程部署") .addClasspathResource("processes/reimburse.bpmn20.xml") .deploy(); // 启动流程实例,businessKey 绑定业务单据 ID,方便反查 Map<String, Object> vars = new HashMap<>(); vars.put("amount", 800); vars.put("initiator", "user_1001"); // 会签参与人集合 vars.put("approvers", Arrays.asList("user_2001", "user_2002")); ProcessInstance instance = runtimeService.startProcessInstanceByKey( "reimburseProcess", // 对应 BPMN 里 process 标签的 id "BIZ_20240101_001", // businessKey vars);

startProcessInstanceByKey的第一个参数是流程定义 key,不是部署 ID,也不是流程名称,写错会抛ActivitiObjectNotFoundException。businessKey 强烈建议绑定业务主键,后面查历史、做关联全靠它。变量在启动时传入后会存进 ACT_RU_VARIABLE,流程结束后转到 ACT_HI_VARINST。

3.3 查询待办与完成任务

任务生成后,用 TaskService 查询和办理。

@Autowired private TaskService taskService; // 查询某个候选人的待办任务 List<Task> tasks = taskService.createTaskQuery() .taskCandidateOrAssigned("user_2001") .orderByTaskCreateTime().desc() .list(); for (Task task : tasks) { // 认领任务,认领后 assignee 会被设置 taskService.claim(task.getId(), "user_2001"); // 办理时传入审批意见等变量 Map<String, Object> approveVars = new HashMap<>(); approveVars.put("approved", true); approveVars.put("comment", "同意报销"); taskService.complete(task.getId(), approveVars); }

taskCandidateOrAssigned会同时匹配候选人和已分配人,比单独用taskAssignee更实用。claim这一步在并行会签里很重要,不认领直接 complete 虽然也能走,但任务归属会不清晰,历史记录里 assignee 为空。complete 传入的变量会合并到流程变量里,后续网关判断可以继续用。

3.4 查历史数据做审批留痕

审批系统离不开历史查询。HistoryService 能查已完成的实例、活动和任务。

@Autowired private HistoryService historyService; // 按 businessKey 查历史流程实例 HistoricProcessInstance hpi = historyService .createHistoricProcessInstanceQuery() .processInstanceBusinessKey("BIZ_20240101_001") .singleResult(); // 查该实例下所有已走过的活动节点,按时间排序 List<HistoricActivityInstance> acts = historyService .createHistoricActivityInstanceQuery() .processInstanceId(hpi.getId()) .orderByHistoricActivityInstanceStartTime().asc() .list();

历史查询的前提是history-level不能是 none。如果查出来是空列表,先确认这个参数。另外历史表的清理策略要提前规划,ACT_HI_* 表会随业务量持续增长,常见做法是按时间分区或定期归档,别等到查询变慢才想起来。

4. 避坑指南:Activiti6.0 落地最常见的五类问题

4.1 流程实例卡住不动,任务查不到

现象:启动流程后,ACT_RU_TASK 里没有记录,实例停在某个节点。原因通常是节点没有正确设置处理人,或者网关条件都不满足导致没有出线可走。解决:先查 ACT_RU_EXECUTION 看当前活动节点,再对照 BPMN 检查该节点的 assignee、candidateUsers 是否为空,网关的默认流(default flow)是否配置。我一般会给排他网关加一条默认流兜底,避免条件遗漏时直接卡死。

4.2 表达式报 Unknown property

现象:流程走到网关或服务任务时抛Unknown property used in expression。原因是表达式里引用的变量在流程变量里不存在,或者变量名拼写不一致。解决:在启动流程和每次 complete 时打印当前变量快照,确认变量确实写入了。对于可能为空的变量,表达式里用${amount != null && amount >= 500}做防御,别裸写比较。

4.3 并行会签人数不对或提前结束

现象:设置了 3 个会签人,实际只生成 1 个任务,或者 2 个人办完流程就往下走了。原因是collection变量传的是字符串而不是集合,或者completionCondition写成了>= 1。解决:确认传入的是 List 类型;completionCondition 里nrOfCompletedInstances和nrOfInstances的含义要分清,全部通过应写>= nrOfInstances或直接用比例 1。

4.4 历史数据查不到

现象:流程明明走完了,HistoricProcessInstance 查出来是 null。原因是history-level设成了 none,或者查询条件用了错误的 businessKey。解决:检查配置,none 级别不记录任何历史;同时确认 businessKey 在启动时确实传了,没传的话历史表里该字段为空,按它查自然查不到。

4.5 数据库连接池被流程引擎拖垮

现象:并发启动流程时,应用报连接超时,数据库连接数飙升。原因是 Activiti 的异步执行器和定时任务会占用连接,加上默认配置下每次操作都可能开新连接。解决:把async-executor-activate按需开启,配置合理的线程池大小;数据源连接池(如 HikariCP)的 maximumPoolSize 要留出余量给流程引擎;历史级别不要盲目设 full,减少写入压力。

5. 进阶技巧:让 Activiti6.0 在业务系统里真正好用

5.1 用业务键和流程变量做业务解耦

很多团队把业务字段一股脑塞进流程变量,导致流程表和业务表强耦合。我的习惯是:流程变量只放流程流转必需的字段(金额、审批结果、参与人),业务详情通过 businessKey 关联业务表查询。这样流程引擎只管流转,业务数据归业务系统,升级或替换引擎时影响面小。

5.2 监听器做审批日志和消息通知

Activiti 提供执行监听器和任务监听器,可以在节点进入、离开、任务创建、任务完成时触发自定义逻辑。我一般用任务监听器在任务创建时发通知,用执行监听器在流程结束时写审批归档。

public class TaskCreateListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { // 任务创建时触发,可发消息、写日志 String assignee = delegateTask.getAssignee(); String taskName = delegateTask.getName(); // 这里调用业务侧的通知服务,注意异常要吞掉,别影响流程流转 try { // notifyService.send(assignee, taskName); } catch (Exception e) { // 通知失败不能阻断流程,记录日志即可 } } }

监听器里最忌讳抛异常。一旦监听器抛异常,整个流程操作会回滚,用户看到的是“办理失败”。所以通知、日志这类旁路逻辑一定要 try-catch 包住,失败只记日志。

5.3 流程版本管理与灰度

流程定义每次部署,如果 key 相同,版本号会自动加一。新启动的实例走新版本,已运行的实例继续走旧版本。这个机制天然支持灰度:先部署新版本,只让新单据走新流程,观察一段时间再全量。要注意的是,旧版本流程定义不要删除,否则运行中的实例会找不到定义而报错。

5.4 一个验证流程是否健康的小技巧

上线后我习惯定期跑一个巡检:查 ACT_RU_EXECUTION 里停留超过 24 小时的实例,查 ACT_RU_TASK 里创建超过 48 小时未办理的任务,查 ACT_HI_PROCINST 里结束时间为空的实例。这三类数据能提前暴露大部分流程异常。与其等用户投诉,不如让巡检脚本每天推一份清单。

最后说个我自己的教训:早期做流程项目时,我总觉得引擎是黑匣子,出问题就重启应用,结果数据越搞越乱。后来养成习惯,每次流程异常先查那几张运行时表,把实例 ID、活动节点、变量快照打出来,九成问题当场就能定位。流程引擎不可怕,可怕的是不看表瞎猜。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询