news 2026/10/15 13:14:07

Activiti6.0工作流引擎落地实战:从BPMN部署到避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Activiti6.0工作流引擎落地实战:从BPMN部署到避坑指南

简介:这份《Activiti6.0工作流使用说明文档v1.0.pdf》面向Java后端开发者、工作流引擎初学者及需要落地审批、审核类业务流程的技术人员,帮助读者系统理解Activiti 6.0的建模与执行机制。文档以模型设计器为主线,逐一讲解用户任务、服务任务、脚本任务、业务规则任务、接收任务、手动任务、邮件任务、Camel任务、Mule任务与决策任务等十类任务元素的适用场景,并延伸至子流程、事件子流程、泳道列表,以及排他、并行、包容、事件四类分支的构造方式,目录层次清晰,便于按模块查阅。资源包内仅含1个PDF文件,大小约1.72MB,内容为37页的图文说明,适合作为日常开发中的速查手册。目前已有1166人学习下载,读者可借此快速掌握流程建模思路、任务类型选型与分支控制逻辑,减少在流程设计与集成环节的试错成本。

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

很多团队第一次接触 Activiti6.0,都是因为业务里出现了“审批”两个字。请假、报销、合同会签、工单流转,需求方一句“要能配流程”,开发就得把状态机推倒重来。Activiti6.0 工作流使用说明文档 v1.0.pdf 这类资料,本质上解决的不是“引擎怎么装”,而是“流程定义怎么画、任务怎么派、变量怎么传、历史怎么查”这一整套落地问题。它适合两类人:一类是刚接手流程模块、需要照着把第一个 BPMN 跑通的后端;另一类是被 Activiti5 或自研状态机坑过、想换到 6.0 但不确定边界的老手。这篇笔记不逐页翻译文档,而是把这份说明文档背后真正要落地的路径拆开:环境怎么搭、模型怎么建、API 怎么调、坑在哪、值不值得投入。

2. 把 Activiti6.0 跑起来:从建表到第一个流程实例的最小闭环

2.1 依赖与数据库:为什么 6.0 的建表策略和 5.x 不一样

Activiti6.0 默认使用 MyBatis 做持久化,启动时会检查ACT_前缀的 25 张表。和 5.x 最大的区别在于,6.0 把act_ge_bytearray里的流程定义部署包和模型编辑器数据做了更清晰的分离,同时act_ru_execution和act_ru_task的关联字段在并发场景下更容易出现死锁。常见做法是先在测试库用databaseSchemaUpdate: true自动建表,确认表结构后再导出 DDL 到生产库手工执行。生产环境千万不要开自动更新,否则一次版本升级可能把历史表结构改坏。

# application-activiti.yml spring: datasource: url: jdbc:mysql://127.0.0.1:3306/activiti_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: act_user password: act_pass driver-class-name: com.mysql.cj.jdbc.Driver activiti: database-schema-update: true # 仅测试环境开启,生产改为 false history-level: full # 记录所有历史,便于审计 db-history-used: true async-executor-activate: false # 先关异步,避免定时任务干扰调试 check-process-definitions: false # 不自动扫描 resources/processes

这段配置里最容易被忽略的是history-level。设成none时查不到历史任务,设成activity只能看到节点,只有full才能把变量、表单、批注一起留下。代价是ACT_HI_*表增长很快,我一般会在生产环境按act_hi_procinst.end_time_做月度归档。async-executor-activate在本地调试时建议关掉,否则定时器任务会不断重试,日志里全是JobEntity的异常,新手很容易以为流程卡死。

2.2 用 BPMN 模型定义第一个请假流程

Activiti6.0 支持在 XML 里直接写 BPMN 2.0,也可以用在线模型器画。文档里通常会给一个请假流程示例,但真正落地时要注意:用户任务必须绑定assignee或candidateUsers,否则任务创建后没人能认领。下面是一个最小可用的 BPMN 片段,包含开始事件、两个用户任务、一个排他网关和结束事件。

<!-- leave-process.bpmn20.xml --> <process id="leaveProcess" name="请假申请" isExecutable="true"> <startEvent id="startEvent" /> <sequenceFlow sourceRef="startEvent" targetRef="submitTask" /> <userTask id="submitTask" name="提交申请" activiti:assignee="${applicant}" /> <sequenceFlow sourceRef="submitTask" targetRef="approveTask" /> <userTask id="approveTask" name="主管审批" activiti:candidateGroups="deptManager" /> <sequenceFlow sourceRef="approveTask" targetRef="decisionGateway" /> <exclusiveGateway id="decisionGateway" /> <sequenceFlow sourceRef="decisionGateway" targetRef="endEvent"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${approved == true}]]> </conditionExpression> </sequenceFlow> <sequenceFlow sourceRef="decisionGateway" targetRef="submitTask"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${approved == false}]]> </conditionExpression> </sequenceFlow> <endEvent id="endEvent" /> </process>

