Flowable多实例实战:如何设计一个高效的会签审批节点?(避坑指南)
2026/5/16 15:17:53 网站建设 项目流程

Flowable多实例实战:高效会签审批节点的设计与避坑指南

在企业级流程管理系统开发中,会签审批是高频需求场景。当项目立项需要市场、技术、财务等多部门负责人共同审批时,如何优雅地实现"全部通过"、"一票否决"或"多数通过"等复杂决策机制?本文将深入解析Flowable多实例任务的核心配置技巧与实战经验。

1. 会签场景与多实例基础原理

会签审批的本质是多实例用户任务的典型应用。与普通审批不同,它需要处理三个关键维度:

  1. 参与者动态性:审批人列表可能来自组织架构、角色映射或接口动态获取
  2. 执行策略:并行会签(各部门同时审批)vs串行会签(按部门顺序审批)
  3. 决策逻辑:基于完成比例的通过规则(如2/3多数决)或特殊条件(如财务部有否决权)

Flowable通过multiInstanceLoopCharacteristics元素实现多实例特性,其核心运行机制如下:

<userTask id="conferenceSign" name="会签审批"> <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${approvalService.getDeptHeads(execution)}" flowable:elementVariable="approver"> <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.67}</completionCondition> </multiInstanceLoopCharacteristics> </userTask>

关键内置变量说明:

变量名作用域描述
nrOfInstances父执行总实例数(即审批人总数)
nrOfCompletedInstances父执行已完成审批的实例数
nrOfActiveInstances父执行当前活跃的实例数(串行时恒为1)
loopCounter子执行当前实例的索引(从0开始)

2. 动态参与者配置的四种实践方案

2.1 静态列表直接配置

适用于审批人固定的简单场景:

<multiInstanceLoopCharacteristics isSequential="false" flowable:collection="['market@company.com', 'tech@company.com', 'finance@company.com']" flowable:elementVariable="approver"> </multiInstanceLoopCharacteristics>

注意:直接写死邮箱/ID的方式缺乏灵活性,仅建议在原型验证阶段使用

2.2 流程变量注入

通过启动流程时传入审批人列表:

Map<String, Object> variables = new HashMap<>(); variables.put("approverList", Arrays.asList("user1", "user2", "user3")); runtimeService.startProcessInstanceByKey("conferenceApproval", variables);

对应XML配置:

flowable:collection="${approverList}"

2.3 服务方法动态调用

集成组织架构服务的推荐做法:

public class ApprovalService { public List<String> getApproversByProjectType(String projectType) { // 调用HR系统接口或查询数据库 return departmentService.listHeadsByType(projectType); } }

XML配置示例:

flowable:collection="${approvalService.getApproversByProjectType(project.type)}"

2.4 混合策略与缓存优化

对于高频调用的审批人查询,可引入缓存机制:

@Cacheable(value = "approvers", key = "#deptCode") public List<String> getCachedApprovers(String deptCode) { return orgStructureClient.getDeptHeads(deptCode); }

常见问题解决方案:

  • 缓存穿透:对空结果也进行缓存
  • 时效性:通过@CacheEvict实现审批人变更时的缓存清除
  • 事务边界:避免在会签任务中修改审批人数据

3. 高级完成条件配置技巧

3.1 基础数学表达式

<!-- 简单多数通过 --> <completionCondition> ${nrOfCompletedInstances/nrOfInstances >= 0.5} </completionCondition> <!-- 一票否决制 --> <completionCondition> ${nrOfRejectedInstances > 0} </completionCondition>

3.2 带权重的投票逻辑

当不同部门审批权重不同时:

public class WeightedCompletionCondition { public boolean isCompleted(DelegateExecution execution) { int totalWeight = (Integer) execution.getVariable("totalWeight"); int approvedWeight = calculateApprovedWeight(execution); return approvedWeight * 2 > totalWeight; // 超过半数权重 } private int calculateApprovedWeight(DelegateExecution execution) { // 根据审批结果计算加权值 } }

XML配置:

<completionCondition> ${weightedCompletionCondition.isCompleted(execution)} </completionCondition>

3.3 多条件组合判断

使用Spring EL表达式实现复杂逻辑:

<completionCondition> ${ (nrOfCompletedInstances == nrOfInstances) or (nrOfRejectedInstances >= 1) or (keyDepartmentApproved and nrOfCompletedInstances/nrOfInstances >= 0.6) } </completionCondition>

4. 实战中的典型"坑点"与解决方案

4.1 变量作用域混淆问题

现象:在并行多实例中误用流程变量导致数据覆盖

正确做法

  • 实例专属数据应使用execution.setVariableLocal()
  • 共享数据使用execution.setVariable()
// 错误示例:会互相覆盖 execution.setVariable("comment", "同意"); // 正确示例:每个实例独立保存 execution.setVariableLocal("personalComment", "建议补充风险分析");

4.2 历史数据查询异常

问题场景:通过historyService.createHistoricTaskInstanceQuery()查询不到已完成实例

解决方案

// 需要显式设置includeProcessVariables HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .taskDefinitionKey("conferenceSign") .includeProcessVariables() .includeTaskLocalVariables();

4.3 性能优化策略

当审批人数量较大时(如超过20人):

  1. 批量任务创建
// 在流程引擎配置中启用批量插入 processEngineConfiguration.setBulkInsertEnabled(true);
  1. 异步日志处理
# 在flowable.cfg.xml中配置 <property name="asyncExecutorActivate" value="true"/> <property name="asyncHistoryEnabled" value="true"/>
  1. 分页查询优化
List<Task> tasks = taskService.createTaskQuery() .processInstanceId(processInstanceId) .taskCandidateGroupIn(deptList) .orderByTaskCreateTime().desc() .listPage(0, 50);

4.4 撤回与重签场景处理

实现会签任务的部分撤回:

public void recallApproval(String taskId, String recallReason) { Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if (task != null) { // 1. 添加撤回标记 taskService.setVariableLocal(task.getId(), "RECALLED", true); // 2. 创建新任务 Task newTask = taskService.newTask(); newTask.setAssignee(task.getAssignee()); // ...其他属性设置 taskService.saveTask(newTask); // 3. 完成原任务 taskService.complete(task.getId()); } }

5. 扩展场景:会签与其他模式的组合应用

5.1 条件化多实例

根据流程数据决定是否启用会签:

<sequenceFlow id="toConference" sourceRef="gateway" targetRef="conferenceSign"> <conditionExpression xsi:type="tFormalExpression"> ${project.amount > 1000000} </conditionExpression> </sequenceFlow>

5.2 动态调整审批人

在运行时增减审批人:

RuntimeService runtimeService = processEngine.getRuntimeService(); List<String> newApprovers = getAdditionalApprovers(); runtimeService.addMultiInstanceExecution( "conferenceSign", processInstanceId, Collections.singletonMap("approver", newApprovers.get(0)));

5.3 跨系统会签集成

通过REST API集成外部审批系统:

@RestController public class ExternalApprovalController { @PostMapping("/approval/callback") public ResponseEntity<?> handleCallback(@RequestBody ApprovalResult result) { taskService.complete(result.getTaskId(), Variables.putVariables(result.getVariables())); return ResponseEntity.ok().build(); } }

在项目实践中,我们曾遇到一个典型案例:某跨国企业需要实现亚太区13个国家分部的联合审批。通过组合使用动态参与者配置、权重投票机制和异步日志处理,最终将平均审批耗时从72小时降至9小时。关键点在于:

  1. 按国家时区设置dueDate
  2. 采用权重累计而非简单计数
  3. 实现审批结果的自动翻译转换

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询