三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Activiti7会签避坑指南:多实例任务完成条件与监听器变量传递的那些坑

Activiti7会签避坑指南:多实例任务完成条件与监听器变量传递的那些坑

Activiti7会签实战避坑手册:多实例任务与变量传递的深度解析

上周团队在重构审批流时,一个看似简单的会签功能让我们连续加班三个晚上——当第五次部署后流程依然卡在网关判断时,我才意识到Activiti7的多实例任务远比想象中复杂。本文将结合真实项目中的血泪教训,拆解会签实现中最容易踩坑的五个技术点。

1. 多实例任务配置的魔鬼细节

很多开发者第一次配置会签时,往往直接复制文档中的基础模板,却忽略了几个关键参数的实际作用机制。以下是一个经过生产验证的标准配置:

<userTask id="countersignTask" name="会签审批"> <multiInstanceLoopCharacteristics activiti:collection="${countersignerList}" activiti:elementVariable="assignee"> <completionCondition>${(pass == 'no') || (nrOfCompletedInstances == nrOfInstances)}</completionCondition> </multiInstanceLoopCharacteristics> </userTask>

常见配置误区对比表

错误配置正确写法导致的后果
${nrOfCompletedInstances/nrOfInstances == 1}${nrOfCompletedInstances == nrOfInstances}浮点运算精度问题导致条件永不触发
collection="assigneeList"collection="${assigneeList}"无法解析变量导致空指针异常
缺失elementVariable明确指定activiti:elementVariable任务实例无法获取当前处理人

提示:Activiti 7.1.0.M6版本后,建议使用nrOfCompletedInstances == nrOfInstances替代除法运算,避免Java浮点数比较的精度问题。

2. 变量作用域的血泪教训

在调试一个"幽灵变量"问题时,我们发现不同环节读取的pass值竟然不一致。根本原因在于没有理清Activiti的变量作用域体系:

变量作用域层级

  1. 流程实例级(Process Instance)
    • 通过runtimeService.setVariable(executionId, key, value)设置
    • 整个流程生命周期有效
  2. 执行实例级(Execution)
    • 通过execution.setVariable(key, value)设置
    • 仅在当前分支有效
  3. 任务实例级(Task)
    • 通过taskService.setVariable(taskId, key, value)设置
    • 仅对当前任务有效
// 典型错误示例 - 在监听器中混用作用域 public void notify(DelegateExecution execution) { // 错误:可能修改的是执行实例级变量 execution.setVariable("pass", "no"); // 正确:显式指定作用域 execution.getEngineServices().getRuntimeService() .setVariable(execution.getProcessInstanceId(), "pass", "no"); }

3. 监听器中的变量陷阱

监听器是实现会签逻辑的核心组件,但90%的坑都集中在这里。以下是经过20次调试总结的最佳实践:

执行监听器配置要点

<extensionElements> <activiti:executionListener class="com.example.CountersignListener" event="end" /> </extensionElements>

关键处理逻辑

public class CountersignListener implements ExecutionListener { @Override public void notify(DelegateExecution execution) { // 必须从父执行实例获取多实例计数 Integer completed = (Integer) execution.getParent().getVariable("nrOfCompletedInstances"); Integer total = (Integer) execution.getParent().getVariable("nrOfInstances"); // 获取表单字段值的安全写法 String opinion = Objects.toString(execution.getVariable("FormProperty_opinion"), ""); if ("reject".equals(opinion)) { // 必须设置到流程实例级别 execution.getEngineServices().getRuntimeService() .setVariable(execution.getProcessInstanceId(), "pass", "no"); } else if (completed + 1 == total) { execution.setVariable("result", "approve"); } } }

注意:在并行多实例场景下,直接操作execution的变量可能导致线程安全问题,建议配合synchronized块或使用流程实例级变量。

4. 网关判断的隐藏逻辑

当会签任务完成后,排他网关的表达式配置直接影响流程走向。常见问题包括:

网关配置对比

<sequenceFlow id="rejectFlow" sourceRef="gateway" targetRef="rejectTask"> <!-- 不推荐:字符串比较未做trim --> <conditionExpression>${result=='N'}</conditionExpression> </sequenceFlow> <sequenceFlow id="approveFlow" sourceRef="gateway" targetRef="approveTask"> <!-- 推荐:增加null判断 --> <conditionExpression>${result!=null && result.trim().equals('Y')}</conditionExpression> </sequenceFlow>

网关判断的黄金法则

  1. 始终在条件表达式中处理null值
  2. 字符串比较前执行trim()
  3. 复杂逻辑建议使用<scriptTask>预处理
  4. 在测试环境验证所有可能的分支组合

5. 实战调试技巧

当会签流程出现异常时,按以下步骤排查:

诊断命令清单

# 查看当前活动节点 SELECT * FROM ACT_RU_TASK WHERE PROC_INST_ID_ = '流程实例ID'; # 检查变量实际值 SELECT * FROM ACT_RU_VARIABLE WHERE EXECUTION_ID_ = '执行实例ID'; # 获取多实例计数 SELECT * FROM ACT_RU_EXECUTION WHERE PARENT_ID_ = '父执行ID';

IDEA调试配置

  1. activiti.cfg.xml中开启调试模式:
<property name="databaseSchemaUpdate" value="true"/> <property name="asyncExecutorActivate" value="false"/>
  1. ExecutionListener实现类打条件断点
  2. 使用Activiti Explorer实时观察流程状态

记得那次凌晨三点,我们发现网关不跳转是因为某个审批人的意见字段包含不可见UTF-8字符。现在团队强制所有表单输入都经过以下处理:

public static String sanitizeInput(String input) { return input == null ? "" : input.replaceAll("[\\u0000-\\u001f]", "") .trim(); }
← 返回列表