Activiti7会签实战:从“全员通过”到“一票否决”,四种表决规则配置详解
在企业级流程审批场景中,会签(Countersign)是实现集体决策的核心机制。当采购金额超过阈值、重大项目立项或关键人事变动时,往往需要多个责任方共同参与决策。Activiti7作为业界领先的工作流引擎,通过多实例任务(Multi-Instance Task)为会签场景提供了灵活的技术支撑。本文将深入解析四种典型表决规则的实现方案,涵盖从XML配置到流程变量的完整实践路径。
1. 会签基础架构与核心要素
多实例任务通过multiInstanceLoopCharacteristics元素实现会签能力,其核心由三个部分组成:
- 参与者集合:通过
activiti:collection指定审批人列表,支持静态定义或动态变量注入 - 实例控制变量:
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${approverList}" activiti:elementVariable="approver"> - 完成条件:
completionCondition表达式决定会签何时终止
系统自动维护的流程变量包括:
nrOfInstances:总实例数(等于审批人列表长度)nrOfCompletedInstances:已完成审批数nrOfActiveInstances:当前活跃实例数(并行模式下等于待处理数)
提示:当
isSequential="true"时,会签转为串行审批模式,此时nrOfActiveInstances恒为1
2. 全员通过:民主决策模式
最严格的审批规则要求所有参与者必须全部同意,常见于高风险操作如资金划转、核心系统变更等场景。
BPMN配置示例:
<userTask id="groupApproval" name="安全委员会评审"> <multiInstanceLoopCharacteristics activiti:collection="${securityCommittee}" activiti:elementVariable="member"> <completionCondition> ${nrOfCompletedInstances == nrOfInstances} </completionCondition> </multiInstanceLoopCharacteristics> </userTask>实现要点:
- 启动流程时注入审批人列表:
Map<String, Object> vars = new HashMap<>(); vars.put("securityCommittee", Arrays.asList("cto","cfo","cio")); runtimeService.startProcessInstanceByKey("riskControl", vars); - 每个审批任务需显式传递同意标记:
taskService.complete(taskId, Variables.putValue("approved", true)); - 建议配合网关进行结果判断:
<sequenceFlow sourceRef="groupApproval" targetRef="exclusiveGateway"> <conditionExpression>${approvedCount == nrOfInstances}</conditionExpression> </sequenceFlow>
典型问题:
- 未处理弃权情况时,默认视为拒绝
- 并行模式下可能出现并发冲突,建议添加乐观锁控制
3. 多数决:平衡效率与风险
当参与人数较多时,可采用比例通过机制。例如5人评审团中3人同意即通过,适合项目立项等常规决策。
动态阈值配置方案:
<completionCondition> ${nrOfCompletedInstances >= Math.ceil(nrOfInstances * 0.6)} </completionCondition>增强型实现(带否决权):
<completionCondition> ${(approvedCount >= requiredApproves) || (rejectedCount > (nrOfInstances - requiredApproves))} </completionCondition>Java服务任务预处理:
public class QuorumCalculator implements JavaDelegate { @Override public void execute(DelegateExecution execution) { Integer memberCount = ((List)execution.getVariable("committee")).size(); execution.setVariable("requiredApproves", (int) Math.ceil(memberCount * 0.6)); } }注意:浮点数比较可能存在精度问题,建议使用整数运算
approvedCount*100 >= nrOfInstances*60
4. 一票否决:安全优先策略
关键领域如财务审计、合规审查等场景,往往采用保守策略。任一反对票立即终止流程。
极简实现方案:
<completionCondition> ${rejectedCount > 0 || nrOfCompletedInstances == nrOfInstances} </completionCondition>带自动终止的增强版:
taskService.complete(taskId, Variables.createVariables() .putValue("vote", "reject") .putValue("terminateAll", true)); // 触发事件终止其他实例终止逻辑事件监听器:
public class TerminateListener implements TaskListener { public void notify(DelegateTask task) { if (task.getVariable("terminateAll") != null) { runtimeService.createChangeActivityStateBuilder() .processInstanceId(task.getProcessInstanceId()) .cancelActivityInstance(task.getExecutionId()) .changeState(); } } }5. 一票通过:快速通道机制
适用于紧急情况处理或高层特权审批,首个同意的审批人即可推动流程前进。
基础配置模式:
<completionCondition> ${approvedCount > 0} </completionCondition>混合决策场景实践:
<completionCondition> <!-- 常规情况需过半同意,CEO审批可直接通过 --> ${(approvedCount >= quorum) || (approver.level == 'CEO' && approved)} </completionCondition>性能优化建议:
- 对高频使用的审批人添加缓存
- 复杂表达式建议前置到服务任务计算
- 并行实例数超过20时考虑分批次执行
6. 高级技巧与避坑指南
动态调整审批人列表:
runtimeService.setVariable( processInstanceId, "approverList", newApprovers // 支持运行时调整参与人 );历史数据追踪方案:
SELECT TASK_DEF_KEY_ as task_key, VAR_TYPE_ as var_type, TEXT_ as decision FROM ACT_HI_VARINST WHERE PROC_INST_ID_ = #{processInstanceId} AND NAME_ like 'vote_%'常见异常处理:
- 空参与者列表:添加前置校验
<sequenceFlow sourceRef="checkApprovers" targetRef="groupApproval"> <conditionExpression>${!empty(approverList)}</conditionExpression> </sequenceFlow> - 表达式性能问题:避免在循环条件中使用复杂计算
- 变量作用域混淆:明确指定变量作用域(流程/任务实例)
在实施医疗设备采购审批系统时,我们发现当会签节点存在动态分支时,必须显式设置activiti:skipExpression以避免实例丢失。例如当某位审批人无需参与特定品类审批时:
<userTask id="medicalReview" activiti:skipExpression="${productType != 'Medical' || approver.role != 'MD'}">