Java案例如何实现会签?深度解析工作流核心机制与代码实践
目录导读
会签的基础概念与业务场景
什么是会签?
在OA系统(办公自动化)或BPM(业务流程管理)中,会签是指一项审批任务需要多个指定人员共同参与表决,且表决结果需满足特定规则(如全部同意、多数同意等)才能进入下一节点,这与“单人审批”形成鲜明对比:会签更适用于需要集体决策的场景,

- 公司内部跨部门项目立项:需要技术总监、财务总监、总经理三方“一票否决”;
- 合同审批:法务、财务、业务主管需分别签署意见;
- 人事任命:董事会成员需投票达成过半数同意。
Java实现会签的核心难点在于:如何动态管理多个参与者、如何汇总表决结果、如何保证并发一致性,目前主流方案包括工作流引擎(如Activiti、Flowable、Camunda)和自研状态机。
Java实现会签的三种主流架构
基于工作流引擎的“多实例任务”模式
工作流引擎通过多实例活动(Multi-instance Activity) 机制实现会签,开发者只需定义“参与者列表”和“完成条件”(如 nrOfCompletedInstances >= nrOfActivitiInstances),引擎会自动为每个参与者生成独立任务,并在参与者逐一审批时收集结果。
- 适用场景:标准化审批流程、可预测参与者数量(如固定3人会签)。
- 优点:开发量小,与流程引擎深度绑定,支持回退、驳回、超时自动处理。
- 缺点:不依赖数据库,但需学习Activiti/Flowable的BPMN2.0规范。
基于Redis/Tair的轻量级会签计数器
若团队不想引入重型工作流引擎,可利用Redis原子操作管理会签票数,核心思路:
-
每次创建会签时,在Redis中插入一条
{taskId, totalCount, approvedCount, rejectedCount}记录(如SET meeting:12345 3)。 -
每个参与者审批后,通过
INCR增加相应计数,并检查是否满足阈值(如approvedCount >= totalCount * 0.6)。 -
条件满足后,触发后续业务(如更新数据库任务状态为“通过”)。
-
适用场景:非流程校验、投票数量动态变化(如圆桌会议)、对实时性要求高的场景。
-
优点:轻量、高并发、不依赖数据库锁;缺点是需要处理Redis宕机后的数据恢复。
基于数据库悲观锁的轮询方案
适用于传统单体项目:在 task_approval 表中存储每个参与者的审批记录,并在汇总逻辑中加 SELECT ... FOR UPDATE 行锁。
- 缺点明显:并发能力差,容易出现死锁,且需要手动管理事务边界。强烈不推荐用于高并发场景。
实战案例:基于Activiti+SpringBoot的会签开发
场景假设
设计一个“项目立项审批”流程:需技术总监、财务总监、总经理3人依次会签(注意:这里是“表决数=人数”,但结果采用“全票通过”),每个角色批准后进入下一人,最终三人全部通过则结束;任一否决则流程终止。
定义BPMN流程模型
使用Activiti Modeler绘制BPMN图,核心节点配置:
<!-- 配置多实例(会签)节点 -->
<userTask id="sid-C5B2E1" name="会签节点" activiti:candidateUsers="${assignees}">
<!-- loopCharacteristics 定义会签行为 -->
<multiInstanceLoopCharacteristics isSequential="true"
activiti:collection="assigneeList" activiti:elementVariable="assignee">
<!-- 条件:全部实例完成时通过 -->
<completionCondition>
${nrOfCompletedInstances == nrOfInstances}
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
关键参数解释:
isSequential:true表示顺序会签(A→B→C),false表示并行会签(三人同时收到任务)。collection:参与者列表(如["tech_director", "finance_director", "ceo"])。completionCondition:决定何时结束循环,常用表达式:- 全部通过:
${nrOfCompletedInstances == nrOfInstances} - 多数通过:
${nrOfCompletedInstances / nrOfActivitiInstances >= 0.6}
- 全部通过:
Java代码实现动态参与者注入
@Service
public class ProjectApprovalService {
@Autowired
private RuntimeService runtimeService;
public void startApprovalProcess(String projectId, List<String> assignees) {
Map<String, Object> vars = new HashMap<>();
vars.put("assigneeList", assignees); // 动态传入会签人员
runtimeService.startProcessInstanceByKey("projectApproval", projectId, vars);
}
}
前端任务列表与决议接口
参与者调用 TaskService.complete() 时,通过引擎自动判断是否满足 completionCondition,若否决,则设置 taskResult="reject"(需要自定义流程变量)。
核心代码片段:
public void submitApproval(String taskId, String actionCode) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
Map<String, Object> vars = new HashMap<>();
vars.put("approved", "Y".equals(actionCode));
taskService.complete(taskId, vars);
}
高频问题与避坑指南(问答区)
Q1:会签过程中,某参与者长时间不审批怎么办?
答:三种方案:
- 超时自动通过:在
completionCondition中添加时间判断,或使用Activiti定时器边界事件(在会签节点添加Timer Event,设置超时后自动执行业务逻辑)。 - 催办机制:定时任务扫描会签节点的
create_time,对未完成任务发送通知。 - 管理员转办:提供“转办/委派”接口,由流程管理员手动将任务分配给其他用户。
Q2:一个会签节点中,某参与者同时属于多个角色,如何避免重复投票?
答:在设计参与者列表时,应通过业务规则去重。
- 若角色是“总经理”,但张三既在总经理组又在副总经理组,需在启动流程前调用
identityService查询用户的唯一组,并在assigneeList中只加入一条记录。 - 也可在参与者列表中加入
taskId+userId的联合唯一索引,当用户点击“同意”时,检查是否已投过票(但工作流引擎通常会自动防止重复完成任务)。
Q3:会签结果如何通知后续节点或系统服务?
答:利用Activiti的服务任务或Java委托:
- 在会签之后添加一个
ServiceTask,配置activiti:class="com.example.NotifyServiceDelegate"。 - 在该委托类中,通过
delegateExecution.getVariable("nrOfCompletedInstances")获取表决数据,然后发送消息(如MQ、Webhook)或更新业务表状态。
public class NotifyServiceDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
// 获取会签结果
int total = (int) delegateExecution.getVariable("nrOfInstances");
int completed = (int) delegateExecution.getVariable("nrOfCompletedInstances");
boolean allApproved = completed == total;
// 发送通知...
}
}
Q4:如何实现会签“同意”和“否决”统计?
答:可以在每个参与者完成任务时,自定义一个 本地变量(如 approved_${assignee}),在 completionCondition 中通过 getVariable("approved_"+assignee) 计数,更优解是:
- 在
activiti:completeTaskListener中,将ActionCode写入执行实例变量collection["approvalStatus"]。 - 然后在
completionCondition中使用execution.getVariable("collection")遍历判断。
性能优化与系统扩展建议
避免线性会签的性能瓶颈
默认的顺序会签会导致后续参与者需等待前一人完成,若业务允许,建议使用并行会签(isSequential="false"),让所有参与者同时收到任务,极大缩短整体流程时间,同时结合Redis计数器模式,实现无锁高并发。
参与者数量过大时的处理
若会签人数超过50人(如全员投票),应避免将所有人直接赋给 assigneeList,而是:
- 使用 角色(Group) 代替用户:将
candidateGroups设为“全体成员”,每个用户看到任务后自行认领(claim)。 - 或者采用外部数据源动态加载:在
completionCondition中实时查询数据库判断是否满足比例,而不将所有参与者提前放置内存。
结合分布式锁保证最终一致性
对于跨系统会签(如微服务架构中,审批结果需同步到订单系统、财务系统),建议在服务层引入分布式事务(如Seata) 或最终一致性方案:
- 在会签结束时通过消息队列(如RocketMQ)发送“审批通过事件”。
- 下游服务消费该事件,并处理自身业务逻辑,同时利用消息重试确保最终成功。
选型推荐
| 业务类型 | 推荐方案 | 理由 |
|---|---|---|
| 复杂流程(多条分支、回退) | Activiti/Flowable多实例 | 天然支持BPMN,减少重复开发 |
| 轻量级投票(参与者<20) | Redis自定义计数器 | 高并发、低延迟 |
| 传统单体应用 | 数据库轮询+悲观锁 | 简单,但慎用于线上高压力 |
最后附上一个设计原则:会签的本质是“协同决策”,技术选型需紧扣业务复杂度,如果团队对工作流引擎不熟悉,优先选择Redis计数器方案,因为它更可控——毕竟,任何复杂的Java案例背后,都需要先理解“谁投票、如何算票、票数达成后做什么”这三个核心问题。