报废审批流规则引擎设计——责任链模式完整实现

目录

[① 导读卡片](#① 导读卡片)

[② 背景与目标](#② 背景与目标)

为什么学?

学完能怎样?

[③ 核心概念与原理](#③ 核心概念与原理)

[3.1 责任链模式](#3.1 责任链模式)

[3.2 和策略模式的区别](#3.2 和策略模式的区别)

[④ 完整代码实现](#④ 完整代码实现)

[4.1 实体对象](#4.1 实体对象)

[4.2 抽象 Handler + 链式编排](#4.2 抽象 Handler + 链式编排)

[4.3 具体 Handler 实现](#4.3 具体 Handler 实现)

[4.4 Spring 自动编排链](#4.4 Spring 自动编排链)

[4.5 Service 层------提交报废审批](#4.5 Service 层——提交报废审批)

[4.6 Controller 层](#4.6 Controller 层)

[⑤ 执行流程全景](#⑤ 执行流程全景)

[⑥ 扩展场景](#⑥ 扩展场景)

[6.1 不同资产类别走不同审批链](#6.1 不同资产类别走不同审批链)

[6.2 并行审批(会签)](#6.2 并行审批(会签))

[6.3 动态审批金额(配置化)](#6.3 动态审批金额(配置化))

[⑦ 常见坑与最佳实践](#⑦ 常见坑与最佳实践)

[7.1 易错点清单](#7.1 易错点清单)

[7.2 自测问题](#7.2 自测问题)

[⑧ 总结](#⑧ 总结)

核心要点速览

自检清单

目录结构


① 导读卡片

🧩 一句话读懂 :用责任链模式替代 if-else 实现报废审批规则,每个审批节点是独立 Handler,通过 @Order 和 setNext 编排成链,金额范围自动路由到对应审批人 🎯 适合人群 :Java 后端开发者、涉及审批流 / 工作流设计的工程师、面试进阶选手 📊 难度等级 :★★★★☆(中等偏上,含完整代码) ⏱ 阅读时长 :约 10 分钟 💡 前置知识:Spring Boot、策略模式 / 职责链模式基本概念


② 背景与目标

为什么学?

报废审批需要根据金额范围路由到不同角色------≤5000 组长批,≤50000 经理批,以此类推。

用 if-else 写当然能跑:

java 复制代码
if (amount <= 5000) { // 组长审批 }
else if (amount <= 50000) { // 经理审批 }
else if (amount <= 200000) { // 总监审批 }
else { // 总经理审批 }

但问题很快会出现:

  • 金额区间调整了 → 改代码

  • 新增审批角色 → 改代码

  • 不同资产类别有不同的审批链 → 条件爆炸

  • 以后加「会签」「转审」「加签」等复杂流程 → 根本没法改

责任链模式正好解决这个问题:每个审批节点是独立对象,链式组合,职责单一,扩展时新增一个 Handler 就行。

学完能怎样?

  • 理解责任链模式在审批流中的完整用法

  • 能写出包含实体、Handler、链式编排、Spring 管理的中等复杂度方案

  • 知道怎么处理异常场景(越级、无需审批、并行审批)

  • 能扩展到会签、加签、条件分支等复杂流程


③ 核心概念与原理

3.1 责任链模式

复制代码
请求通过一条 Handler 链,每个 Handler 决定自己能不能处理:
  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ 金额 ≤5k  │ →  │  ≤50k   │ →  │ ≤200k   │ →  │  >200k   │
  │ 组长审批  │    │ 经理审批  │    │ 总监审批  │    │ 总经理审批 │
  └──────────┘    └──────────┘    └──────────┘    └──────────┘
      │                 │               │              │
      └──── 能处理就停,不能就往下传

关键特征:

  • 每个节点独立判断:能不能处理自己说了算

  • 链式传递:处理不了就交下一个

  • 职责单一:组长 Handler 只关心 5000 以内的逻辑

3.2 和策略模式的区别

维度 策略模式 责任链模式
路由方式 Map 匹配 key 直接定位 链上逐节点命中
节点关系 独立,互不知晓 知道下一个节点
适用场景 精确匹配(如 1:1 的支付方式路由) 区间/范围匹配(审批流)
扩展性 加策略 + 改工厂 加 Handler + 插入链中
链式编排 不支持 天然支持

④ 完整代码实现

4.1 实体对象

java 复制代码
/**
 * 报废审批请求
 */
@Data
public class ScrapApprovalRequest {
    
    /** 报废单 ID */
    private Long scrapId;
    
    /** 报废金额 */
    private BigDecimal amount;
    
    /** 资产类别(不同类别可能走不同审批链) */
    private String assetCategory;
    
    /** 提交人 */
    private String submitterId;
    
    /** 当前处理人角色(推进时设置) */
    private String currentRole;
    
    /** 审批意见 */
    private String remark;
    
    /** 审批结果 */
    private ApprovalResult result;
}
java 复制代码
/**
 * 报废单实体
 */
@Data
@TableName("scrap_order")
public class ScrapOrder {
    @TableId
    private Long id;
    
    /** 报废单号 */
    private String orderNo;
    
    /** 报废金额 */
    private BigDecimal amount;
    
    /** 资产编码 */
    private String assetCode;
    
    /** 资产类别 */
    private String assetCategory;
    
    /** 状态: 0-待提交 1-审批中 2-已通过 3-已驳回 */
    private Integer status;
    
    /** 当前审批角色 */
    private String currentApproverRole;
    
    /** 当前审批人 ID */
    private String currentApproverId;
    
    /** 创建人 */
    private String createBy;
    
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
java 复制代码
/**
 * 审批记录
 */
@Data
@TableName("approval_record")
public class ApprovalRecord {
    @TableId
    private Long id;
    
    /** 关联报废单 ID */
    private Long scrapId;
    
    /** 审批角色 */
    private String approverRole;
    
    /** 审批人 */
    private String approverId;
    
    /** 审批动作: approve / reject */
    private String action;
    
    /** 审批意见 */
    private String remark;
    
    /** 审批顺序 */
    private Integer sortOrder;
    
    private LocalDateTime createTime;
}

4.2 抽象 Handler + 链式编排

java 复制代码
/**
 * 抽象审批节点
 */
public abstract class ScrapApprovalHandler {
    
    /** 下一个审批节点 */
    protected ScrapApprovalHandler next;
    
    /** 设置下一个节点,返回 next 以支持链式调用 */
    public ScrapApprovalHandler setNext(ScrapApprovalHandler next) {
        this.next = next;
        return next;
    }
    
    /**
     * 模板方法:处理审批请求
     */
    public final ApprovalResult handle(ScrapApprovalRequest request) {
        // 1. 判断当前节点是否有权限处理
        if (canApprove(request)) {
            // 2. 执行本节点审批
            ApprovalResult result = doApprove(request);
            
            // 3. 如果通过且仍有金额未覆盖完,交给下一个节点
            if (result.isApproved() && needContinue(request)) {
                if (next != null) {
                    return next.handle(request);
                }
            }
            
            // 4. 如果驳回,直接返回,不继续往下传
            if (result.isRejected()) {
                return result;
            }
            
            return result;
        }
        
        // 本节点无权限 → 交给下一个
        if (next != null) {
            return next.handle(request);
        }
        
        // 没人能处理(理论上不会发生)
        throw new BizException("无匹配的审批节点,金额: " + request.getAmount());
    }
    
    /** 是否可以对当前请求进行审批 */
    protected abstract boolean canApprove(ScrapApprovalRequest request);
    
    /** 执行审批逻辑 */
    protected abstract ApprovalResult doApprove(ScrapApprovalRequest request);
    
    /** 审批通过后是否继续往下传(默认不传,交给更高级别审批时覆盖) */
    protected boolean needContinue(ScrapApprovalRequest request) {
        return false;
    }
    
    /** 获取本节点角色 */
    public abstract String getRole();
}
java 复制代码
/**
 * 审批结果
 */
@Data
@AllArgsConstructor
public class ApprovalResult {
    private boolean approved;
    private boolean rejected;
    private String approverRole;
    private String message;
    
    public static ApprovalResult approved(String role) {
        return new ApprovalResult(true, false, role, role + "审批通过");
    }
    
    public static ApprovalResult rejected(String role, String reason) {
        return new ApprovalResult(false, true, role, reason);
    }
    
    public static ApprovalResult passToNext(String role) {
        return new ApprovalResult(true, false, role, role + "审批通过,转交下一级");
    }
}

4.3 具体 Handler 实现

java 复制代码
/**
 * 组长审批节点------金额 ≤ 5,000
 */
@Component
@Order(1)
public class LeaderApprovalHandler extends ScrapApprovalHandler {
    
    @Override
    public String getRole() {
        return "组长";
    }
    
    @Override
    protected boolean canApprove(ScrapApprovalRequest request) {
        return request.getAmount().compareTo(new BigDecimal("5000")) <= 0;
    }
    
    @Override
    protected ApprovalResult doApprove(ScrapApprovalRequest request) {
        // 实际场景:推送给对应的组长,等待组长审批
        // 这里简化直接通过
        log.info("组长审批通过,报废单: {}, 金额: {}", 
                 request.getScrapId(), request.getAmount());
        
        // 如需记录审批记录
        // approvalRecordService.save(scrapId, "组长", "approve");
        
        return ApprovalResult.approved("组长");
    }
}
/**
 * 经理审批节点------金额 5,001 ~ 50,000
 * 注意:组长审核完,还需要经理审核
 */
@Component
@Order(2)
public class ManagerApprovalHandler extends ScrapApprovalHandler {
    
    @Override
    public String getRole() {
        return "经理";
    }
    
    @Override
    protected boolean canApprove(ScrapApprovalRequest request) {
        return request.getAmount().compareTo(new BigDecimal("50000")) <= 0;
    }
    
    @Override
    protected ApprovalResult doApprove(ScrapApprovalRequest request) {
        log.info("经理审批通过,报废单: {}, 金额: {}", 
                 request.getScrapId(), request.getAmount());
        return ApprovalResult.approved("经理");
    }
    
    /** 金额超过 5000 的,经理审批完还要交给总监 */
    @Override
    protected boolean needContinue(ScrapApprovalRequest request) {
        return request.getAmount().compareTo(new BigDecimal("5000")) > 0;
    }
}
/**
 * 总监审批节点------金额 50,001 ~ 200,000
 */
@Component
@Order(3)
public class DirectorApprovalHandler extends ScrapApprovalHandler {
    
    @Override
    public String getRole() {
        return "总监";
    }
    
    @Override
    protected boolean canApprove(ScrapApprovalRequest request) {
        return request.getAmount().compareTo(new BigDecimal("200000")) <= 0;
    }
    
    @Override
    protected ApprovalResult doApprove(ScrapApprovalRequest request) {
        log.info("总监审批通过,报废单: {}, 金额: {}", 
                 request.getScrapId(), request.getAmount());
        return ApprovalResult.approved("总监");
    }
    
    /** 金额超过 50000 的转交总经理 */
    @Override
    protected boolean needContinue(ScrapApprovalRequest request) {
        return request.getAmount().compareTo(new BigDecimal("50000")) > 0;
    }
}
/**
 * 总经理审批节点------金额 > 200,000
 * 最后一个节点,不需要 needContinue
 */
@Component
@Order(4)
public class GmApprovalHandler extends ScrapApprovalHandler {
    
    @Override
    public String getRole() {
        return "总经理";
    }
    
    @Override
    protected boolean canApprove(ScrapApprovalRequest request) {
        return true;  // 兜底:只要到我这,就能批
    }
    
    @Override
    protected ApprovalResult doApprove(ScrapApprovalRequest request) {
        log.info("总经理审批通过,报废单: {}, 金额: {}", 
                 request.getScrapId(), request.getAmount());
        return ApprovalResult.approved("总经理");
    }
}

4.4 Spring 自动编排链

关键设计:用 @Order 注解 + Spring 自动注入,Handler 自己不用关心链式关系。

java 复制代码
/**
 * 审批链工厂------自动编排 Handler 链
 */
@Component
public class ApprovalChainFactory {
    
    /** Spring 自动注入所有 ScrapApprovalHandler,按 @Order 排序 */
    @Autowired
    private List<ScrapApprovalHandler> handlers;
    
    /** 链头 */
    private ScrapApprovalHandler chainHead;
    
    @PostConstruct
    public void initChain() {
        if (handlers == null || handlers.isEmpty()) {
            throw new IllegalStateException("未配置任何审批节点");
        }
        
        // 按 @Order 排序(Spring 已排序,再手动确保)
        handlers.sort(AnnotationAwareOrderComparator.INSTANCE);
        
        // 链式编排:Handler1 → Handler2 → Handler3 → Handler4
        chainHead = handlers.get(0);
        ScrapApprovalHandler current = chainHead;
        for (int i = 1; i < handlers.size(); i++) {
            current.setNext(handlers.get(i));
            current = handlers.get(i);
        }
        
        log.info("审批链初始化完成,共 {} 个节点", handlers.size());
    }
    
    /**
     * 处理审批请求
     */
    public ApprovalResult process(ScrapApprovalRequest request) {
        return chainHead.handle(request);
    }
    
    /**
     * 获取当前链的所有节点角色(用于前端展示审批流程)
     */
    public List<String> getChainRoles() {
        return handlers.stream()
            .map(ScrapApprovalHandler::getRole)
            .collect(Collectors.toList());
    }
}

4.5 Service 层------提交报废审批

java 复制代码
/**
 * 报废审批 Service
 */
@Service
@RequiredArgsConstructor
public class ScrapApprovalService {
    
    private final ApprovalChainFactory chainFactory;
    private final ScrapOrderMapper scrapOrderMapper;
    private final ApprovalRecordMapper approvalRecordMapper;
    
    /**
     * 提交报废审批
     */
    @Transactional(rollbackFor = Exception.class)
    public void submitScrapApproval(ScrapApprovalRequest request) {
        // 1. 校验报废单
        ScrapOrder order = scrapOrderMapper.selectById(request.getScrapId());
        if (order == null) {
            throw new BizException("报废单不存在");
        }
        if (order.getStatus() != 0) {
            throw new BizException("报废单状态不允许提交审批");
        }
        
        // 2. 注入金额
        request.setAmount(order.getAmount());
        
        // 3. 走审批链
        ApprovalResult result = chainFactory.process(request);
        
        // 4. 更新报废单状态
        order.setStatus(result.isApproved() ? 2 : 3);  // 2-通过 3-驳回
        order.setCurrentApproverRole(result.getApproverRole());
        order.setUpdateTime(LocalDateTime.now());
        scrapOrderMapper.updateById(order);
        
        // 5. 保存审批记录
        ApprovalRecord record = new ApprovalRecord();
        record.setScrapId(order.getId());
        record.setApproverRole(result.getApproverRole());
        record.setAction(result.isApproved() ? "approve" : "reject");
        record.setRemark(request.getRemark());
        record.setCreateTime(LocalDateTime.now());
        approvalRecordMapper.insert(record);
    }
}

4.6 Controller 层

java 复制代码
@RestController
@RequestMapping("/scrap/approval")
@RequiredArgsConstructor
public class ScrapApprovalController {
    
    private final ScrapApprovalService scrapApprovalService;
    
    @PostMapping("/submit")
    public R<Void> submitApproval(@RequestBody ScrapApprovalRequest request) {
        scrapApprovalService.submitScrapApproval(request);
        return R.ok("审批提交成功");
    }
    
    @GetMapping("/chain")
    public R<List<String>> getApprovalChain() {
        // 前端用来展示:当前报废单需要经过哪些角色审批
        return R.ok(chainFactory.getChainRoles());
    }
}

⑤ 执行流程全景

以金额 80,000 的报废单为例,完整的执行路径:

复制代码
请求: amount=80000, scrapId=1001
    │
    ▼
LeaderApprovalHandler.canApprove(80000)
    ├─ 80000 <= 5000? → false
    └─ → next.handle()
        │
        ▼
    ManagerApprovalHandler.canApprove(80000)
        ├─ 80000 <= 50000? → false
        └─ → next.handle()
            │
            ▼
        DirectorApprovalHandler.canApprove(80000)
            ├─ 80000 <= 200000? → true ✅
            ├─ doApprove → 总监审批通过
            ├─ needContinue(80000)?
            │   └─ 80000 > 50000? → true → next.handle()
            │       │
            │       ▼
            │   GmApprovalHandler.canApprove(80000)
            │       ├─ true(兜底)
            │       ├─ doApprove → 总经理审批通过
            │       └─ needContinue → false(末节点)
            │           │
            │           ▼
            └─ 返回最终结果: 已通过

各金额范围的审批路径:

金额 审批路径
¥3,000 组长 ✅ → 结束
¥20,000 组长 ✅ → 经理 ✅ → 结束
¥80,000 组长 ✅ → 经理 ✅ → 总监 ✅ → 总经理 ✅ → 结束
¥300,000 组长 ✅ → 经理 ✅ → 总监 ✅ → 总经理 ✅ → 结束

⑥ 扩展场景

6.1 不同资产类别走不同审批链

java 复制代码
@Component
public class ApprovalChainFactory {
    
    /** key=assetCategory, value=审批链头 */
    private Map<String, ScrapApprovalHandler> chainMap = new HashMap<>();
    
    @PostConstruct
    public void initChains() {
        // 生产设备:组长 → 经理 → 总监
        chainMap.put("production", buildChain(
            leader, manager, director
        ));
        
        // 办公设备:组长 → 经理(简化流程)
        chainMap.put("office", buildChain(
            leader, manager
        ));
        
        // IT 设备:组长 → IT 主管 → 经理
        chainMap.put("it", buildChain(
            leader, itManager, manager
        ));
    }
    
    public ApprovalResult process(ScrapApprovalRequest request) {
        ScrapApprovalHandler chain = chainMap.get(request.getAssetCategory());
        if (chain == null) {
            chain = defaultChain; // 默认链
        }
        return chain.handle(request);
    }
}

6.2 并行审批(会签)

当需要"总监和财务总监同时审批"时,引入并行节点:

java 复制代码
/**
 * 并行审批节点------所有子节点都通过才算通过
 */
public class ParallelApprovalHandler extends ScrapApprovalHandler {
    
    private List<ScrapApprovalHandler> parallelHandlers;
    
    @Override
    protected ApprovalResult doApprove(ScrapApprovalRequest request) {
        for (ScrapApprovalHandler handler : parallelHandlers) {
            ApprovalResult result = handler.handle(request);
            if (result.isRejected()) {
                return result; // 一个驳回就是驳回
            }
        }
        return ApprovalResult.approved("会签");
    }
}

6.3 动态审批金额(配置化)

把金额阈值放到数据库或配置中心,不用改代码:

java 复制代码
// 数据库表 approval_rule
// id | role | min_amount | max_amount | sort_order

@Component
public class DynamicApprovalHandler extends ScrapApprovalHandler {
    
    @Autowired
    private ApprovalRuleMapper ruleMapper;
    
    @Override
    protected boolean canApprove(ScrapApprovalRequest request) {
        ApprovalRule rule = ruleMapper.selectByRole(getRole());
        return request.getAmount().compareTo(rule.getMinAmount()) >= 0
            && request.getAmount().compareTo(rule.getMaxAmount()) <= 0;
    }
}

⑦ 常见坑与最佳实践

7.1 易错点清单

# 坑点 现象 ✅ 避坑方案
1 Handler 用 @Component 但未加 @Order 链顺序随机 全部加上 @Order 明确顺序
2 金额比较用 BigDecimal 的 equals 2.0 和 2.00 比较失败 compareTo() 而不是 equals()
3 末节点 canApprove 没兜底 超出金额范围抛异常 最末节点设 canApprove = true
4 needContinue 逻辑遗漏 金额>5000的只走了组长/经理就结束了 每个节点检查有没有更高审批层级
5 事务范围覆盖整个链 审批链长时连接长时间占用 审批链只做逻辑判断,事务放在 Service 层

7.2 自测问题

Q:你们审批流怎么设计的?
用责任链模式,每个审批节点是一个独立的 Handler 类。Handler 的判断逻辑是金额区间------比如组长只能批 5000 以内的,经理批 50000 以内。超出自己范围就交链上下一个 Handler。所有 Handler 通过 Spring 自动注入 + @Order 排序,由工厂类 @PostConstruct 编排成链,Service 层调 chainHead.handle() 即可。
Q:不用 if-else 而用责任链,好处是什么?
主要有三点。一是职责单一,每个 Handler 只关心自己的逻辑,组长 Handler 不会看到经理的代码。二是扩展方便,新加一个审批角色只需新增一个 Handler 类,插入到链中就行,不用改任何已有代码。三是不同资产类别可以自由组合不同的链,生产设备走 4 级审批,办公设备走 2 级,通过工厂配置就行。
Q:金额超过 5000 需要组长+经理双重审批,这个在责任链里怎么实现的?
Handler 有一个 needContinue() 方法。比如组长节点:金额 ≤5000 就自己批完并结束;金额 >5000 自己先批,但返回 passToNext 把请求继续往下传。这样金额 30000 的单子就会依次经过组长→经理,各自做审批记录,最终谁驳回就终止。
Q:如果以后规则复杂了,责任链还够用吗?
纯金额区间 + 角色是标配场景,责任链足够了。如果后面加了维度(部门归属、资产类别、历史报废次数等)组合成复杂表达式,可以升级成规则引擎(如 Drools、QLExpress),把规则存到数据库,Handler 的 canApprove 里调用规则引擎判断。


⑧ 总结

核心要点速览

维度 要点 一句话记住
核心模式 责任链 每个 Handler 独立判断,能批就批,不能就传
编排方式 @Order + Spring 注入 不改代码调顺序
金额比较 compareTo() 不是 equals,是 compareTo
多维度 Map 维护多链 不同资产走不同链
兜底 末节点 canApprove=true 不漏单

自检清单

  • 我能写出责任链的抽象 Handler 基类
  • 我知道 needContinue 的用法和什么时候需要它
  • 我能用 Spring @Order + List 注入自动编排链
  • 我知道怎么扩展到不同资产类别走不同审批链
  • 我知道金额比较要用 compareTo 而不是 equals

目录结构

复制代码
com.example.scrap.approval
├── entity
│   ├── ScrapOrder.java           # 报废单实体
│   ├── ApprovalRecord.java       # 审批记录
│   └── ScrapApprovalRequest.java # 审批请求 DTO
├── handler
│   ├── ScrapApprovalHandler.java  # 抽象 Handler
│   ├── LeaderApprovalHandler.java # 组长
│   ├── ManagerApprovalHandler.java# 经理
│   ├── DirectorApprovalHandler.java# 总监
│   └── GmApprovalHandler.java    # 总经理
├── factory
│   └── ApprovalChainFactory.java  # 链工厂
├── service
│   └── ScrapApprovalService.java  # 审批 Service
└── controller
    └── ScrapApprovalController.java
相关推荐
geovindu2 小时前
python: Functional Options Pattern
开发语言·后端·python·设计模式·惯用法模式·函数式选项模式
2501_925963382 小时前
外设的常见问题
linux
wuyk5552 小时前
24. C 语言模块化:不是拆几个.c 文件那么简单
c语言·开发语言·stm32·单片机
l1t2 小时前
在linux和windows中解决duckdb 1.6dev版本输出执行计划报错问题
linux·运维·数据库·windows·duckdb
柳鲲鹏2 小时前
LINUX高通平台交叉编译地图软件GDAL
linux
凯瑟琳.奥古斯特2 小时前
K次取反最大化数组和解法(力扣1005)
开发语言·c++·算法·leetcode·职场和发展
fei_sun2 小时前
路径MTU发现
linux·运维·网络
AC赳赳老秦3 小时前
防火墙规则批量配置实战:OpenClaw 自动生成模板、批量下发与合规性校验全解析
java·开发语言·人工智能·python·github·php·openclaw
☆cwlulu3 小时前
调试排查工具介绍(gdb、strace、Valgrind等)
开发语言·c++·嵌入式硬件·ubuntu