Java案例如何实现审批流程?

wen python案例 3

Java案例如何实现审批流程?从零搭建企业级工作流引擎

目录导读

  1. 为什么需要审批流程系统?
  2. Java实现审批流程的三大核心机制
  3. 实战案例:基于Spring Boot的请假审批系统
  4. 数据库设计:状态机与审批链
  5. 核心代码实现逐行解析
  6. 常见问题FAQ

为什么需要审批流程系统?

在企业级应用中,审批流程是业务流转的“高速公路”,无论是OA系统的请假审批、财务系统的报销审批,还是ERP系统的采购审批,都需要一套灵活、可扩展的Java架构来承载。

Java案例如何实现审批流程?

问:为什么不用现成的工作流引擎(如Activiti)?
答: 轻量级场景下,自研审批流能避免Activiti的数据库表结构复杂、学习成本高、部署臃肿等问题,通过”一张审批表+状态机模式“,可快速实现95%的审批场景。


Java实现审批流程的三大核心机制

机制 核心要素 类比
状态机 待审批→审批中→已通过/已驳回 电梯的向上/向下/停止
审批链 申请人→直属上级→部门经理→HR 多米诺骨牌逐个倒下
回滚补偿 驳回→退回上一级/重新提交 游戏里的存档读档

关键设计原则

  • 每一个审批节点只关心当前操作
  • 通过数据库字段记录状态而非动态流程引擎
  • 使用策略模式处理不同审批类型的业务逻辑

实战案例:基于Spring Boot的请假审批系统

业务需求

员工提交请假申请 → 直属上级审批 → 部门经理审批 → 人事备案(通过后)
支持:退回重新提交、会签(多人同时审批)、条件分支(请假>3天需总监审批)

技术栈选型

  • Spring Boot 2.7.x
  • MyBatis-Plus
  • MySQL 8.0
  • Redis(用于并发控制)

数据库设计:状态机与审批链

核心表结构:approval_request

CREATE TABLE `approval_request` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) DEFAULT 'LEAVE' COMMENT '审批类型:LEAVE, EXPENSE, PURCHASE',
  `applicant_id` bigint(20) DEFAULT NULL COMMENT '申请人ID',
  `current_node` tinyint(4) DEFAULT '0' COMMENT '当前节点编号:0-待提交,1-直属上级,2-部门经理,3-人事备案',
  `status` tinyint(4) DEFAULT '0' COMMENT '状态:0-进行中,1-通过,2-驳回,3-撤回', varchar(200) DEFAULT NULL COMMENT '标题',
  `content` text,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

审批记录表:approval_log

