ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Activiti6.0工作流引擎落地实战:从核心模型到避坑指南

Activiti6.0工作流引擎落地实战:从核心模型到避坑指南 简介这份《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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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}会签节点设置multiInstanceLoopCharacteristicsisSequential为 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方便反查 MapString, 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; // 查询某个候选人的待办任务 ListTask tasks taskService.createTaskQuery() .taskCandidateOrAssigned(user_2001) .orderByTaskCreateTime().desc() .list(); for (Task task : tasks) { // 认领任务认领后 assignee 会被设置 taskService.claim(task.getId(), user_2001); // 办理时传入审批意见等变量 MapString, 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(); // 查该实例下所有已走过的活动节点按时间排序 ListHistoricActivityInstance 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、活动节点、变量快照打出来九成问题当场就能定位。流程引擎不可怕可怕的是不看表瞎猜。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进