1. 为什么需要单接口适配多审核节点?
在企业级应用开发中,审核流程的设计往往面临一个典型困境:随着业务发展,审核节点数量不断增加,每个节点可能有不同的业务规则和处理逻辑。如果为每个审核节点单独开发接口,会导致代码臃肿、维护困难。我最近在重构一个电商平台的订单审核系统时就遇到了这个问题——原本只有简单的客服审核,后来陆续增加了风控审核、财务审核、物流审核等6个节点,代码里充斥着大量的if-else分支。
更糟糕的是,不同业务线的审核流程还存在差异:普通商品订单需要3级审核,虚拟商品只需2级,而大额订单则要多走一道财务总监审批。如果继续用传统方式开发,每新增一个审核类型就需要修改核心业务代码,不仅容易引入bug,还违反了开闭原则。
2. 策略模式与工厂模式的组合方案设计
2.1 策略模式处理审核逻辑差异
策略模式的核心思想是将算法家族分别封装,使它们可以互相替换。对应到审核系统,我们可以为每种审核类型创建具体的策略类:
public interface AuditStrategy { AuditResult execute(AuditContext context); } // 示例:风控审核策略 public class RiskAuditStrategy implements AuditStrategy { @Override public AuditResult execute(AuditContext context) { // 调用风控规则引擎 RiskEvaluation risk = riskEngine.evaluate(context.getOrder()); if (risk.getScore() > 60) { return AuditResult.reject("风控评分过高"); } return AuditResult.pass(); } }2.2 工厂模式管理策略实例
单纯使用策略模式还存在一个问题:客户端需要知道具体使用哪个策略。这时候就需要工厂模式来封装对象创建逻辑:
public class AuditStrategyFactory { private static Map<AuditType, AuditStrategy> strategies = new HashMap<>(); static { strategies.put(AuditType.RISK, new RiskAuditStrategy()); strategies.put(AuditType.FINANCE, new FinanceAuditStrategy()); // 其他策略注册... } public static AuditStrategy getStrategy(AuditType type) { return strategies.get(type); } }2.3 两种模式的协作流程
当审核请求到达统一接口时,处理流程如下:
- 解析请求参数获取审核类型
- 通过工厂获取对应策略实例
- 执行策略的审核方法
- 返回标准化结果
这种架构下,新增审核类型只需要:
- 创建新的策略实现类
- 在工厂中注册映射关系
- 无需修改任何现有业务代码
3. 核心实现细节与避坑指南
3.1 上下文对象的设计技巧
策略执行需要的参数应该通过上下文对象传递,我推荐采用Builder模式构建:
public class AuditContext { private Order order; private User operator; private Map<String, Object> extParams; // Builder实现... } // 使用示例 AuditContext context = AuditContext.builder() .order(order) .operator(currentUser) .param("urgent", true) .build();实际项目中遇到过JSON参数直接透传导致的序列化问题,建议在上下文对象中明确定义所有需要传递的参数。
3.2 策略的线程安全性处理
策略对象通常是无状态的,可以被多线程共享。但如果策略中需要依赖某些有状态的服务(如数据库连接),有两种解决方案:
- 每次从工厂获取新实例(适合轻量级对象)
public AuditStrategy getStrategy(AuditType type) { return strategyPrototypes.get(type).clone(); }- 使用ThreadLocal管理资源(适合重量级对象)
public class FinanceAuditStrategy implements AuditStrategy { private ThreadLocal<AccountingService> accountingService; public FinanceAuditStrategy() { accountingService = ThreadLocal.withInitial(() -> new AccountingService()); } }3.3 审核结果的标准化设计
建议定义统一的返回结构,包含:
- 是否通过
- 错误代码(可枚举)
- 提示信息
- 扩展数据
public class AuditResult { private boolean passed; private ErrorCode errorCode; private String message; private Map<String, Object> data; public static AuditResult pass() { return new AuditResult(true, null, null, null); } // 其他工厂方法... }4. 性能优化与扩展实践
4.1 策略的懒加载与缓存
当策略数量较多时,可以采用懒加载策略:
public class LazyInitStrategyFactory { private ConcurrentHashMap<AuditType, AuditStrategy> strategies = new ConcurrentHashMap<>(); public AuditStrategy getStrategy(AuditType type) { return strategies.computeIfAbsent(type, this::createStrategy); } private AuditStrategy createStrategy(AuditType type) { // 根据类型创建具体策略 } }4.2 组合策略的实现
对于需要多个策略组合执行的场景,可以设计组合策略:
public class CompositeAuditStrategy implements AuditStrategy { private List<AuditStrategy> strategies; @Override public AuditResult execute(AuditContext context) { for (AuditStrategy strategy : strategies) { AuditResult result = strategy.execute(context); if (!result.isPassed()) { return result; } } return AuditResult.pass(); } }4.3 与Spring框架的集成技巧
在Spring环境中,可以更优雅地管理策略bean:
@Service public class SpringAuditStrategyFactory { @Autowired private Map<String, AuditStrategy> strategyMap; public AuditStrategy getStrategy(AuditType type) { return strategyMap.get(type.getBeanName()); } } // 策略实现类添加注解 @Component("riskAuditStrategy") public class RiskAuditStrategy implements AuditStrategy { //... }5. 实际应用中的经验总结
在电商订单审核系统重构过程中,这套方案帮助我们实现了:
- 审核节点从6个扩展到15个,代码量反而减少40%
- 新审核类型的上线时间从2天缩短到2小时
- 不同业务线的审核流程可以独立配置
几个特别值得注意的实践经验:
策略接口的设计要足够抽象,但不要过度设计。初期我们试图设计一个万能接口,结果导致所有策略类都要处理大量无关参数。
上下文对象的版本兼容性很重要。新增字段时尽量采用Optional包装,避免旧策略报NPE。
对于耗时较长的审核策略(如风控引擎调用),建议设置超时控制:
public AuditResult executeWithTimeout(AuditContext ctx, long timeout) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<AuditResult> future = executor.submit(() -> execute(ctx)); try { return future.get(timeout, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { return AuditResult.reject("审核超时"); } }这套方案特别适合以下场景:
- 存在多个相似但又有差异的处理流程
- 处理逻辑可能需要频繁变更或扩展
- 需要支持动态配置的业务规则
对于简单的审核流程(只有2-3个固定节点),直接使用if-else可能更合适,避免过度设计带来的复杂度。架构决策永远要考虑投入产出比。