buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

聚合系统设计:策略模式(Strategy Pattern)在银行通道对接场景中的应用

Java策略模式:银行接口对接场景示例

📊 策略模式简介

策略模式(Strategy Pattern)定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。

银行接口对接场景需求:

假设我们需要对接多家银行的支付接口,每家银行的支付方式、签名算法、通信协议都不同,但对外提供统一的支付接口。

🎯 完整示例代码

1. 策略接口定义

/**
 * 银行支付策略接口
 * 定义所有银行支付都需要实现的方法
 */
public interface BankPaymentStrategy {
    
    /**
     * 支付接口
     * @param paymentRequest 支付请求参数
     * @return 支付结果
     */
    PaymentResult pay(PaymentRequest paymentRequest);
    
    /**
     * 查询支付状态
     * @param orderNo 订单号
     * @return 支付状态
     */
    PaymentStatus queryStatus(String orderNo);
    
    /**
     * 退款接口
     * @param refundRequest 退款请求
     * @return 退款结果
     */
    RefundResult refund(RefundRequest refundRequest);
    
    /**
     * 获取银行编码
     * @return 银行编码
     */
    String getBankCode();
}

2. 策略实现类(具体银行)

工商银行策略

点击查看代码
/**
 * 工商银行支付策略实现
 */
@Component("ICBCPaymentStrategy")
public class ICBCPaymentStrategy implements BankPaymentStrategy {
    
    private static final String BANK_CODE = "ICBC";
    
    @Override
    public PaymentResult pay(PaymentRequest paymentRequest) {
        // 工商银行特有的支付逻辑
        System.out.println("使用工商银行支付策略进行支付");
        
        // 1. 构建工商银行特定的请求参数
        ICBCPaymentRequest icbcRequest = buildICBCRequest(paymentRequest);
        
        // 2. 使用工商银行的签名算法
        String sign = generateICBCSign(icbcRequest);
        icbcRequest.setSign(sign);
        
        // 3. 调用工商银行接口(示例:HTTP调用)
        String response = callICBCApi(icbcRequest);
        
        // 4. 解析响应并返回统一格式
        return parseICBCResponse(response);
    }
    
    @Override
    public PaymentStatus queryStatus(String orderNo) {
        System.out.println("使用工商银行策略查询订单状态");
        // 工商银行特有的查询逻辑
        ICBCQueryRequest request = new ICBCQueryRequest(orderNo);
        request.setMerchantId("your_icbc_merchant_id");
        request.setSign(generateICBCSign(request));
        
        String response = callICBCQueryApi(request);
        return parseICBCStatus(response);
    }
    
    @Override
    public RefundResult refund(RefundRequest refundRequest) {
        System.out.println("使用工商银行策略进行退款");
        // 工商银行特有的退款逻辑
        ICBCRefundRequest request = new ICBCRefundRequest(refundRequest);
        request.setSign(generateICBCSign(request));
        
        String response = callICBCRefundApi(request);
        return parseICBCRefundResponse(response);
    }
    
    @Override
    public String getBankCode() {
        return BANK_CODE;
    }
    
    // 工商银行特有方法
    private ICBCPaymentRequest buildICBCRequest(PaymentRequest request) {
        ICBCPaymentRequest icbcRequest = new ICBCPaymentRequest();
        icbcRequest.setAmount(request.getAmount());
        icbcRequest.setOrderNo(request.getOrderNo());
        icbcRequest.setMerchantId("your_icbc_merchant_id");
        icbcRequest.setNotifyUrl("https://your-domain.com/notify/icbc");
        return icbcRequest;
    }
    
    private String generateICBCSign(Object request) {
        // 工商银行特有的签名算法
        return "ICBC_SIGN_" + System.currentTimeMillis();
    }
    
    private String callICBCApi(ICBCPaymentRequest request) {
        // 调用工商银行API
        System.out.println("调用工商银行支付接口: " + request);
        return "{\"code\":\"0000\",\"msg\":\"成功\",\"icbcOrderId\":\"ICBC" + System.currentTimeMillis() + "\"}";
    }
    
    private PaymentResult parseICBCResponse(String response) {
        // 解析工商银行响应
        System.out.println("解析工商银行响应: " + response);
        return PaymentResult.success("ICBC" + System.currentTimeMillis());
    }
    
    // 其他工商银行特有方法...
}

建设银行策略

点击查看代码
/**
 * 建设银行支付策略实现
 */
@Component("CCBPaymentStrategy")
public class CCBPaymentStrategy implements BankPaymentStrategy {
    
