简介:本资源是面向Java开发工程师与流程自动化实践者的Activiti 6.0工作流引擎入门与实操指南,聚焦业务流程建模、任务类型配置与流程控制逻辑落地。文档系统梳理模型设计器核心能力,涵盖用户任务、服务任务、脚本任务、邮件任务等10类任务的适用场景与配置要点,并详解子流程、并行/排他分支、事件子流程等关键构造机制,助力开发者快速掌握流程定义、部署与调试全流程。资源为单文件PDF格式,共1个1.72MB文档,内容结构清晰,含37页完整目录与分章节技术说明,便于按需查阅与实践对照。目前已有1168人学习下载,适合初接触Activiti的工作流开发人员、企业OA/审批系统改造工程师及Spring Boot集成流程引擎的学习者系统入门与快速上手。
1. 这不是一份“翻完就扔”的 PDF:Activiti 6.0 工作流使用说明文档 v1.0 是你落地 BPMN 流程的实操黑匣子
如果你正在用 Spring Boot 集成工作流,却卡在「画完流程图,一部署就报错Invalid BPMN model」;或者调试时发现「定时器没触发、信号收不到、邮件发不出」,翻遍官网文档和 Stack Overflow 还是找不到对应配置项在哪——那你手头这份《Activiti6.0工作流使用说明文档v1.0.pdf》不是过时资料,而是当年某高校实验室在真实产线项目中沉淀下来的「避坑型操作手册」。它不讲抽象理论,37 页里有 28 页是设计器界面截图+属性面板填空式说明,连「log4j.logger.org.activiti.engine.impl.persistence.entity=DEBUG」这种打开 SQL 日志的开关都标在第 4 页脚注里。它专为两类人存在:一是刚接手遗留 Activiti 6 项目的维护工程师,需要快速定位Camel 任务怎么配 Spring Bean、决策表变量为空导致后续${riskCheck==false}报错的根因;二是想绕过官方 User Guide 的冗长描述,直接抄参数、粘配置、复现 zip 示例包的实战派。它不承诺兼容 7.x,但能让你在 JDK8 + Spring 4.x + Tomcat 8 的老环境中,把「用户审批 → 调用风控服务 → 自动发邮件 → 超时自动升级」这条链路真正跑通。
2. 模型设计器不是画布,是流程逻辑的编译器:从拖拽元素到生成可执行 BPMN 文件的完整闭环
Activiti 6 的模型设计器(qqdznyyglpt-workflow-designer)本质是一个 BPMN 2.0 编译前端:你拖进去的每个图标,最终都会被翻译成<userTask>、<serviceTask>等 XML 元素,并注入具体属性值。它的输出不是图片,而是.bpmn20.xml文件——这才是引擎真正加载的对象。理解这一点,才能避开「界面看着对,运行就报错」的玄学现场。
2.1 任务节点:10 类任务的本质差异与选型铁律
任务是流程的原子单元,但不同任务的执行机制天差地别。选错类型,轻则功能失效,重则流程卡死。
2.1.1 用户任务:必须人工介入的强制关卡
用户任务(<userTask>)是唯一要求「人」参与的节点。关键配置项不是名称或描述,而是assignee(处理人)和candidateGroups(候选组):
assignee填admin表示固定分配给 admin 用户;candidateGroups填hr,finance表示 HR 和财务组所有成员都能拾取该任务;- 若两者都为空,任务将处于
unclaimed状态,需调用taskService.claim(taskId, userId)手动认领。
提示:生产环境严禁硬编码
assignee="admin"。应使用表达式${initiator}(发起人)或${ldapService.getApproverByDept(dept)}(动态查LDAP),否则流程无法流转。
2.1.2 服务任务:三种实现方式的边界与血泪经验
服务任务(<serviceTask>)用于执行 Java 逻辑,但类、表达式、委托表达式三者不可混用,且依赖注入方式完全不同:
| 实现方式 | 配置位置 | 依赖注入要求 | 典型错误场景 |
|---|---|---|---|
| 类(Class) | 属性面板Class字段 | 类必须实现JavaDelegate或ActivityBehavior,且含@Override execute(DelegateExecution)方法 | 忘记在activiti.cfg.xml中注册该类为 bean,导致ClassNotFoundException |
| 表达式(Expression) | Field→Name: resultVar,String value: ${myService.doSomething()} | 方法必须返回值,且resultVar名称需全局唯一 | 在resultVar中用了${}表达式,导致解析失败 |
| 委托表达式(Delegate Expression) | Delegate expression字段,填#{myServiceBean} | myServiceBean必须在 Spring 容器中声明为@Bean,且实现JavaDelegate | Spring 上下文未加载,#{myServiceBean}解析为空 |
实际项目中,我一般会强制走委托表达式路线:
- 在
SpringConfig.java中声明:
@Bean public MyOrderService myOrderService() { return new MyOrderService(); }- 设计器中
Delegate expression填#{myOrderService},Field中添加orderNo=${execution.getVariable("orderNo")}传参。
这样既保证 Spring 管理生命周期,又避免类路径硬编码。
2.1.3 脚本任务:JavaScript 引擎的隐形依赖与变量陷阱
脚本任务(<scriptTask>)默认用 Nashorn(JDK8 内置 JS 引擎),但必须显式声明脚本格式:
- 属性面板
Script format填javascript(不能写js或留空); - 脚本内容中禁止使用
out,context,elcontext等保留字作为变量名(文档第 6 页明确警告); - 设置流程变量必须用
execution.setVariable("key", value),不能用var key = value。
一个典型翻车案例:
// ❌ 错误:用 var 声明,变量仅在脚本作用域内 var amount = execution.getVariable("loanAmount"); if (amount > 10000) { execution.setVariable("riskLevel", "high"); // 此处设值有效 } else { execution.setVariable("riskLevel", "low"); // 此处设值有效 } // 但下面这行无效!因为 amount 是局部变量,无法被后续节点引用 execution.setVariable("amount", amount); // ✅ 必须显式 set2.1.4 业务规则任务:Drools 规则文件的打包与加载规范
业务规则任务(<businessRuleTask>)依赖 Drools 引擎,但规则文件(.drl)必须和 BPMN 文件同级打包:
- 正确结构:
process.bpmn20.xml+rules.drl(同一 ZIP 包内); - 设计器中
Rule files字段填rules.drl(若多个用逗号分隔); - 规则中
input变量名必须与流程变量名完全一致(大小写敏感),output只能有一个变量名(如decisionResult)。
若规则中定义rule "CheckLoan" when $l: Loan(amount > 10000) then $l.setRisk("high"); end,则流程中必须存在名为Loan的变量对象,且含amount属性。否则规则不触发,且无任何日志提示——这是最隐蔽的坑。
2.2 分支与网关:排他、并行、包容、事件四类网关的执行逻辑拆解
网关控制流程走向,但每种网关的「条件判断时机」和「实例生成策略」完全不同,直接决定流程是否按预期并发或串行。
2.2.1 排他网关(Exclusive Gateway):单选题,但默认选第一个
排他网关(<exclusiveGateway>)像一道单选题:
- 每条出线可配置
conditionExpression(如${loanAmount > 5000}); - 引擎从上到下扫描,遇到第一个为
true的条件即执行,其余忽略; - 若所有条件为
false,抛出ActivitiException: No outgoing sequence flow of the exclusive gateway ...。
注意:条件表达式必须返回布尔值。
${status == 'APPROVED'}合法,${status}不合法(返回字符串,非布尔)。
2.2.2 并行网关(Parallel Gateway):强制并发,条件失效
并行网关(<parallelGateway>)的分叉(Fork)和聚合(Join)是强绑定的:
- 分叉后,所有出线任务必定同时启动,无论是否配置条件;
- 聚合时,必须等待所有入线任务完成才继续;
- 若某条线任务失败,整个流程卡在聚合点,除非配置
async或异常处理。
典型误用:想让「审核A」和「审核B」并行,但希望「审核B」只在dept == 'finance'时执行。此时不能在并行网关出线上写条件,而应改用包容网关。
2.2.3 包容网关(Inclusive Gateway):带条件的并行,但聚合逻辑更严苛
包容网关(<inclusiveGateway>)是排他与并行的混合体:
- 分叉时,只启动条件为
true的出线任务; - 聚合时,必须等待所有「已启动」的任务完成才继续(不是所有出线,而是所有满足条件的出线);
- 若无一条出线条件为
true,流程直接卡死(同排他网关)。
配置要点:
- 分叉前必须有
inclusiveGateway标签; - 每条出线
conditionExpression必须明确(不能留空); - 聚合点也必须是
inclusiveGateway,且default属性无效。
2.2.4 事件网关(Event Gateway):挂起等待,非抢占式分支
事件网关(<eventGateway>)不基于条件,而是等待事件发生:
- 出线只能连接中间捕获事件(如
messageEvent,signalEvent,timerEvent); - 流程执行到此即挂起,订阅所有出线事件;
- 任一事件触发,其他事件订阅自动取消,流程沿该事件线继续。
关键限制:
- 至少连接2 个中间捕获事件(文档第 13 页强调,否则部署失败);
- 每个事件只能有一个输入流(不能从其他节点连入);
- 定时器事件需配置
timeDuration(如PT10M表示 10 分钟),否则不生效。
2.3 事件:捕获与抛出的生命周期管理
事件是流程的「神经末梢」,但捕获(Catch)和抛出(Throw)行为严格分离,且生命周期由引擎托管。
2.3.1 定时器事件:三种配置格式的语法细节
定时器事件(<timerEventDefinition>)必须配置timeDate、timeDuration或timeCycle之一,否则发布失败。常见错误:
| 配置类型 | 正确示例 | 常见错误 | 原因 |
|---|---|---|---|
timeDate | 2023-12-25T09:00:00+08:00 | 2023/12/25 09:00:00 | 必须 ISO-8601 格式,T和时区+08:00不可省略 |
timeDuration | PT30M(30分钟) | P30M(30个月) | P后跟T表示时间,不跟T表示周期(年月日) |
timeCycle | R3/PT1H(循环3次,间隔1小时) | R/PT1H(无限循环) | R后必须跟数字,否则引擎无法解析 |
提示:
timeCycle的 cron 表达式需用0 0/5 * * * ?格式(秒 分 时 日 月 周 年),且jobExecutorActivate=true必须在activiti.cfg.xml中开启。
2.3.2 信号事件:全局与实例作用域的隔离机制
信号事件(<signalEventDefinition>)分两种作用域:
- 全局信号:
RuntimeService.signalEventReceived("alert"),所有订阅该信号的流程实例均接收; - 实例信号:
RuntimeService.signalEventReceived("alert", executionId),仅指定实例接收。
关键约束:
- 捕获信号只能捕获「信号抛出之后」的事件(文档第 14 页),即流程必须先到达信号捕获节点,再抛出信号;
- 若流程尚未到达捕获节点就抛出信号,该信号丢失,无日志;
- 查询订阅:
runtimeService.createExecutionQuery().signalEventSubscriptionName("alert").list()返回当前所有等待该信号的执行实例。
2.3.3 消息事件:点对点通信的协议约定
消息事件(<messageEventDefinition>)与信号核心区别是一对一:
- 每个消息名(
messageName)唯一对应一个接收方; - 抛出消息必须指定
executionId(不能全局); - 消息内容通过
HashMap<String, Object>传递,如:
Map<String, Object> vars = new HashMap<>(); vars.put("orderId", "ORD-2023-001"); runtimeService.messageEventReceived("orderConfirmed", executionId, vars);- 接收方在
messageEvent节点中通过${orderId}获取变量。
3. workflow-service 模块:流程服务层的配置与方法调用契约
workflow-service是连接设计器与业务系统的胶水层,其核心是ProcessEngineConfiguration的定制化。它不提供新功能,但决定了「流程如何与你的代码对话」。
3.1 数据更新逻辑:流程变量与业务数据的双向同步
流程变量(execution.setVariable())默认存储在 Activiti 自身数据库中,但业务系统通常有自己的数据表。workflow-service通过监听器实现同步:
3.1.1 执行监听器(Execution Listener):流程节点级别的钩子
在 BPMN 文件中为节点添加:
<userTask id="approveTask" name="审批"> <extensionElements> <activiti:executionListener event="start" class="com.example.listener.ApproveStartListener"/> <activiti:executionListener event="end" delegateExpression="${approveEndListener}"/> </extensionElements> </userTask>event="start":节点开始执行前触发;event="end":节点执行完成后触发(无论成功或失败);class方式需实现ExecutionListener接口;delegateExpression方式需 Spring Bean 实现ExecutionListener。
典型用途:
start时校验前置条件(如检查申请人是否有权限);end时更新业务表状态(如UPDATE order SET status='APPROVED' WHERE id=#{orderId})。
3.1.2 任务监听器(Task Listener):任务生命周期的精细控制
任务监听器(<activiti:taskListener>)作用于任务状态变更:
event="create":任务创建时(taskService.newTask()后);event="assign":任务被分配时(taskService.claim()后);event="complete":任务完成时(taskService.complete()后)。
注意:
assign事件在claim时触发,但create事件在流程启动时即触发(即使任务未被领取)。因此,发送待办通知应在create事件中,而非assign。
3.2 方法表达式配置:让流程调用你的 Service 方法
方法表达式是服务任务的简化版,但需严格遵循 SpEL 语法:
3.2.1 方法调用的参数传递规范
在服务任务属性中:
Expression字段填#{orderService.confirmOrder(execution.getVariable('orderId'), execution.getVariable('userId'))};Result variable字段填confirmResult(接收返回值)。
关键约束:
- 方法参数必须是流程变量,不能直接写字符串字面量(如
#{orderService.confirmOrder('ORD-001', 'admin')}会报错); - 若方法返回对象,
confirmResult将存入流程变量,后续节点可用${confirmResult.status}访问。
3.2.2 外置表单(External Form)的集成路径
外置表单(HTML/JS)需通过formKey关联:
- BPMN 中用户任务设置
formKey="orderForm"; workflow-service需提供/form/orderForm接口,返回表单 HTML;- 表单提交时,
POST /runtime/tasks/{taskId}/variables提交变量。
提示:表单中字段名必须与流程变量名一致(如
<input name="amount">对应${amount}),否则变量无法注入。
3.3 事件监听器配置:全局事件的捕获与响应
全局事件监听器(org.activiti.engine.delegate.event.ActivitiEventListener)可监听所有流程事件:
3.3.1 监听器注册方式
在activiti.cfg.xml中:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="eventListeners"> <list> <bean class="com.example.listener.GlobalProcessListener"/> </list> </property> </bean>3.3.2 关键事件类型与处理建议
| 事件类型 | 触发时机 | 推荐处理动作 | 避坑点 |
|---|---|---|---|
EVENTTYPE_PROCESS_STARTED | 流程实例启动 | 记录日志、初始化监控指标 | 避免在此处调用耗时 DB 操作,阻塞流程启动 |
EVENTTYPE_TASK_ASSIGNED | 任务分配 | 发送站内信、邮件提醒 | event.getTask().getAssignee()可能为 null(未分配) |
EVENTTYPE_ACTIVITY_COMPLETED | 节点完成 | 更新业务状态、触发下游流程 | event.getActivityId()返回节点 ID,非任务 ID |
4. 避坑指南:Activiti 6.0 设计器与引擎的 5 个高频翻车现场
这些坑我在三个项目中反复踩过,每次排查都耗掉半天。列在这里,帮你省下 15 小时 debug 时间。
4.1 现象:流程部署成功,但启动时报Invalid BPMN model: no start event found
原因:BPMN 文件中确实没有<startEvent>节点,或开始事件未正确连接到第一个任务。设计器有时会因拖拽失误导致开始事件「悬浮」在画布外,XML 中<startEvent>存在但无outgoing连接。
解决:打开.bpmn20.xml文件,搜索<startEvent>,确认其id是否在<sequenceFlow sourceRef="xxx" targetRef="yyy"/>中作为sourceRef出现。若无,手动添加<sequenceFlow sourceRef="startEventId" targetRef="firstTaskId"/>。
4.2 现象:定时器事件(Timer Event)始终不触发,日志无任何报错
原因:jobExecutorActivate或asyncExecutorActivate未启用。Activiti 6 默认关闭异步执行器,定时器、邮件等异步任务全部失效。
解决:在activiti.cfg.xml中添加:
<property name="jobExecutorActivate" value="true"/> <!-- 或 --> <property name="asyncExecutorActivate" value="true"/>并确保activiti-app.properties中mail.server.host等配置正确(影响邮件任务,但定时器依赖执行器本身)。
4.3 现象:信号事件(Signal Event)抛出后,流程无响应,createExecutionQuery().signalEventSubscriptionName(...).list()返回空列表
原因:流程未执行到信号捕获节点就抛出了信号。信号是「广播」,但捕获是「订阅」,必须先订阅后广播。
解决:在流程中插入一个User Task或Service Task,确保流程至少执行一步后再到达信号捕获节点。例如:
Start → Dummy Task(空服务任务,仅 sleep(100))→ Signal Catch → Next Node然后在Dummy Task的end监听器中抛出信号。
4.4 现象:决策任务(Business Rule Task)中${riskCheck==false}报错Cannot resolve identifier 'riskCheck'
原因:决策表(.drl)中未为riskCheck输出变量赋值,或规则未匹配导致变量未创建。Activiti 不会为未命中的规则创建输出变量。
解决:在.drl文件末尾添加兜底规则:
rule "Default Risk Check" when // 无条件 then $decision.setRiskCheck(false); end确保所有路径都有输出。
4.5 现象:邮件任务(Mail Task)配置了activiti-app.properties,但启动时抛javax.mail.AuthenticationFailedException
原因:activiti-app.properties中mail.server.password是明文,但某些邮件服务器(如 QQ 邮箱)要求使用「授权码」而非登录密码,且授权码需 URL 编码。
解决:将授权码进行 URL 编码(如abc123!@#编码为abc123%21%40%23),再填入配置:
mail.server.password=abc123%21%40%23并在log4j.properties中开启DEBUG级日志,确认org.activiti.engine.impl.bpmn.behavior.MailActivityBehavior是否正常加载。
5. 验证与调试:用三步法把「流程跑起来」变成「流程稳得住」
部署一个流程不难,难的是确保它在高并发、异常网络、数据库抖动下依然可靠。我总结了一套验证闭环,从单点测试到混沌压测,覆盖 95% 的线上问题。
5.1 第一步:SQL 日志 + 流程实例快照,定位执行断点
Activiti 的最大优势是全程可追溯。开启 SQL 日志是诊断的第一步:
- 修改
log4j.properties:
log4j.logger.org.activiti.engine.impl.persistence.entity=DEBUG log4j.logger.org.activiti.engine.impl.jobexecutor=DEBUG- 启动应用,执行一次流程;
- 查看日志中
INSERT INTO ACT_RU_EXECUTION和UPDATE ACT_RU_TASK的 SQL,确认:- 流程实例 ID(
ID_)是否生成; - 当前执行节点(
ACT_ID_)是否指向预期任务; - 任务表(
ACT_RU_TASK)中ASSIGNEE_是否为空(未分配)或有值(已分配)。
- 流程实例 ID(
提示:若
ACT_RU_EXECUTION中IS_ACTIVE_=0且END_TIME_为空,说明流程卡在某个节点,需检查该节点的异步配置或监听器是否抛异常。
5.2 第二步:用 REST API 模拟全流程,绕过前端干扰
前端页面可能隐藏表单校验或 JS 错误,直接调用 Activiti REST API 可排除干扰:
- 启动流程实例:
curl -X POST "http://localhost:8080/activiti-rest/service/runtime/process-instances" \ -H "Content-Type: application/json" \ -u "admin:test" \ -d '{"processDefinitionKey":"orderProcess","variables":[{"name":"orderNo","value":"ORD-001"},{"name":"amount","value":9999}]}'- 查询待办任务:
curl "http://localhost:8080/activiti-rest/service/runtime/tasks?assignee=admin" \ -u "admin:test"- 完成任务:
curl -X POST "http://localhost:8080/activiti-rest/service/runtime/tasks/ taskId/complete" \ -H "Content-Type: application/json" \ -u "admin:test" \ -d '{"variables":[{"name":"approved","value":true}]}'若 API 能跑通而前端不行,问题一定在前端 JS 或表单渲染逻辑。
5.3 第三步:混沌测试:主动制造故障,验证补偿与重试
生产环境不会总是一帆风顺。我习惯在预发环境做三类主动故障测试:
- 数据库连接中断:停掉 MySQL,观察流程是否卡在
ACT_RU_EXECUTION,恢复后是否自动重试(需配置asyncExecutor); - 服务任务超时:在
MyOrderService.confirmOrder()中加Thread.sleep(60000),确认async=true的节点是否挂起,而主线程继续; - 补偿事件触发:在事务子流程中,故意让服务任务抛
BpmnError("PAYMENT_FAILED"),检查边界补偿事件是否执行refund()方法。
从那以后我每次上线新流程,都强制走一遍这三步:SQL 日志确认执行路径 → REST API 验证接口契约 → 混沌测试验证容错能力。少一次,就可能多一个凌晨三点的告警电话。希望帮到你。
本文还有配套的精品资源,点击获取