37| 设计模式在多渠道支付里面的设计+编码实战 小滴课堂讲师 2024年09月18日 预计阅读 16 分钟 原文 ### 软件架构设计-设计模式的六大原则你知道多少 设计模式是站在设计原则的基础之上的,软件架构也一样,有必要对这些设计原则先做一下了解 软件设计开发原则 - 为了让的代码更好重用性,可读性,可靠性,可维护性 - 诞生出了很多软件设计的原则,这6大设计原则是我们要掌握的 - 将六大原则的英文首字母拼在一起就是SOLID(稳定的),所以也称之为SOLID原则 - 单一职责原则 - 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因 - 是实现**高内聚、低耦合**的指导方针 - 解释: - 高内聚 - 尽可能类的每个成员方法只完成一件事(最大限度的聚合) - 模块内部的代码, 相互之间的联系越强,内聚就越高, 模块的独立性就越好 - 低耦合: 减少类内部,一个成员方法调用另一个成员方法, 不要有牵一发动全身 - 开闭原则 - 对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果 - 里氏替换原则LSP - 任何基类可以出现的地方,子类一定可以出现 - 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象 - controller->service->dao - 依赖倒转原则 - 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体 - 高层模块不应该依赖低层模块,二者都应该依赖其抽象 - 接口隔离原则 - 客户端不应该依赖那些它不需要的接口 - 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度 - 迪米特法则 - 最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立 - 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及 - 通过引入一个合理的第三者来降低现有对象之间的耦合度 ### 设计模式最佳实践-第三方支付对接-工厂模式回顾 工厂模式介绍: - 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象 工厂模式有 3 种不同的实现方式 - 简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单 一 , 可扩展性相对较差; - 工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强; - 抽象工厂模式:基于上述两种模式的拓展,且支持细化产品 例子: - 需要购买一辆车,不用管车辆如何组装,且可以购买不同类型的比如轿车、SUV、跑车,直接去4s店购买就行(4s店就是工厂) - 工厂生产电脑,除了A品牌、还可以生产B、C、D品牌电脑 - 业务开发中,支付很常见,里面有统一下单和支付接口,具体的支付实现可以微信、支付宝、银行卡等  简单工厂模式 - 又称静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类 - 由于工厂方法是静态方法,可通过类名直接调用,而且只需要传入简单的参数即可 - 优点: - 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。 - 缺点 - 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背 - 即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果 - 将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不用模式 * 项目里面的应用  ### 设计模式最佳实践-第三方支付对接-策略模式回顾 策略模式(Strategy Pattern) - 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换 - 淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的, 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换 应用场景 - 老王计划外出旅游,选择骑自行车、坐汽车、飞机等,每一种旅行方式都是一个策略 - Java AWT中的LayoutManager,即布局管理器 - 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么可以使用策略模式 - 不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式来封装算法 - 对接第三方支付里面,微信支付、支付宝支付等都可以是一种策略 角色 - Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化 - Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性 - ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法  ### 多渠道支付对接-策略模式+工厂模式编码实战 * PayInfoVO类开发 ```java @Data @AllArgsConstructor @NoArgsConstructor public class PayInfoVO { /** * 订单号 */ private String outTradeNo; /** * 订单总金额 */ private BigDecimal payFee; /** * 支付类型 微信-支付宝-银行-其他 */ private String payType; /** * 端类型 APP/H5/PC */ private String clientType; /** * 标题 */ private String title; /** * 描述 */ private String description; /** * 订单支付超时时间,毫秒 */ private long orderPayTimeoutMills; } ``` * 策略接口开发 PayStrategy ```java public interface PayStrategy { /** * 下单 */ String unifiedorder(PayInfoVO payInfoVO); /** * 退款 */ default String refund(PayInfoVO payInfoVO){return "";} /** * 查询支付是否成功 */ default String queryPaySuccess(PayInfoVO payInfoVO){return "";} } ``` * 策略上下文 PayStrategyContext开发 ```java public class PayStrategyContext { private PayStrategy payStrategy; public PayStrategyContext(PayStrategy payStrategy){ this.payStrategy = payStrategy; } /** * 根据支付策略,调用不同的支付 */ public String executeUnifiedorder(PayInfoVO payInfoVO){ return this.payStrategy.unifiedorder(payInfoVO); } /** * 根据支付的策略,调用不同的查询订单支持状态 */ public String executeQueryPaySuccess(PayInfoVO payInfoVO){ return this.payStrategy.queryPaySuccess(payInfoVO); } } ``` * 具体支付策略开发 AlipayStrategy、WechatPayStrategy ```java @Slf4j @Service public class AlipayStrategy implements PayStrategy { @Autowired private PayUrlConfig payUrlConfig; @Override public String unifiedorder(PayInfoVO payInfoVO) { // todo } @Override public String refund(PayInfoVO payInfoVO) { return null; } @Override public String queryPaySuccess(PayInfoVO payInfoVO) { // todo } } ``` ```java @Slf4j @Service public class WechatPayStrategy implements PayStrategy { @Override public String unifiedorder(PayInfoVO payInfoVO) { return null; } @Override public String refund(PayInfoVO payInfoVO) { return null; } @Override public String queryPaySuccess(PayInfoVO payInfoVO) { return null; } } ``` * 简单工厂类开发 ```java @Component @Slf4j public class PayFactory { @Autowired private AlipayStrategy alipayStrategy; @Autowired private WechatPayStrategy wechatPayStrategy; /** * 创建支付,简单工程模式 * @param payInfoVO * @return */ public String pay(PayInfoVO payInfoVO){ String payType = payInfoVO.getPayType(); if(ProductOrderPayTypeEnum.ALIPAY.name().equalsIgnoreCase(payType)){ //支付宝支付 PayStrategyContext payStrategyContext = new PayStrategyContext(alipayStrategy); return payStrategyContext.executeUnifiedorder(payInfoVO); } else if(ProductOrderPayTypeEnum.WECHAT.name().equalsIgnoreCase(payType)){ //微信支付 暂未实现 PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy); return payStrategyContext.executeUnifiedorder(payInfoVO); } return ""; } /** * 查询订单支付状态 * * 支付成功返回非空,其他返回空 * * @param payInfoVO * @return */ public String queryPaySuccess(PayInfoVO payInfoVO){ String payType = payInfoVO.getPayType(); if(ProductOrderPayTypeEnum.ALIPAY.name().equalsIgnoreCase(payType)){ //支付宝支付 PayStrategyContext payStrategyContext = new PayStrategyContext(alipayStrategy); return payStrategyContext.executeQueryPaySuccess(payInfoVO); } else if(ProductOrderPayTypeEnum.WECHAT.name().equalsIgnoreCase(payType)){ //微信支付 暂未实现 PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy); return payStrategyContext.executeQueryPaySuccess(payInfoVO); } return ""; } } ``` ### 策略设计模式-支付宝支付下单策略编码实战 * 下单策略编码实战 * 二次支付订单超时设计考虑 ```java @Override public String unifiedorder(PayInfoVO payInfoVO) { HashMap content = new HashMap<>(); //商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复 content.put("out_trade_no", payInfoVO.getOutTradeNo()); content.put("product_code", "FAST_INSTANT_TRADE_PAY"); //订单总金额,单位为元,精确到小数点后两位 content.put("total_amount", payInfoVO.getPayFee().toString()); //商品标题/交易标题/订单标题/订单关键字等。 注意:不可使用特殊字符,如 /,=,& 等。 content.put("subject", payInfoVO.getTitle()); //商品描述,可空 content.put("body", payInfoVO.getDescription()); double timeout = Math.floor(payInfoVO.getOrderPayTimeoutMills()/(1000*60)); //前端也需要判断订单是否要关闭了, 如果要快要到期则不给二次支付 if(timeout<1){ throw new BizException(BizCodeEnum.PAY_ORDER_PAY_TIMEOUT); } // 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 // 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 content.put("timeout_express", Double.valueOf(timeout).intValue()+"m"); String clientType = payInfoVO.getClientType(); String form = ""; try{ if(clientType.equalsIgnoreCase(ClientType.H5.name())){ //H5手机网页支付 AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest(); request.setBizContent(JSON.toJSONString(content)); request.setNotifyUrl(payUrlConfig.getAlipayCallbackUrl()); request.setReturnUrl(payUrlConfig.getAlipaySuccessReturnUrl()); AlipayTradeWapPayResponse alipayResponse = AlipayConfig.getInstance().pageExecute(request); log.info("响应日志:alipayResponse={}",alipayResponse); if(alipayResponse.isSuccess()){ form = alipayResponse.getBody(); } else { log.error("支付宝构建H5表单失败:alipayResponse={},payInfo={}",alipayResponse,payInfoVO); } }else if(clientType.equalsIgnoreCase(ClientType.PC.name())){ //PC支付 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setBizContent(JSON.toJSONString(content)); request.setNotifyUrl(payUrlConfig.getAlipayCallbackUrl()); request.setReturnUrl(payUrlConfig.getAlipaySuccessReturnUrl()); AlipayTradePagePayResponse alipayResponse = AlipayConfig.getInstance().pageExecute(request); log.info("响应日志:alipayResponse={}",alipayResponse); if(alipayResponse.isSuccess()){ form = alipayResponse.getBody(); } else { log.error("支付宝构建PC表单失败:alipayResponse={},payInfo={}",alipayResponse,payInfoVO); } } }catch (AlipayApiException e){ log.error("支付宝构建表单异常:payInfo={},异常={}",payInfoVO,e); } return form; } ``` ### 策略设计模式-支付宝订单状态查询编码实战 * 订单状态查询策略编码实战 * 接口文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.query ```java /** * 查询订单状态 * 支付成功 返回非空 * 其他返回空 * * 未支付 * {"alipay_trade_query_response":{"code":"40004","msg":"Business Failed","sub_code":"ACQ.TRADE_NOT_EXIST","sub_msg":"交易不存在","buyer_pay_amount":"0.00","invoice_amount":"0.00","out_trade_no":"adbe8e8f-3b18-4c9e-b736-02c4c2e15eca","point_amount":"0.00","receipt_amount":"0.00"},"sign":"xxxxx"} * * 已经支付 * {"alipay_trade_query_response":{"code":"10000","msg":"Success","buyer_logon_id":"mqv***@sandbox.com","buyer_pay_amount":"0.00","buyer_user_id":"2088102176996700","buyer_user_type":"PRIVATE","invoice_amount":"0.00","out_trade_no":"adbe8e8f-3b18-4c9e-b736-02c4c2e15eca","point_amount":"0.00","receipt_amount":"0.00","send_pay_date":"2020-12-04 17:06:47","total_amount":"111.99","trade_no":"2020120422001496700501648498","trade_status":"TRADE_SUCCESS"},"sign":"xxxx"} * * @param payInfoVO * @return */ @Override public String queryPaySuccess(PayInfoVO payInfoVO) { AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); HashMap content = new HashMap<>(); //订单商户号,64位 content.put("out_trade_no", payInfoVO.getOutTradeNo()); request.setBizContent(JSON.toJSONString(content)); AlipayTradeQueryResponse response = null; try { response = AlipayConfig.getInstance().execute(request); log.info("支付宝订单查询响应:{}", response.getBody()); } catch (AlipayApiException e) { log.error("支付宝订单查询异常:{}", e); } if (response.isSuccess()) { log.info("支付宝订单状态查询成功:{}", payInfoVO); return response.getTradeStatus(); } else { log.info("支付宝订单状态查询失败:{}", payInfoVO); return ""; } } ```
评论区