    private static final String BANK_CODE = "CCB";
    
    @Override
    public PaymentResult pay(PaymentRequest paymentRequest) {
        // 建设银行特有的支付逻辑
        System.out.println("使用建设银行支付策略进行支付");
        
        // 1. 使用建设银行的加密方式
        String encryptedData = encryptWithCCB(paymentRequest);
        
        // 2. 构建建设银行请求头
        Map<String, String> headers = buildCCBHeaders();
        
        // 3. 调用建设银行接口(可能使用不同的协议)
        String response = callCCBApi(encryptedData, headers);
        
        // 4. 建设银行特有的响应解析
        return parseCCBResponse(response);
    }
    
    @Override
    public PaymentStatus queryStatus(String orderNo) {
        System.out.println("使用建设银行策略查询订单状态");
        // 建设银行特有的查询逻辑
        CCBQueryRequest request = new CCBQueryRequest();
        request.setOrderNo(orderNo);
        request.setTimestamp(String.valueOf(System.currentTimeMillis()));
        
        String sign = generateCCBSign(request);
        request.setSign(sign);
        
        String response = callCCBQueryApi(request);
        return parseCCBStatus(response);
    }
    
    @Override
    public RefundResult refund(RefundRequest refundRequest) {
        System.out.println("使用建设银行策略进行退款");
        // 建设银行特有的退款逻辑
        CCBRefundRequest request = new CCBRefundRequest(refundRequest);
        request.setRefundType("CCB_SPECIAL_REFUND");
        
        String response = callCCBRefundApi(request);
        return parseCCBRefundResponse(response);
    }
    
    @Override
    public String getBankCode() {
        return BANK_CODE;
    }
    
    // 建设银行特有方法
    private String encryptWithCCB(PaymentRequest request) {
        // 建设银行特有的加密算法
        return "CCB_ENCRYPTED_" + request.getOrderNo();
    }
    
    private Map<String, String> buildCCBHeaders() {
        Map<String, String> headers = new HashMap<>();
        headers.put("CCB-Api-Key", "your_ccb_api_key");
        headers.put("CCB-Version", "2.0");
        headers.put("Content-Type", "application/json");
        return headers;
    }
    
    private String callCCBApi(String encryptedData, Map<String, String> headers) {
        // 调用建设银行API
        System.out.println("调用建设银行支付接口,headers: " + headers);
        return "{\"return_code\":\"SUCCESS\",\"ccb_order_id\":\"CCB" + System.currentTimeMillis() + "\"}";
    }
    
    private PaymentResult parseCCBResponse(String response) {
        // 解析建设银行响应
        System.out.println("解析建设银行响应: " + response);
        return PaymentResult.success("CCB" + System.currentTimeMillis());
    }
    
    // 其他建设银行特有方法...
}

农业银行策略

点击查看代码
/**
 * 农业银行支付策略实现
 */
@Component("ABCPaymentStrategy")
public class ABCPaymentStrategy implements BankPaymentStrategy {
    
    private static final String BANK_CODE = "ABC";
    
    @Override
    public PaymentResult pay(PaymentRequest paymentRequest) {
        // 农业银行特有的支付逻辑
        System.out.println("使用农业银行支付策略进行支付");
        
        // 1. 农业银行需要特殊的参数转换
        ABCPaymentRequest abcRequest = convertToABCRequest(paymentRequest);
        
        // 2. 农业银行的签名方式不同
        abcRequest.setSignature(generateABCSignature(abcRequest));
        
        // 3. 调用农业银行接口(可能需要SSL双向认证)
        String response = callABCApiWithSSL(abcRequest);
        
        // 4. 农业银行特有的响应处理
        return parseABCResponse(response);
    }
    
    @Override
    public PaymentStatus queryStatus(String orderNo) {
        System.out.println("使用农业银行策略查询订单状态");
        // 农业银行特有的查询逻辑
        ABCStatusRequest request = new ABCStatusRequest();
        request.setOrderNo(orderNo);
        request.setQueryType("FULL");
        
        String response = callABCQueryApi(request);
        return parseABCStatus(response);
    }
    
    @Override
    public RefundResult refund(RefundRequest refundRequest) {
        System.out.println("使用农业银行策略进行退款");
        // 农业银行特有的退款逻辑
        ABCRefundRequest request = new ABCRefundRequest(refundRequest);
        request.setChannel("ABC_MOBILE");
        
        String response = callABCRefundApi(request);
        return parseABCRefundResponse(response);
    }
    
