Java案例如何实现会签?

wen python案例 3

Java案例如何实现会签?深度解析工作流核心机制与代码实践

目录导读

  1. 会签的基础概念与业务场景
  2. Java实现会签的三种主流架构
  3. 实战案例:基于Activiti+SpringBoot的会签开发
  4. 高频问题与避坑指南(问答区)
  5. 性能优化与系统扩展建议

会签的基础概念与业务场景

什么是会签?
在OA系统(办公自动化)或BPM(业务流程管理)中,会签是指一项审批任务需要多个指定人员共同参与表决,且表决结果需满足特定规则(如全部同意、多数同意等)才能进入下一节点,这与“单人审批”形成鲜明对比:会签更适用于需要集体决策的场景,

Java案例如何实现会签?

  • 公司内部跨部门项目立项:需要技术总监、财务总监、总经理三方“一票否决”;
  • 合同审批:法务、财务、业务主管需分别签署意见;
  • 人事任命:董事会成员需投票达成过半数同意。

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:会签过程中,某参与者长时间不审批怎么办?

:三种方案:

  1. 超时自动通过:在 completionCondition 中添加时间判断,或使用Activiti定时器边界事件(在会签节点添加 Timer Event,设置超时后自动执行业务逻辑)。
  2. 催办机制:定时任务扫描会签节点的 create_time,对未完成任务发送通知。
  3. 管理员转办:提供“转办/委派”接口,由流程管理员手动将任务分配给其他用户。

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案例背后,都需要先理解“谁投票、如何算票、票数达成后做什么”这三个核心问题。

抱歉,评论功能暂时关闭!