Activiti7会签实战避坑:从‘一票否决’到‘比例通过’,我的自定义完成条件开发笔记

张开发
2026/6/8 22:52:12 15 分钟阅读

分享文章

Activiti7会签实战避坑:从‘一票否决’到‘比例通过’,我的自定义完成条件开发笔记
Activiti7会签实战避坑从‘一票否决’到‘比例通过’我的自定义完成条件开发笔记当审批流程遇到需要5个部门负责人中至少3人同意且财务总监必须投赞成票这类复杂规则时Activiti7原生的会签功能就显得捉襟见肘。本文将分享如何突破引擎限制实现基于角色权重的动态审批逻辑并解决实际开发中那些教科书不会告诉你的坑点。1. 为什么需要自定义完成条件去年我们为某金融机构重构信贷审批系统时遇到了这样的业务场景一笔大额贷款需要经过风险、法务、财务等7个部门的联合审批但不同部门的投票权重不同——风险部门的1票相当于普通部门的2票且财务总监具有一票否决权。Activiti7原生的三种会签变量nrOfInstances/nrOfCompletedInstances/nrOfActiveInstances显然无法满足这种需求。更棘手的是审批过程中还需要实时检查外部风控系统的黑名单状态。经过多次技术验证我们最终确定了自定义完成条件的解决方案。2. 核心实现MultiInstanceCompleteCondition2.1 基础框架搭建首先创建实现类继承JavaDelegate注意要标记为Spring组件以支持依赖注入Component public class WeightedApprovalCondition implements JavaDelegate { Autowired private RiskControlService riskControlService; Override public void execute(DelegateExecution execution) { // 实现逻辑将在下文展开 } }在BPMN配置中引用这个类作为完成条件multiInstanceLoopCharacteristics isSequentialfalse completionCondition${weightedApprovalCondition.isComplete(execution)} !-- 其他配置 -- /multiInstanceLoopCharacteristics2.2 动态权重计算逻辑我们通过流程变量和外部服务的组合实现权重计算public boolean isComplete(DelegateExecution execution) { // 获取基础会签数据 int total (int) execution.getVariable(nrOfInstances); int completed (int) execution.getVariable(nrOfCompletedInstances); // 从流程变量读取自定义权重规则 MapString, Integer weightRules (MapString, Integer) execution.getVariable(approvalWeightRules); // 计算当前总权重 int totalWeight calculateTotalWeight(execution, weightRules); int approvedWeight calculateApprovedWeight(execution, weightRules); // 检查财务总监是否行使否决权 if (hasVetoRejection(execution)) { return true; // 提前终止流程 } // 检查外部风控状态 if (riskControlService.checkBlockStatus(execution.getBusinessKey())) { execution.setVariable(rejectionReason, 风控系统拦截); return true; } // 双重条件判断 return approvedWeight totalWeight * 0.6 completed total / 2 1; }关键点说明calculateTotalWeight方法会遍历所有审批人累加其角色对应的权重calculateApprovedWeight只计算已同意审批人的权重风控检查通过注入的Spring Bean实现3. 那些年我们踩过的坑3.1 幽灵任务问题在测试阶段发现当快速连续完成多个审批时偶尔会出现已经完成的任务再次出现的现象。根本原因是Activiti的异步机制导致状态更新延迟在高并发场景下可能出现脏读解决方案// 在条件判断前强制同步状态 managementService.executeCommand(new CommandVoid() { public Void execute(CommandContext commandContext) { commandContext.getDbSqlSession().flush(); return null; } });3.2 变量传递失效当会签节点跳转到下一个节点时自定义变量经常丢失。这是因为多实例节点会创建新的执行流(execution)默认只传递流程级别的变量正确的变量传递方式// 在完成条件类中设置流程级变量 execution.getProcessInstance().setVariable(finalApproval, result); // 或者使用执行流继承 execution.setVariableLocal(tempData, value, true);3.3 回退时的数据一致性当审批被拒绝需要回退时必须清理中间状态// 在退回命令中增加清理逻辑 public class RollbackCommand implements CommandVoid { public Void execute(CommandContext context) { // 1. 清理会签产生的临时变量 execution.removeVariablesLocal(); // 2. 重置审批状态 historyService.createHistoricVariableInstanceUpdate() .processInstanceId(processInstanceId) .variable(approvalStatus, REJECTED) .execute(); // 3. 执行默认退回逻辑 new MoveTaskCommand(taskId, targetNodeId).execute(context); } }4. 性能优化实践当审批人超过20个时我们发现性能明显下降。通过以下优化手段将响应时间从1200ms降到200ms内优化方案对比表优化点原始方案优化方案效果提升权重计算实时查询数据库预加载到流程变量65%风控检查同步调用异步预检缓存结果40%历史记录全量保存只记录关键操作30%变量存取频繁读写流程变量使用Execution本地变量50%关键代码实现// 预加载权重数据 StartEventListener public void onProcessStart(DelegateExecution execution) { ListApprover approvers approverService.getByProcessKey(execution.getProcessDefinitionId()); MapString, Integer weightMap approvers.stream() .collect(Collectors.toMap(Approver::getUserId, Approver::getWeight)); execution.setVariable(weightMap, weightMap); // 异步预检风控状态 riskControlService.asyncCheck(execution.getBusinessKey()); }5. 测试策略建议针对自定义会签逻辑我们建立了四层测试体系单元测试验证权重计算等纯逻辑Test void testWeightCalculation() { MapString, Integer rules Map.of(RISK, 2, NORMAL, 1); int weight calculator.calculateTotalWeight(approvers, rules); assertEquals(8, weight); }集成测试验证与Activiti引擎的交互SpringBootTest class MultiInstanceIT { Test void shouldCompleteWhenMajorityApproved() { // 启动流程并模拟审批操作 ProcessInstance instance runtimeService.startProcessInstanceByKey(...); taskService.complete(task1.getId(), approveVariables); taskService.complete(task2.getId(), approveVariables); // 验证是否跳转到正确节点 assertThat(runtimeService.getActiveActivityIds(instance.getId())) .containsExactly(nextStep); } }并发测试使用JMeter模拟50用户并发审批jmeter -n -t src/test/resources/jmeter/concurrent_approval.jmx -l result.jtl异常测试模拟网络超时、服务宕机等场景特别提醒一定要测试以下边界情况最后一个审批人拒绝时权重刚好达到临界值时外部服务不可用时审批人列表为空时6. 生产环境监控方案上线后我们通过以下手段确保稳定性监控指标看板配置// 在完成条件类中添加埋点 public boolean isComplete(DelegateExecution execution) { long start System.currentTimeMillis(); try { // 业务逻辑 return result; } finally { metrics.recordExecutionTime( approval_condition, System.currentTimeMillis() - start, execution.getProcessDefinitionId()); } }关键监控项包括会签平均完成时间各审批节点通过率外部服务调用成功率变量存取异常次数当我们在ELK中看到这样的日志模式时往往意味着有问题WARN 完成条件执行超过2000ms [processDefinitionIdloanApproval:3] INFO 检测到重复审批尝试 [taskId23523, userIdrisk_leader]7. 扩展思考动态调整审批规则在项目运行半年后业务部门提出了新需求能否在流程运行过程中临时调整投票规则我们通过以下架构实现了这个功能核心实现逻辑// 在完成条件中检查规则版本 public boolean isComplete(DelegateExecution execution) { int currentVersion (int) execution.getVariable(ruleVersion); int latestVersion ruleService.getLatestVersion(); if (currentVersion latestVersion) { Rule newRule ruleService.getRule(latestVersion); execution.setVariable(approvalWeightRules, newRule.getWeights()); execution.setVariable(ruleVersion, latestVersion); } // 继续原有逻辑 }这个改进使得业务人员可以在管理后台实时调整各角色的投票权重通过率阈值必须审批的角色列表而正在运行的流程实例会在下次审批时自动应用新规则。

更多文章