前段時間對接了微信支付,於是乎,從網上找了一下別人寫過的一頓copy后,修修改改終於實現完成了。
本以為萬事大吉, 但是項目經理review代碼時候,發現我寫的支付功能和系統業務功能高度耦合, 攪和在一起結果就扣了我一部分績效(mmp…) 。
為了避免以后扣績效,所以決定研究一下,怎么設計支付接口比較合理。末尾附上 git傳送門代碼
1. if 編碼方式
支付渠道暫時雖然只有微信,但是保不齊后面再加支付寶,銀聯啥的。到時候,代碼就會像這樣
if (payType.equals ("微信")) { //dosomething }else if (payType.equals ("支付寶")) { //dosomething }else if(payType.equals ("銀聯")) { //dosomething }
每多一個支付渠道,改動的地方包括:支付接口、支付配置、退款、統計業務。
2.聚合支付
萬能的百度上搜索了一下對接多個系統的方案(參考末尾的鏈接),其中聚合支付的方案比較合理。雖然可能用不到其中一部分的功能,比如結算功能。
1) 什么是聚合支付呢?
說白了就是一個項目接入了多個支付渠道,而且能夠使用任意一個渠道進行支付、退款等操作,而且任何渠道之間沒有任何關系,彼此不會互相干擾。
2) 簡單梳理一下聚合支付的業務
•需要對接多個支付渠道
• 所有的支付能夠兼容任意渠道
• 所有的退款能夠兼容任何渠道
• 任何渠道都能需要獨立進行配置
• 任何渠道都有統計功能
• 渠道之間能夠無縫進行切換(比如某個渠道奔潰了,能夠切換到其他渠道)
如果想滿足上面的功能,又不影響原有的業務的情況下,就需要將原有的支付模塊獨立抽離開來,單獨作為一個服務,也就是聚合支付,凡是項目里面的任何支付、退款、查詢、統計等都要通過聚合支付來處理。
3) 如何設計
設計模式是面試時候經常問的,那么,大膽地使用合理的設計模式對功能進行設計吧!
工廠模式: 每個支付渠道可以看成一個工廠
適配器模式: 不同的支付渠道使用的API,參數或者返回結果都可能不一樣
策略模式: 根據支付類型創建對應的支付通道
3.工廠模式
1) 創建一個支付的統一接口 , 這里列舉幾個接口
/** * 支付接口, 所有支付類的接口,系統所有支付功能類都需要實現它; */ public interface Pay { /** * 下單 */ DoOrderVo doOrder(DoOrderSo so); /** * 支付或者退款通知 */ PayNotifyVo payNotify(PayNotifySo so); /** * 查詢退款 */ QueryRefundVo queryRefund(QueryRefundSo so); }
2) 支付方式枚舉類
/** * 支付類型枚舉類 * <p> * 每增加一種支付渠道,需要同時在工廠類{@link PayFactory}里面配置 * </p> */ public enum PayWayEnum { /** * WX_APP */ WX_APP("WX_APP"), /** * WX_NATIVE */ WX_NATIVE("WX_NATIVE"), /** * ALI_APP */ ALI_APP("ALI_APP"), /** * ALI_WEB */ ALI_WEB("ALI_WEB"); PayWayEnum(String key) { } }
3) 實現類
支付渠道有微信,支付寶,銀聯或者第三方支付等, 而微信又有Native支付,native支付,手機支付等方式....
使用工廠方式可以根據支付方式枚舉出來具體實現, 結構如下
圖一 支付接口
4) 工廠獲取實例對象 (又要用if...或者switch....,不過后面再優化....)
@Service public class PayFactory { //根據類型獲取結果處理的實現類 public Pay getPayImpl(PayWayEnum payWayEnum) { Assert.notNull(payWayEnum, "付款渠道不能為空"); if (payWayEnum.equals(PayWayEnum.WX_APP)) { return new WxPayAppImpl(); } else if (payWayEnum.equals(PayWayEnum.WX_NATIVE)) { return new WxPayNativeImpl(); } else if (payWayEnum.equals(PayWayEnum.ALI_APP)) { return new AliPayAppImpl(); } else if (payWayEnum.equals(PayWayEnum.ALI_WEB)) { return new AliPayWebImpl(); } else { return null; } } }
4.適配器模式
1) 多個實現Pay接口問題
圖一里面,通過觀察發現:
①4個實現類都統一實現了pay的接口,如果再在pay接口中增加一個接口void doAction(),那么4個接口都得重新加上
②微信支付的API有些是通用的,比如統一下單,簽名和驗簽,沒必要在每個實現類里面都加上
2) 接口適配器模式 (缺省適配器模式)
適用場景: 當不需要全部實現接口提供的方法時,可先設計一個抽象類實現接口,並為該接口中每個方法提供一個默認實現(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求。
微信支付抽象類:
/** * 微信支付底層抽象類 * <p>為對接第三方支付接口的支付抽象類,需要實現第三方支付接口的所有API交互,為支付功能類提供功能方法</p> * <p>每一種支付方法,都可以繼承該抽象類,並擁有自己的獨立的支付流程,</p> */ public abstract class WxPay implements Pay { //=============================下面是支付的業務功能接口================== @Override public WxDoOrderVo doOrder(DoOrderSo so) { //子類實現 return null; } @Override public PayNotifyVo payNotify(PayNotifySo so) { //子類實現 return null; } @Override public QueryRefundVo queryRefund(QueryRefundSo so) { //子類實現 return null; } //=============================下面是微信支付的基礎API和相關方法================== /** * 統一下單接口 * * @param so */ public WxUnifiedOrderVo unifiedOrder(WxUnifiedOrderSo so) { System.out.println("WxPay->unifiedOrder :" + so.toString()); /** * 這里調用微信支付API 發送下單請求,返回二維碼鏈接等信息 , */ // 創建簽名 createSign(so.toString()); //發送請求 String resultXml = PaymenUtils.doPost("www.weixinpay/unifiedOrder", so.toString()); //解析結果成實體,並返回 WxUnifiedOrderVo wxUnifiedOrderVo = PaymenUtils.parseWxUnifiedOrderResult(resultXml); return wxUnifiedOrderVo; } /** * 生成簽名 */ public void createSign(String params) { System.out.println("WxPay->createSign :" + params); } /** * 驗證簽名 */ public void checkSign(String params) { System.out.println("WxPay->checkSign :" + params); } }
微信Native支付方式實現類 (假如我只想使用native下單,其他功能不需要,只需要重寫一下doOrder方法,其他的不需要重寫)
@Service public class WxPayNativeImpl extends WxPay { @Override public WxDoOrderVo doOrder(DoOrderSo so) { System.out.println("------微信-APP方式-------"); //調用統一下單邏輯 WxUnifiedOrderSo unifiedOrderSo = new WxUnifiedOrderSo(); WxUnifiedOrderVo unifiedOrderVo = super.unifiedOrder(unifiedOrderSo); WxNativeDoOrderVo wxNativeDoOrderVo = new WxNativeDoOrderVo(); wxNativeDoOrderVo.setNativeFlag("------nativeflag------"); wxNativeDoOrderVo.setWxUnifiedOrderVo(unifiedOrderVo); return wxNativeDoOrderVo; } }
接口設計如下圖
5.策略模式
1) 策略模式的定義
策略模式是對算法的包裝,把使用算法的責任和算法本身分隔開,委派給不同的對象管理。策略模式通常把一系列的算法包裝到一系列的策略類里面,作為一個抽象策略類的子類。
2) PayFactory 工廠優化
前面工廠類,是根據if判斷各種支付類型來new xxxPayImpl() 實例創建對象,
根據類型來實例化不同的對象,可以看做是多種實現策略,可以使用策略模式來優化一下;
3) 建立支付方式->實現類的對應關系
/** * 具體支付方式的配置 * key 表示支付方式, * value 表示支付具體實現類,** 注意這里類名小寫 */ public static final Map<PayWayEnum, String> PAY_MAP = new HashMap<>(8); static { PAY_MAP.put(PayWayEnum.WX_APP, "wxPayAppImpl"); PAY_MAP.put(PayWayEnum.WX_NATIVE, "wxPayNativeImpl"); PAY_MAP.put(PayWayEnum.ALI_APP, "aliPayAppImpl"); PAY_MAP.put(PayWayEnum.ALI_WEB, "aliPayWebImpl"); }
4) Spring實例化bean(而不是手動new xxx)
spring工具類
/** * 直接通過Spring 上下文獲取SpringBean,用於多線程環境 */ @Component public class SpringContextUtil implements ApplicationContextAware { // Spring應用上下文環境 private static ApplicationContext applicationContext; /** * 實現ApplicationContextAware接口的回調方法。設置上下文環境 */ @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 獲取對象 * * @param name * @return Object * @throws BeansException */ public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } public static void main(String[] args) { //具體使用: // AliPay aliPayImpl =(AliPay) SpringContextUtil.getBean("aliPayImpl"); // aliPayImpl.pay(); } }
根據類型獲取支付渠道的實現類
//根據類型獲取結果處理的實現類 public Pay getPay(PayWayEnum payWayEnum) { Assert.notNull(payWayEnum, "付款渠道不能為空"); return (Pay) SpringContextUtil.getBean(PAY_AFTER_MAP.get(payWayEnum)); }
6.Controller調用
1) 請求接口
@Autowired
private PayFactory payFactory;
@GetMapping("/hellopay/{typeEnum}") public String hello(@PathVariable("typeEnum") String typeEnumStr) { PayWayEnum typeEnum = PayWayEnum.valueOf(typeEnumStr); //由工廠獲取具體的pay實現類 Pay pay = payFactory.getPay(typeEnum); System.out.println("pay:" + pay); DoOrderSo doOrderSo = new DoOrderSo(); doOrderSo.setOrderNo("下單的單號OrderNo_00001"); doOrderSo.setTradeNo("下單的外部單號TradeNo_111111"); //獲取處理結果,這里實際轉換成了具體的結果實現類 DoOrderVo doOrderVo = pay.doOrder(doOrderSo); // 這里其實是具體的vo System.out.println("調用doOrder返回結果:doOrderVo :" + doOrderVo); return null; }
說明: 這里的xxxSo 表示參數, xxxVo表示返回值, (可以自行定義), So和So之間存在父子關系,Vo和Vo之間也存在父子關系,
這樣設計主要是: 方便支付的API處理邏輯有一個統一的返回,然后再交給系統進行DB等業務處理~
2) 測試請求
http://localhost:8088/demo/testpay/hellopay/WX_NATIVE
http://localhost:8088/demo/testpay/hellopay/ZFB_WEB
請求會看到不同的效果,(ZFB_WEB的實現類需要按照上面那樣寫一點邏輯就可以看到效果)
7.支付結果后處理
DoOrderVo 是調用支付的API后,統一處理的實體,我們需要根據不同的類型,轉發到不同的后處理service的具體業務實現類中,
主要是在系統中記錄一些DB信息和訂單等信息,仿照上面的接口設計,
//工廠獲取支付后處理的實現類 PayAfter payAfter = payFactory.getPayAfter(typeEnum); System.out.println("payAfter:" + payAfter); payAfter.doOrderAfter(doOrderVo);
PayAfter是后處理統一接口,doOrderAfter是doOrder支付接口的后處理邏輯;
8.調用效果
請求:
http://localhost:8088/demo/testpay/hellopay/WX_NATIVE
http://localhost:8088/demo/testpay/hellopay/ALI_WEB
結果:
git傳送門代碼: https://github.com/ColoZhu/paydemo
參考:
https://blog.csdn.net/think2017/article/details/79820786?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param ,
https://www.cnblogs.com/lyc94620/p/13055116.html ,