CREATE TABLE `approval_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `request_id` bigint(20) NOT NULL,
  `operator_id` bigint(20) DEFAULT NULL,
  `node` tinyint(4) DEFAULT NULL COMMENT '处理节点',
  `action` tinyint(4) DEFAULT NULL COMMENT '操作:1-通过,2-驳回',
  `comment` text,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设计亮点

  • current_node 字段替代了复杂的流程引擎表
  • 通过status字段组合current_node实现完整状态机
  • 利用approval_log实现全链路审计

核心代码实现逐行解析

Step 1:定义审批节点枚举

public enum ApprovalNode {
    DRAFT(0, "草稿"),
    DIRECT_LEADER(1, "直属上级"),
    DEPT_MANAGER(2, "部门经理"),
    HR(3, "人事备案"),
    FINISHED(4, "已完成");
    private int code;
    private String desc;
    // getter...
}

Step 2:实现审批服务核心逻辑

@Service
public class ApprovalService {
    public void approve(Long requestId, Long operatorId, boolean approved, String comment) {
        // 1. 通过Redis分布式锁防止并发
        String lockKey = "approval:lock:" + requestId;
        boolean locked = redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS);
        try {
            // 2. 查询当前审批单
            ApprovalRequest request = approvalMapper.selectById(requestId);
            // 3. 权限校验:必须是当前节点的审批人
            if (!checkApprover(request.getCurrentNode(), operatorId)) {
                throw new BusinessException("您不是当前环节的审批人");
            }
            // 4. 处理审批结果
            if (approved) {
                // 通过:推进到下一节点
                request.setCurrentNode(request.getCurrentNode() + 1);
                // 如果已经是最后一个节点,标记为通过
                if (request.getCurrentNode() == ApprovalNode.FINISHED.getCode()) {
                    request.setStatus(1); // 审批完成
                }
            } else {
                // 驳回:退回上一级(或直接退回申请人)
                request.setCurrentNode(ApprovalNode.DIRECT_LEADER.getCode());
                request.setStatus(2); // 驳回状态
            }
            // 5. 记录审批日志
            ApprovalLog log = new ApprovalLog();
            log.setRequestId(requestId);
            log.setOperatorId(operatorId);
            log.setAction(approved ? 1 : 2);
            log.setComment(comment);
            approvalLogMapper.insert(log);
            // 6. 更新主表
            request.setUpdateTime(new Date());
            approvalMapper.updateById(request);
        } finally {
            redisLock.unlock(lockKey);
        }
    }
}

Step 3:动态审批人策略实现

@Component
public class ApproverStrategy {
    // 根据不同节点动态获取审批人ID
    public Long getApproverByNode(ApprovalRequest request, int node) {
        // 实际项目可以从部门表、组织架构表中动态查询
        switch (node) {
            case 1: // 直属上级
                return userService.getDirectLeader(request.getApplicantId());
            case 2: // 部门经理
                return userService.getDeptManager(request.getApplicantId());
            case 3: // 人事
                return userService.getHRUserId();
            default:
                return null;
        }
    }
    // 条件分支:请假>3天需要总监审批
    public boolean needAdditionalNode(ApprovalRequest request) {
        if ("LEAVE".equals(request.getType())) {
            JSONObject content = JSONObject.parseObject(request.getContent());
            int days = content.getIntValue("days");
            return days > 3;
        }
        return false;
    }
}

关键设计要点总结

  1. 单一职责原则:每个审批节点只更新current_node,不关注全局流程
  2. 幂等性:通过分布式锁防止同一条记录被并发审批
  3. 可回溯:所有操作落日志,支持撤销/追踪
  4. 扩展性:通过策略模式支持不同审批类型的差异化逻辑

常见问题FAQ

Q1:多人会签(如需3人同时审批)怎么实现?
A:可以在approval_request增加sign_count字段,每次通过加1,当达到阈值时自动推进节点,拒绝则立即驳回。

Q2:如何实现催办/超时自动审批?
A:使用Spring Task定时扫描超过24小时未处理的审批单,发送钉钉/邮件通知。

@Scheduled(cron = "0 0 8 * * ?") // 每天上午8点执行
public void handleTimeoutApprovals() {
    List<ApprovalRequest> timeoutList = approvalMapper.selectTimeoutList();
    timeoutList.forEach(request -> {
        // 发送提醒通知
        notificationService.sendUrge(request);
    });
}

Q3:审批过程中业务数据变动怎么处理?
A:建议将业务数据(如请假天数)与审批状态解耦,审批通过后,通过事件监听机制更新业务表:

@EventListener
public void handleApprovalPassed(ApprovalPassedEvent event) {
    if ("LEAVE".equals(event.getType())) {
        // 更新考勤记录
        attendanceService.updateLeave(event);
    }
}

注意:本文所有代码示例已过滤敏感域名信息,可直接复制用于学习。

通过本文的案例,你应该已经掌握了用Java实现审批流程的最小可行方案,实际生产中,可根据业务复杂度选择是否引入Activiti/Flowable等专业引擎,但对于80%的企业场景,这种”一张表+状态机“的轻量方案已经足够高效且易于维护。

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