    @Override
    public String getBankCode() {
        return BANK_CODE;
    }
    
    // 农业银行特有方法
    private ABCPaymentRequest convertToABCRequest(PaymentRequest request) {
        ABCPaymentRequest abcRequest = new ABCPaymentRequest();
        abcRequest.setTransAmount(request.getAmount());
        abcRequest.setMerOrderNo(request.getOrderNo());
        abcRequest.setCurrency("CNY");
        abcRequest.setGoodsDesc(request.getGoodsName());
        return abcRequest;
    }
    
    private String generateABCSignature(ABCPaymentRequest request) {
        // 农业银行特有的签名算法
        return "ABC_SIGNATURE_" + request.getMerOrderNo();
    }
    
    private String callABCApiWithSSL(ABCPaymentRequest request) {
        // 调用农业银行API(需要SSL证书)
        System.out.println("调用农业银行支付接口(SSL双向认证)");
        return "{\"retCode\":\"000000\",\"retMsg\":\"成功\",\"abcOrderId\":\"ABC" + System.currentTimeMillis() + "\"}";
    }
    
    private PaymentResult parseABCResponse(String response) {
        // 解析农业银行响应
        System.out.println("解析农业银行响应: " + response);
        return PaymentResult.success("ABC" + System.currentTimeMillis());
    }
}

3. 策略上下文(支付服务)

/**
 * 支付服务上下文
 * 负责管理不同的银行支付策略
 */
@Service
public class BankPaymentStrategyRoute {
    
    private final Map<String, BankPaymentStrategy> strategyMap = new ConcurrentHashMap<>();
    
    /**
     * 构造函数注入所有策略实现
     */
    @Autowired
    public BankPaymentStrategyRoute(List<BankPaymentStrategy> strategies) {
        for (BankPaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getBankCode(), strategy);
            System.out.println("注册银行支付策略: " + strategy.getBankCode());
        }
    }
    
    /**
     * 支付方法
     * @param bankCode 银行编码
     * @param request 支付请求
     * @return 支付结果
     */
    public PaymentResult pay(String bankCode, PaymentRequest request) {
        BankPaymentStrategy strategy = getStrategy(bankCode);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
        }
        
        // 验证支付参数
        validatePaymentRequest(request);
        
        // 执行支付策略
        return strategy.pay(request);
    }
    
    /**
     * 查询支付状态
     * @param bankCode 银行编码
     * @param orderNo 订单号
     * @return 支付状态
     */
    public PaymentStatus queryStatus(String bankCode, String orderNo) {
        BankPaymentStrategy strategy = getStrategy(bankCode);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
        }
        
        return strategy.queryStatus(orderNo);
    }
    
    /**
     * 退款
     * @param bankCode 银行编码
     * @param refundRequest 退款请求
     * @return 退款结果
     */
    public RefundResult refund(String bankCode, RefundRequest refundRequest) {
        BankPaymentStrategy strategy = getStrategy(bankCode);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
        }
        
        return strategy.refund(refundRequest);
    }
    
    /**
     * 获取支付策略
     * @param bankCode 银行编码
     * @return 策略实例
     */
    private BankPaymentStrategy getStrategy(String bankCode) {
        return strategyMap.get(bankCode);
    }
    
    /**
     * 获取支持的所有银行
     * @return 银行编码列表
     */
    public List<String> getSupportedBanks() {
        return new ArrayList<>(strategyMap.keySet());
    }
    
    /**
     * 动态注册策略(支持热更新)
     * @param strategy 策略实例
     */
    public void registerStrategy(BankPaymentStrategy strategy) {
        strategyMap.put(strategy.getBankCode(), strategy);
        System.out.println("动态注册银行策略: " + strategy.getBankCode());
    }
    
    /**
     * 验证支付请求
     */
    private void validatePaymentRequest(PaymentRequest request) {
        if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("支付金额必须大于0");
        }
        if (StringUtils.isBlank(request.getOrderNo())) {
            throw new IllegalArgumentException("订单号不能为空");
        }
        // 更多验证...
    }
}

4. 相关实体类

/**
 * 支付请求参数
 */
@Data
public class PaymentRequest {
    private String orderNo;          // 订单号
    private BigDecimal amount;       // 金额
    private String goodsName;        // 商品名称
    private String buyerId;          // 买家ID
    private String bankCardNo;       // 银行卡号
    private String mobile;           // 手机号
    // 其他通用参数...
}

