别再手动抄送了!用Activiti7多实例搞定会签审批,附赠SpringBoot集成避坑指南
Activiti7会签审批实战:告别低效抄送,用多实例重构企业审批流
当审批单在OA系统中流转时,你是否还在为以下场景头疼?财务报销需要部门全员确认,却要手动勾选十几个抄送人;项目立项必须获得三位总监联签,但系统只能逐个串行审批;紧急请假需要任意一位经理快速审批,却因流程设计被迫等待固定审批人。这些传统审批流程中的"人工接力赛",正在吞噬企业运营效率。
1. 会签与或签:重新定义审批协作模式
会签(Approval Meeting)和或签(Or Approval)是工作流引擎中两种典型的多人协作审批模式。前者要求指定范围内一定数量的审批人达成共识,后者则只需任意一位审批人处理即可推进流程。这两种模式在Activiti7中均通过**多实例活动(Multi-Instance Activity)**实现,但配置策略和适用场景截然不同。
表:会签与或签的核心差异对比
| 特征 | 会签模式 | 或签模式 |
|---|---|---|
| 完成条件 | 需满足预设人数/比例 | 任意一人处理即完成 |
| 典型应用 | 部门预算审批、项目立项 | 紧急请假、故障处理 |
| 并行性 | 可配置并行/串行 | 通常并行执行 |
| 结果处理 | 支持投票统计 | 首个响应者决定流向 |
| 系统负载 | 较高(需跟踪多实例状态) | 较低(单实例完成即结束) |
在技术实现层面,这两种模式共享相同的底层机制——都通过BPMN 2.0规范的 multiInstanceLoopCharacteristics 元素实现。关键区别仅体现在 completionCondition 属性的表达式配置上:
<!-- 会签示例:超过半数同意即通过 -->
<userTask id="groupApproval" name="部门会签">
<multiInstanceLoopCharacteristics
isSequential="false"
collection="approvers"
elementVariable="approver">
<completionCondition>${nrOfCompletedInstances/nrOfInstances > 0.5}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<!-- 或签示例:任意一人处理即完成 -->
<userTask id="urgentApproval" name="紧急审批">
<multiInstanceLoopCharacteristics
isSequential="false"
collection="managers"
elementVariable="manager">
<completionCondition>${nrOfCompletedInstances >= 1}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
2. 会签实现四步法:从设计到部署
2.1 BPMN可视化配置
在Activiti Modeler中创建会签节点时,需要关注以下核心属性配置:
- 多实例类型 :选择"Parallel"实现并行会签(审批人同时收到任务),选择"Sequential"实现串行会签(按列表顺序逐个审批)
- 参与者集合 :通过
collection属性指定审批人列表变量名(如${approvers}) - 元素变量 :设置临时变量名存储当前审批人(通常与任务Assignee占位符一致)
- 完成条件 :使用预定义变量编写UEL表达式:
${nrOfCompletedInstances == nrOfInstances}(全员通过)${nrOfCompletedInstances >= 2}(至少两人同意)${nrOfCompletedInstances/nrOfInstances > 0.6}(超过60%同意)
图:会签节点在流程设计器中的典型配置界面 (注:此处应插入配置截图,展示Loop cardinality、Completion condition等字段的填写示例)
2.2 动态参与者配置
审批人列表的注入时机和方式直接影响会签的灵活性。推荐三种实践方案:
方案一:启动时静态注入
// 流程启动时传入审批人列表
Map<String, Object> variables = new HashMap<>();
variables.put("approvers", Arrays.asList("user1", "user2", "user3"));
runtimeService.startProcessInstanceByKey("meetingApproval", variables);
方案二:运行时动态查询
// 通过监听器动态设置参与者
public class DynamicApproverListener implements TaskListener {
@Override
public void notify(DelegateTask task) {
String deptId = (String) task.getVariable("applyDept");
List<String> approvers = departmentService.findApprovers(deptId);
task.setVariable("approvers", approvers);
}
}
方案三:混合模式(固定+动态)
// 合并固定审批人和动态查询结果
List<String> fixedApprovers = Arrays.asList("CFO", "CTO");
List<String> dynamicApprovers = projectService.getProjectStakeholders(projectId);
List<String> allApprovers = Stream.concat(fixedApprovers.stream(),
dynamicApprovers.stream()).collect(Collectors.toList());
variables.put("approvers", allApprovers);
2.3 完成条件进阶策略
除基础的数量条件外,实际业务往往需要更复杂的完成逻辑:
权重投票场景 :
<!-- 不同审批人拥有不同票权 -->
<completionCondition>
${(voteResults['director'] ? 3 : 0) +
(voteResults['manager'] ? 2 : 0) +
(voteResults['staff'] ? 1 : 0) >= 5}
</completionCondition>
自定义条件类 :
public class CustomCompletionCondition implements JavaDelegate {
public void execute(DelegateExecution execution) {
boolean isRejected = (boolean) execution.getVariable("globalReject");
int agreeCount = ((List)execution.getVariable("voteResults"))
.stream().filter(r -> "agree".equals(r)).count();
execution.setVariable("meetingPassed", !isRejected && agreeCount > 1);
}
}
2.4 会签结果聚合
多实例任务完成后,通常需要汇总各审批意见。通过 execution.getVariableLocal() 可获取实例级变量:
// 在流程后续节点中收集会签结果
List<Map<String, Object>> allComments = new ArrayList<>();
for (String approver : approvers) {
Map<String, Object> comment = taskService.getVariableLocal(
taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskAssignee(approver)
.singleResult().getId(), "approvalComment");
allComments.add(comment);
}
3. SpringBoot集成实战与避坑指南
3.1 自动配置陷阱
当SpringBoot遇到Activiti7时,这些配置项最容易出问题:
# 必须关闭自动部署校验(否则修改BPMN后需重启)
spring.activiti.check-process-definitions=false
# 建议使用新事务管理器(避免会签任务卡死)
spring.activiti.async-executor-activate=true
spring.activiti.async-executor-thread-pool-size=10
3.2 变量传递黑洞
会签任务中变量作用域的特殊性常导致意外:
// 错误示范:直接设置全局变量(其他审批人看不到)
taskService.setVariable(taskId, "comment", "同意");
// 正确做法:使用setVariableLocal设置实例级变量
taskService.setVariableLocal(taskId, "privateComment", "建议补充材料");
表:Activiti变量作用域对照
| 方法 | 作用域 | 多实例可见性 |
|---|---|---|
| runtimeService.setVariable | 流程实例 | 所有节点可见 |
| taskService.setVariable | 任务 | 仅当前任务可见 |
| taskService.setVariableLocal | 任务实例 | 多实例中各任务独立 |
3.3 事务一致性方案
会签任务并行执行时,需要考虑事务隔离问题。推荐采用Saga模式:
@Transactional
public void completeApproval(String taskId, ApprovalVO vo) {
// 1. 记录审批操作(本地事务保证)
approvalRecordRepository.save(convertToEntity(vo));
// 2. 发送领域事件(异步补偿)
eventPublisher.publishEvent(new ApprovalEvent(
taskId, vo.getComment(), vo.isAgree()));
// 3. 完成任务(最后执行)
taskService.complete(taskId,
Collections.singletonMap("approvalResult", vo.isAgree()));
}
3.4 性能优化策略
大规模会签场景下,这些优化手段能显著提升性能:
- 批量任务查询 :避免N+1查询问题
// 低效做法
for (String approver : approvers) {
Task task = taskService.createTaskQuery()
.taskAssignee(approver).singleResult();
}
// 高效做法
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskAssigneeIds(approvers)
.list();
- 启用历史级别优化 :
# 只记录必要的历史数据
spring.activiti.history-level=audit
- 异步日志处理 :
@Bean
public AsyncLogListener asyncLogListener() {
return new AsyncLogListener(executor);
}
4. 企业级会签场景深度解析
4.1 层级审批:矩阵式会签
对于需要跨部门联动的审批场景,可采用分层会签设计:
<process id="matrixApproval">
<startEvent id="start"/>
<!-- 第一层:部门内会签 -->
<userTask id="deptApproval" name="部门审批">
<multiInstanceLoopCharacteristics
collection="${deptApprovers}"
elementVariable="approver">
<completionCondition>${nrOfCompletedInstances >= 2}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<!-- 第二层:跨部门会签 -->
<userTask id="crossDeptApproval" name="跨部门会签">
<multiInstanceLoopCharacteristics
collection="${crossDeptApprovers}"
elementVariable="approver">
<completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<endEvent id="end"/>
</process>
4.2 动态路由:条件化或签
结合网关实现智能路由的或签流程:
<sequenceFlow id="toHR" sourceRef="approvalGateway" targetRef="hrApproval">
<conditionExpression xsi:type="tFormalExpression">
${firstApprover.department == 'HR'}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="toFinance" sourceRef="approvalGateway" targetRef="financeApproval">
<conditionExpression xsi:type="tFormalExpression">
${firstApprover.department == 'Finance'}
</conditionExpression>
</sequenceFlow>
4.3 混合模式:会签+或签组合
复杂审批链的典型结构示例:
开始 → [预算会签](3人至少2人同意) → [技术或签](任意架构师) →
[CFO审批](单人) → [终审会签](全部董事) → 结束
对应的BPMN片段:
<userTask id="finalApproval" name="董事会终审">
<multiInstanceLoopCharacteristics
isSequential="true"
collection="${boardMembers}"
elementVariable="member">
<completionCondition>
<![CDATA[
${nrOfCompletedInstances == nrOfInstances ||
nrOfRejectedInstances >= 1}
]]>
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
4.4 异常处理:会签超时与干预
对于可能陷入僵局的会签任务,需要设计熔断机制:
@Scheduled(fixedDelay = 3600000)
public void monitorStuckApprovals() {
List<ProcessInstance> instances = runtimeService.createProcessInstanceQuery()
.activityId("groupApproval")
.startedBefore(new Date(System.currentTimeMillis() - 48 * 3600000))
.list();
instances.forEach(instance -> {
runtimeService.setVariable(instance.getId(), "forceComplete", true);
runtimeService.trigger(instance.getId());
});
}
在BPMN中配置相应的边界事件:
<boundaryEvent id="timeoutEvent" attachedToRef="groupApproval">
<timerEventDefinition>
<timeDuration>PT48H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
更多推荐


所有评论(0)