这里applicant和approved都是流程变量。assignee用表达式${applicant}表示启动流程时传入的变量决定任务归属;candidateGroups则让整个组都能看到任务。排他网关的两个条件必须互斥且覆盖所有分支,否则流程实例会卡在网关抛ActivitiException: No outgoing sequence flow found。我见过最典型的翻车是条件写成${approved}和${!approved},结果变量没传时两个都不成立,实例直接挂起。

2.3 部署、启动、查询:三个 API 把闭环跑通

部署用RepositoryService,启动用RuntimeService,查任务用TaskService。下面这段 Java 代码把“部署—启动—查任务—完成任务”串成一个可复现的闭环。

@Autowired private RepositoryService repositoryService; @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; public void runLeaveProcess() { // 1. 部署 BPMN 文件 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/leave-process.bpmn20.xml") .name("请假流程部署") .deploy(); // 2. 启动流程实例,传入申请人变量 Map<String, Object> vars = new HashMap<>(); vars.put("applicant", "user_1001"); ProcessInstance instance = runtimeService .startProcessInstanceByKey("leaveProcess", vars); // 3. 查询当前任务 List<Task> tasks = taskService.createTaskQuery() .processInstanceId(instance.getId()) .list(); for (Task task : tasks) { System.out.println("任务:" + task.getName() + ",办理人:" + task.getAssignee()); } // 4. 完成审批任务并设置 approved 变量 Task approveTask = tasks.get(0); Map<String, Object> approveVars = new HashMap<>(); approveVars.put("approved", true); taskService.complete(approveTask.getId(), approveVars); }

startProcessInstanceByKey里的 key 对应 BPMN 里<process id="leaveProcess">。如果部署了多个版本,默认走最新版;要指定版本可以用startProcessInstanceByKeyAndTenantId或先查ProcessDefinition。taskService.complete的第二个参数会合并进流程变量,排他网关就是靠这个approved决定走向。注意:如果任务有candidateGroups但没有assignee,直接 complete 也能成功,但生产环境最好先claim再complete,否则历史表里办理人字段是空的,审计时说不清。

3. 流程变量、任务监听与网关:Activiti6.0 最容易配错的三个地方

3.1 流程变量的作用域:全局变量和局部变量别混用

Activiti6.0 的变量分两类:setVariable设的是全局变量,整个流程实例可见;setVariableLocal只挂在当前执行流或任务上。文档里往往一笔带过,但实际排错时这是高频问题。比如在会签场景里,每个子任务都需要独立的审批意见,如果用全局变量,后一个任务会把前一个覆盖掉。正确做法是在任务监听器里用taskService.setVariableLocal(taskId, "comment", ...)。

// 在 TaskListener 的 notify 方法中 public void notify(DelegateTask delegateTask) { String taskId = delegateTask.getId(); // 局部变量:只属于当前任务 taskService.setVariableLocal(taskId, "approveTime", new Date()); // 全局变量:整个实例共享,谨慎使用 delegateTask.setVariable("lastOperator", delegateTask.getAssignee()); }

setVariableLocal写入的是ACT_HI_VARINST里带TASK_ID_的记录,查询时要用historyService.createHistoricVariableInstanceQuery().taskId(taskId)。如果混用全局变量,在并行网关分叉后会出现变量互相覆盖,表现为“审批意见串了”。我一般建议:流程控制用的变量(如approved)用全局,业务表单字段用局部,并在流程结束时统一归档。

3.2 任务监听器和执行监听器的选择

监听器是 Activiti6.0 里最容易写错类型的地方。TaskListener挂在用户任务上,能拿到DelegateTask;ExecutionListener挂在流程或连线事件上,拿到的是DelegateExecution。常见需求“审批通过后自动发通知”,应该用TaskListener的complete事件,而不是ExecutionListener的end,因为后者在任务完成前就触发了,此时变量还没写入。

public class ApproveNotifyListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { String eventName = delegateTask.getEventName(); if ("complete".equals(eventName)) { Object approved = delegateTask.getVariable("approved"); if (Boolean.TRUE.equals(approved)) { // 发送通知逻辑 System.out.println("审批通过,通知申请人"); } } } }

在 BPMN 里绑定监听器:

<userTask id="approveTask" name="主管审批" activiti:candidateGroups="deptManager"> <extensionElements> <activiti:taskListener event="complete" class="com.example.listener.ApproveNotifyListener" /> </extensionElements> </userTask>

注意event可选create、assignment、complete、delete。如果写成end,Activiti6.0 会直接抛XMLException,因为用户任务不支持end事件。这个报错信息不直观,新手经常以为是类路径问题。

3.3 排他网关和并行网关的条件表达式写法

排他网关按顺序判断条件,第一个为 true 的连线被选中;并行网关则所有连线都会走,不需要条件。文档里通常只给一个简单示例,但实际业务里经常出现“默认流”需求。Activiti6.0 支持在连线上不写条件作为默认流,但一个网关只能有一条默认流,且必须放在最后。

<exclusiveGateway id="gw" default="flowDefault" /> <sequenceFlow id="flowA" sourceRef="gw" targetRef="taskA"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${amount > 5000}]]> </conditionExpression> </sequenceFlow> <sequenceFlow id="flowDefault" sourceRef="gw" targetRef="taskB" />

这里default="flowDefault"指向的连线不能带条件表达式,否则启动时报ActivitiException: Default sequence flow has a condition。另外,条件表达式里的变量如果为 null,比较运算会抛PropertyNotFoundException。稳妥做法是在启动流程时给所有判断变量设默认值,比如amount默认 0,approved默认 false。

4. 避坑与排查:Activiti6.0 落地时最常遇到的 5 个问题

4.1 现象:流程启动报 “No process definition found for key”

原因:BPMN 文件没有被部署,或者部署时resourceName不是.bpmn20.xml结尾。Activiti6.0 默认只解析.bpmn20.xml和.bpmn,如果文件名是leave.bpmn20缺少.xml,部署会静默成功但查不到定义。解决:统一用xxx.bpmn20.xml命名,部署后用repositoryService.createProcessDefinitionQuery().processDefinitionKey("leaveProcess").list()确认。

4.2 现象:任务查不到,但流程实例存在

原因:用户任务的assignee表达式引用了不存在的变量,导致ASSIGNEE_为 null,而查询时用了taskAssignee("user_1001")。解决:启动流程前确保变量已传入,或者改用taskCandidateUser查询候选组任务。排查时先查ACT_RU_TASK表的ASSIGNEE_字段,比看日志快。

4.3 现象:排他网关抛 “No outgoing sequence flow found”

原因:所有条件都不满足,且没有默认流。常见于变量类型不匹配,比如 BPMN 里写${approved == 'true'},但传入的是 Boolean。解决:条件表达式里统一用${approved == true},并在启动时给默认值。如果业务允许兜底,给网关加default流。

4.4 现象:历史表数据量暴涨,查询变慢

原因:history-level设为full且没有归档策略。ACT_HI_DETAIL会记录每一次变量更新,一个审批流跑一个月可能产生几十万行。解决:生产环境按act_hi_procinst.end_time_做分区或定期删除,或者把非关键流程的history-level降到audit。注意降级后ACT_HI_DETAIL不再写入,但ACT_HI_PROCINST和ACT_HI_TASKINST还在。

4.5 现象:并行网关后任务重复创建

原因:并行网关的分支里又嵌套了用户任务,且每个分支的assignee相同,导致同一个人看到多条待办。解决:在并行分支上用candidateUsers区分,或者在任务监听器里做去重。更根本的做法是重新审视流程设计,并行网关适合“同时做、互不依赖”的场景,如果两个任务有先后关系,应该用排他网关或串行。

5. 从能跑到好用:Activiti6.0 的进阶技巧与验证方法

5.1 用历史服务做流程审计和耗时分析

流程跑通只是第一步,业务方迟早会问“每个节点平均耗时多少”。Activiti6.0 的HistoryService提供了HistoricTaskInstanceQuery和HistoricProcessInstanceQuery,可以按流程定义、任务名称、时间范围聚合。下面这段代码查某个流程所有已完成任务的耗时,并按任务名分组。

public Map<String, Double> avgTaskDuration(String processKey) { List<HistoricTaskInstance> tasks = historyService .createHistoricTaskInstanceQuery() .processDefinitionKey(processKey) .finished() .list(); Map<String, List<Long>> grouped = new HashMap<>(); for (HistoricTaskInstance task : tasks) { long duration = task.getDurationInMillis(); grouped.computeIfAbsent(task.getName(), k -> new ArrayList<>()) .add(duration); } Map<String, Double> result = new HashMap<>(); grouped.forEach((name, list) -> { double avg = list.stream().mapToLong(Long::longValue).average().orElse(0); result.put(name, avg); }); return result; }

getDurationInMillis()返回的是任务从创建到完成的毫秒数。如果任务被认领后长时间不处理,这个值会很大,可以用来识别瓶颈节点。注意:finished()只查已完成任务,未完成的任务要用unfinished()。生产环境建议把结果写入独立的统计表,不要每次实时查历史表。

5.2 流程定义的版本控制与灰度

Activiti6.0 每次部署同一个 key 的 BPMN,版本号自动加一,新启动的实例走新版本,旧实例继续走旧版本。这个机制天然适合灰度:先部署新版本,只让部分用户启动新流程,观察一段时间再全量。验证方法是查ACT_RE_PROCDEF表的VERSION_字段,确认新旧版本并存。如果要强制旧实例迁移到新版本,可以用runtimeService.createProcessInstanceMigrationBuilder(),但迁移前必须保证新旧版本的节点 ID 一致,否则会抛MigrationException。我一般不建议在生产环境做自动迁移,手工处理更稳。

5.3 一个验证流程是否健康的检查清单

跑通之后,我会用下面这张表做一次自检,避免上线后才发现问题。

检查项验证方法合格标准
流程定义是否部署查ACT_RE_PROCDEF有记录且SUSPENSION_STATE_=1
变量是否完整启动后查ACT_RU_VARIABLE关键变量无 null
任务是否有办理人查ACT_RU_TASK.ASSIGNEE_非空或候选组非空
历史是否记录查ACT_HI_PROCINST有开始时间,结束后有结束时间
网关是否有默认流检查 BPMN XML排他网关有default属性
异步任务是否可控查ACT_RU_JOB无大量重试记录

这张表里的每一项都对应一次真实的翻车。比如“网关默认流”这一条,我见过一个流程因为条件写错,实例卡在网关三天没人发现,最后靠定时任务扫ACT_RU_EXECUTION才捞出来。后来我养成了一个习惯:任何排他网关,必须配默认流,哪怕默认流只是抛一个业务异常。这样至少能在日志里看到明确原因,而不是让实例静默挂起。

流程引擎这东西,配起来不难,难的是边界。Activiti6.0 的文档能告诉你 API 怎么调,但不会告诉你变量作用域混用会在并行网关后串数据,也不会告诉你历史表涨得比业务表还快。我的经验是:先把最小闭环跑通,再用检查清单过一遍,最后把监听器和网关条件当成代码一样做单元测试。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 13:13:54

C#入门经典教程:从环境搭建到面向对象与LINQ实战

简介&#xff1a;一份面向C#与.NET初学者的经典入门教程PPT&#xff0c;内容从.NET平台与CLR/JIT/MSIL等底层概念讲起&#xff0c;涵盖C#程序基本结构、面向对象设计、委托与泛型、集合类、文本处理及文件IO等内容&#xff0c;也适合有一定编程基础、希望系统了解.NET Framewor…

作者头像 李华
网站建设 2026/10/15 13:13:30

A3PE3000 Flash FPGA封装选型与工程实践:从PQ208到FG896

简介&#xff1a;A3PE3000 是 Microsemi 推出的 ProASIC3E 系列闪存 FPGA 芯片手册&#xff0c;面向嵌入式硬件、STM32/ARM 单片机系统与 FPGA 开发工程师&#xff0c;可用于器件选型、封装对比、引脚规划、电源树与时钟树设计、I/O 接口方案评估。手册覆盖 PQ208、FG256、FG32…

作者头像 李华
网站建设 2026/10/15 13:12:33

八数码问题BFS算法详解:Java实现与性能优化

简介&#xff1a;这是一份面向Java初学者与算法学习者的八数码问题宽度优先搜索实现资源&#xff0c;聚焦经典AI搜索问题——33滑动拼图的最短路径求解。资源以Java语言编写&#xff0c;借助队列存储待扩展状态、哈希表去重&#xff0c;完整呈现从状态表示、邻居生成到路径回溯…

作者头像 李华
网站建设 2026/10/15 13:12:14

斯坦福因果推断教材:从识别假设到工程落地的完整路线

简介&#xff1a;《因果推断&#xff1a;统计学习方法》是斯坦福大学Stefan Wager撰写的最新因果推断教科书&#xff08;2024年草稿版&#xff09;&#xff0c;面向数据科学、统计学、经济学及公共政策领域的研究者与学生&#xff0c;系统串联从随机对照试验到政策学习的完整知…

作者头像 李华
网站建设 2026/10/15 13:11:41

飞飞江湖v2.0论坛源码部署与二次开发实战指南

简介&#xff1a;《飞飞江湖 v2.0正式商业版》是一套面向Web开发者与社区建设者的论坛社区类源码资源&#xff0c;适合希望研究BBS系统架构、用户认证与权限管理实现的中高级开发者参考学习。资源以rar压缩包形式提供&#xff0c;包内文件总数与类型明细上游暂未给出&#xff0…

作者头像 李华
网站建设 2026/10/15 13:11:37

递归元嵌套函数与P/NP问题的计算复杂性新探

1. 项目概述这个标题涉及三个核心概念&#xff1a;真理的递归元嵌套函数范式、P/NP问题、佩雷尔曼证明思路的同构分析。乍看之下像是数学、计算机科学和哲学的交汇点&#xff0c;实际上这是一次对计算复杂性理论根基的深度探索。我在研究计算理论时发现&#xff0c;传统P/NP问题…

作者头像 李华