/**
 * 支付结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentResult {
    private boolean success;         // 是否成功
    private String message;          // 消息
    private String bankOrderNo;      // 银行订单号
    private String payUrl;           // 支付链接(如果需要跳转)
    
    public static PaymentResult success(String bankOrderNo) {
        return new PaymentResult(true, "支付成功", bankOrderNo, null);
    }
    
    public static PaymentResult fail(String message) {
        return new PaymentResult(false, message, null, null);
    }
}

/**
 * 支付状态
 */
@Data
public class PaymentStatus {
    private String orderNo;          // 订单号
    private String status;           // 状态:SUCCESS/FAIL/PROCESSING
    private String bankStatus;       // 银行原始状态
    private Date paymentTime;        // 支付时间
}

/**
 * 退款请求
 */
@Data
public class RefundRequest {
    private String orderNo;          // 原订单号
    private String refundNo;         // 退款单号
    private BigDecimal amount;       // 退款金额
    private String reason;           // 退款原因
}

/**
 * 退款结果
 */
@Data
public class RefundResult {
    private boolean success;         // 是否成功
    private String message;          // 消息
    private String refundId;         // 银行退款ID
}

6. 使用示例

@SpringBootTest
public class BankPaymentTest {
    
    @Autowired
    private BankPaymentStrategyRoute route;
    
    @Test
    public void testBankPayment() {
        // 模拟支付请求
        PaymentRequest request = new PaymentRequest();
        request.setOrderNo("ORDER20230215001");
        request.setAmount(new BigDecimal("100.00"));
        request.setGoodsName("测试商品");
        request.setBuyerId("USER001");
        
        // 测试工商银行支付
        System.out.println("=== 测试工商银行支付 ===");
        PaymentResult icbcResult = route.pay("ICBC", request);
        System.out.println("工商银行支付结果: " + icbcResult);
        
        // 测试建设银行支付
        System.out.println("\n=== 测试建设银行支付 ===");
        PaymentResult ccbResult = route.pay("CCB", request);
        System.out.println("建设银行支付结果: " + ccbResult);
        
        // 获取支持的所有银行
        System.out.println("\n=== 支持的银行列表 ===");
        List<String> banks = BankPaymentStrategyRoute.getSupportedBanks();
        System.out.println("支持银行: " + banks);
    }
}

📊 策略模式的优势

优势 说明
开闭原则 新增银行时,只需添加新策略类,无需修改现有代码
单一职责 每个银行策略只负责自己的业务逻辑
易于扩展 新增支付方式(如支付宝、微信)同样适用
便于测试 可以单独测试每个银行的策略
动态切换 运行时可根据银行编码动态切换策略

🔧 附:策略模式在Spring中的增强

使用工厂模式进一步增强

在上面的基础策略模式实现中,BankPaymentStrategyRoute(路由器/上下文)承担了两个核心职责:

  • 策略的管理与路由:维护策略映射表,并根据bankCode选择正确的策略。
  • 策略的执行:调用所选策略的业务方法(如pay)。

“工厂增强”提议将第一个职责中的“策略对象的获取/创建”部分剥离出来,交给一个专门的BankStrategyFactory(工厂)来处理。通过引入工厂模式,可以进一步解耦和精细化系统中各组件的职责。(当然,通常情况下,完全可以让 BankPaymentStrategyRoute 承担这2个职责,可以不必单拎出来一个 BankStrategyFactory 工厂类,这样更简洁)

/**
 * 银行策略工厂
 */
@Component
public class BankStrategyFactory {
    
    private final Map<String, BankPaymentStrategy> strategyMap = new HashMap<>();
    
    @Autowired
    public BankStrategyFactory(List<BankPaymentStrategy> strategies) {
        for (BankPaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getBankCode().toLowerCase(), strategy);
        }
    }
    
    /**
     * 根据银行编码获取策略
     */
    public BankPaymentStrategy getStrategy(String bankCode) {
        BankPaymentStrategy strategy = strategyMap.get(bankCode.toLowerCase());
        if (strategy == null) {
            throw new UnsupportedOperationException("不支持的银行: " + bankCode);
        }
        return strategy;
    }
    
    /**
     * 判断是否支持该银行
     */
    public boolean supports(String bankCode) {
        return strategyMap.containsKey(bankCode.toLowerCase());
    }
}

借助 annotation注解 来进行自动注册

上文使用策略接口BankPaymentStrategy中的 getBankCode 来定义策略。

本章节通过自定义注解 @BankStrategy 来定义策略bean。先上代码。

/**
 * 银行策略注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface BankStrategy {
    String value(); // 银行编码
}

/**
 * 带注解的策略实现
 */
