Activiti7 会签多实例任务实战:动态审批人、一票否决与网关路由详解
1. 会签多实例任务的核心概念会签是工作流中常见的审批场景多个审批人需要同时对同一事项进行审批。在Activiti7中这种需求可以通过多实例任务来实现。简单来说就是把一个任务复制成多份分发给不同的审批人。想象一下公司采购申请的审批流程财务、法务、技术三个部门需要同时审核。传统串行审批需要等前一个部门完成才能到下一个而会签模式下三个部门可以同时处理效率提升明显。Activiti7的多实例任务实现依赖于三个关键属性collection审批人集合比如[财务,法务,技术]elementVariable当前处理的审批人变量名completionCondition任务完成条件实际项目中我遇到过这样的坑忘记设置elementVariable导致任务无法分配。正确的做法应该像这样配置multiInstanceLoopCharacteristics activiti:collectionapprovers activiti:elementVariableapprover completionCondition${nrOfCompletedInstances/nrOfInstances 0.6}/completionCondition /multiInstanceLoopCharacteristics2. 动态设置审批人实战静态配置审批人的方式在实际开发中基本不可行因为审批人可能来自组织架构、角色配置或特定业务规则。通过RuntimeService传递动态审批人集合才是正确姿势。最近给客户做OA系统时我们是这样实现的先通过组织架构接口获取部门经理列表再结合业务规则过滤出最终审批人。核心代码如下// 获取动态审批人列表 ListString approvers orgService.getDepartmentManagers(deptId); // 添加业务规则筛选 approvers approvers.stream() .filter(u - !conflictCheckService.hasConflict(u, businessId)) .collect(Collectors.toList()); // 启动流程时传入变量 MapString, Object vars new HashMap(); vars.put(approvers, approvers); vars.put(approverCount, approvers.size()); runtimeService.startProcessInstanceByKey(multiApprove, vars);特别注意当审批人列表为空时需要特殊处理。我们的做法是添加默认审批人并记录告警日志避免流程卡死。3. 一票否决机制的实现一票否决是会签流程的关键特性。在Activiti中可以通过两种方式实现完成条件表达式completionCondition ${rejected || nrOfCompletedInstances nrOfInstances} /completionCondition执行监听器动态干预public class RejectListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { String comment (String)execution.getVariable(comment); if(reject.equals(comment)){ execution.setVariable(rejected, true); // 强制终止其他实例 runtimeService.deleteMultiInstanceExecution( execution.getId(), false); } } }实测发现第一种方式更简洁但第二种更灵活。比如我们有个项目需要在否决时自动发送加急通知就只能用监听器实现。4. 网关路由的智能判断会签完成后通常需要根据结果路由到不同分支。排他网关Exclusive Gateway配合条件表达式是最佳选择。这里分享几个实用技巧条件表达式优化conditionExpression xsi:typetFormalExpression !-- 更健壮的判断逻辑 -- ${!rejected approvedCount 0} /conditionExpression常见问题排查表达式返回null会导致流程挂起变量作用域问题建议使用execution.setVariable类型转换异常字符串和布尔值比较要特别注意在最近的项目中我们封装了安全的条件表达式工具类public class ExpressionUtil { public static boolean safeEval(DelegateExecution exec, String expr) { try { Object result runtimeService .createConditionEvaluation(exec) .evaluate(expr); return Boolean.TRUE.equals(result); } catch (Exception e) { log.warn(表达式执行失败, e); return false; } } }5. 完整实现案例解析结合上述知识点我们来看一个生产级实现。这个案例包含以下特性动态审批人设置一票否决机制智能路由判断审批意见收集流程定义关键部分userTask idmultiApprove name会签审批 multiInstanceLoopCharacteristics activiti:collection${approvers} activiti:elementVariablecurrentApprover completionCondition ${rejected || nrOfCompletedInstances nrOfInstances} /completionCondition /multiInstanceLoopCharacteristics extensionElements activiti:formProperty idcomment / activiti:executionListener eventend classcom.example.RejectListener/ /extensionElements /userTask exclusiveGateway iddecisionGateway / sequenceFlow sourceRefdecisionGateway targetRefrejectTask conditionExpression${rejected}/conditionExpression /sequenceFlow sequenceFlow sourceRefdecisionGateway targetRefnextStep conditionExpression${!rejected}/conditionExpression /sequenceFlow监听器实现要点public class RejectListener implements ExecutionListener { Override public void notify(DelegateExecution exec) { String comment (String)exec.getVariable(comment); if(comment ! null comment.contains(拒绝)){ // 设置否决标志 exec.setVariable(rejected, true); // 记录否决人 String rejecter (String)exec.getVariable(currentApprover); exec.setVariable(rejecter, rejecter); // 发送实时通知 notifyService.sendRejectAlert(rejecter); } } }这个方案在某大型企业的合同审批流程中稳定运行了2年多日均处理3000流程实例。关键优化点在于使用异步监听器处理通知逻辑添加了完备的日志记录对边界条件做了充分处理6. 性能优化与常见问题在大规模使用会签任务时我们踩过不少性能坑。这里分享几个关键优化点审批人集合优化控制审批人数量建议不超过20人使用缓存减少组织架构查询分批处理超大规模审批数据库优化-- 添加必要的索引 CREATE INDEX idx_act_ru_exec_mi ON act_ru_execution(proc_inst_id_, is_mi_root_); -- 定期清理历史数据常见问题解决方案任务卡住检查nrOfInstances和nrOfActiveInstances是否一致网关不触发确认条件表达式变量已正确设置监听器不执行检查event类型是否匹配start/end在最近一次性能调优中我们通过以下改动将处理速度提升了5倍将同步监听器改为异步优化变量存储大对象存MongoDB添加流程实例缓存7. 扩展应用场景会签模式不仅适用于审批流程还可以灵活应用于这些场景技术评审代码审查多人并行评审测试用例多人验证架构设计多方确认业务场景采购多方比价合同多方签署项目多方验收最近我们实现了一个创新的应用在DevOps流程中使用会签任务控制发布流程。当多个系统需要同时发布时各系统负责人并行确认准备状态全部就绪后自动触发发布。关键实现点在于动态设置审批人为各系统负责人并设置超时自动通过机制。// 动态设置超时自动通过 timerService.schedule(new Runnable(){ public void run(){ if(!taskService.createTaskQuery() .processInstanceId(procId) .unfinished().list().isEmpty()){ // 自动完成所有任务 tasks.forEach(t - taskService.complete(t.getId(), autoApproveVars)); } } }, 2, TimeUnit.HOURS);