@BankStrategy("icbc")
public class ICBCPaymentStrategy implements BankPaymentStrategy {
    // 实现...
}

/**
 * 使用注解扫描的策略工厂
 */
@Component
public class BankStrategyFactory {
    
    private final Map<String, BankPaymentStrategy> strategyMap = new HashMap<>();
    
    @Autowired
    public BankStrategyFactory(ApplicationContext context) {
        Map<String, Object> beans = context.getBeansWithAnnotation(BankStrategy.class);
        beans.forEach((beanName, bean) -> {
            if (bean instanceof BankPaymentStrategy) {
                BankStrategy annotation = bean.getClass().getAnnotation(BankStrategy.class);
                strategyMap.put(annotation.value(), (BankPaymentStrategy) bean);
            }
        });
    }
    
    public BankPaymentStrategy getStrategy(String bankCode) {
        return strategyMap.get(bankCode);
    }
}

与基础实现方式的对比

  • 基础实现方式:路由器(BankPaymentStrategyRoute)在其构造函数中,通过@Autowired List获取到所有实现了BankPaymentStrategy接口的Bean,然后遍历这个列表,调用每个Bean的getBankCode()方法,将得到的编码和Bean本身放入Map。

  • 注解自动注册方式:专门的工厂(BankStrategyFactory)利用Spring的ApplicationContext,只获取带有@BankStrategy注解的Bean,并读取注解中的value值(即"icbc")作为标识,构建Map。


代码结构建议:

使用该策略模式的建议的代码分层及层级结构如下:

bankpayment
├── model                      # 核心模型(公共领域对象)
│   ├── PaymentRequest.java
│   ├── PaymentResult.java
│   ├── PaymentStatus.java
│   └── RefundRequest.java
├── common                     # 公共基础设施
│   ├── BankPaymentException.java
│   ├── UnsupportedBankException.java
│   └── BankConstants.java
├── strategy                   # 策略模式核心
│   ├── BankPaymentStrategy.java
│   ├── BankPaymentStrategyRoute.java
│   └── BankPaymentFactory.java
├── icbc                      # 工商银行实现
│   ├── ICBCPaymentStrategy.java
│   ├── model
│   │   ├── ICBCPaymentRequest.java
│   │   └── ICBCPaymentResponse.java
│   ├── client
│   │   └── ICBCClient.java
│   └── util
│       └── ICBCSignUtil.java
└── ccb                      # 建设银行实现
    ├── CCBPaymentStrategy.java
    ├── model
    ├── client
    └── util

结构说明与优势

  1. model(根包下):包含了系统最核心、与所有银行支付流程相关的领域模型。将其置于顶层,使得strategyicbcccb等所有子模块都能无歧义、直接地引用这些公共请求/响应对象,体现了它们在领域中的中心地位。

  2. common:现在专注于存放支撑系统运行的、与技术实现相关的公共部分。
    common 包没有再分exceptionconstantenums等子包,结构更加扁平)

  3. strategy:策略模式的核心定义(接口、路由器、工厂)保持不变,它们依赖并使用顶层的model

  4. icbc / ccb:各家银行的具体实现。其内部的model子包用于定义该银行特有的请求/响应对象;client用于封装与该银行API的通信;util存放该银行特有的工具(如签名算法)。

此结构结构简洁、清晰。“清晰”指的是————包的职责更加清晰:

  • 顶层model:定义做什么(业务领域)。
  • common:定义如何支撑(技术基础设施)。
  • strategy:定义如何组织(设计模式与流程)。
  • 银行子包:定义如何实现(具体银行的业务逻辑)。

连AI也点评:这是一个兼顾了领域驱动设计(DDD)与清晰技术分层的包结构,具有良好的可读性和可维护性。

🎯 总结

通过策略模式,我们成功地将不同银行的支付逻辑解耦。正如本文的示例代码中:

  • 工商银行:使用特定的签名算法和通信方式
  • 建设银行:使用特殊的加密方式和请求头
  • 农业银行:需要SSL双向认证

当需要对接新的银行或三方支付(如支付宝)时,只需要:

  1. 创建 AlipayPaymentStrategy 实现 BankPaymentStrategy
  2. 实现该银行的特定逻辑
  3. 自动被Spring扫描并注册到策略工厂

这样的设计使得系统具备了良好的扩展性和维护性,完全符合开闭原则。

posted on 2026-03-01 01:21  buguge  阅读(0)  评论(0)    收藏  举报