惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
C
Check Point Blog
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
SecWiki News
SecWiki News
有赞技术团队
有赞技术团队
Latest news
Latest news
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
腾讯CDC
P
Proofpoint News Feed
L
LINUX DO - 热门话题
C
Cisco Blogs
P
Palo Alto Networks Blog
Vercel News
Vercel News
P
Privacy International News Feed
爱范儿
爱范儿
Scott Helme
Scott Helme
L
Lohrmann on Cybersecurity
MyScale Blog
MyScale Blog
K
Kaspersky official blog
B
Blog RSS Feed
美团技术团队
Microsoft Security Blog
Microsoft Security Blog
O
OpenAI News
博客园 - 叶小钗
量子位
T
Tenable Blog
C
Cybersecurity and Infrastructure Security Agency CISA
J
Java Code Geeks
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
F
Fortinet All Blogs
N
News | PayPal Newsroom
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
N
News and Events Feed by Topic
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI

GentleLucky

周报 Vol.34 - 琐碎半年 · GentleLucky 2025 - 冷不丁梆梆又一年 · GentleLucky 周报 Vol.33 - 大连威海游 · GentleLucky 周报 Vol.32 - 出差上海 · GentleLucky 周报 Vol.31 - PP 吞针 · GentleLucky 忙 忙 忙 · GentleLucky 周报 Vol.29 - 浪浪山小妖怪 · GentleLucky 周报 Vol.28 - 祝你早安、午安、晚安 · GentleLucky 周报 Vol.27 - 消失的成就感 · GentleLucky 周报 Vol.26 - 琅琊山 · GentleLucky 周报 Vol.25 - 缺失的驱动力 · GentleLucky 周报 Vol.24 - 断工作 · GentleLucky 周报 Vol.23 - 25年第一场篮球 · GentleLucky 周报 Vol.22 - 归心似箭的心态 · GentleLucky 周报 Vol.21 - 固执己见是一座大山 · GentleLucky 周报 Vol.20 - 信封预算法 · GentleLucky 数字日记 - 自动化工作流 · GentleLucky 周报 Vol.19 - 消失的年味儿 · GentleLucky 周报 Vol.18 - 慢慢远去 · GentleLucky 个人记账管理系统(MoneyWiz) · GentleLucky 周报 Vol.17 - 2024 年终述职 · GentleLucky 周报 Vol.16 - 登黄山 · GentleLucky 2024 - 感知世界 · GentleLucky 周报 Vol.15 - 家庭网络折腾记录 · GentleLucky 周报 Vol.14 - W2X产品构建(一) · GentleLucky 周报 Vol.13 - 如何看待初创公司 · GentleLucky 周报 Vol.12 - 新徽园 · GentleLucky 周报 Vol.11 - 《好东西》 · GentleLucky 周报 Vol.10 - 锐锐脱口秀 · GentleLucky 周报 Vol.09 - 合肥玩石银河左岸音乐节 · GentleLucky 周报 Vol.08 - 这感觉真的很奇妙吗? · GentleLucky 周报 Vol.07 - 全家福 · GentleLucky 周报 Vol.06 - 爱自己 · GentleLucky 周报 Vol.05 - 德宝病了 · GentleLucky How to write · GentleLucky 信息输入: Follow 初体验 · GentleLucky 使用 Toggl Track 做时间追踪 · GentleLucky 周报 Vol.02 - Notion 删除所有俄罗斯用户数据的思考 · GentleLucky 周报 Vol.01 - 寻找心流 · GentleLucky PP之噶🥚日记 · GentleLucky
从对接银行出发,如何看待实现方式? · GentleLucky
作者 gentlelucky 后端 & Java研发工程师。 在 GitHub 关注我。在我的 Telegram 频道了解更多。 · 2024-11-29 · via GentleLucky

前言

由于业务需要,近期看了一下平台应用如何调用「招商银行(CMB)」和「平安银行(PAB)」接口进行业务交互。于是在思考,针对于这样的业务场景,是否需要考虑 设计模式、是否需要考虑 后期维护 、是否考虑 易扩展性 等。

接下来,我将从对接银行接口出发,列举出三套实现方案。从 基础实现 -> 接口 -> 高级抽象 维度来实现。

基础实现

所谓的「基础实现」就是过程式编码,满足业务功能,不考虑任何扩展、设计模式等因素;主打一个能用就行。

  • CmbService:招商银行的接口逻辑

    // 招商银行接口
    public class CmbService {
      // 获取子账号余额
      public CmbSubAccountBalance getSubAccountBalance(CmbSubAccountBalanceParam param) {
        // 请求招商银行
        String s = this.request(param);
        // 响应消息序列化成对象
        return JSONUtil.toBean(s, CmbSubAccountBalance.class);
      }
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    
    }
    
  • PabService:平安银行的接口逻辑

    // 平安银行接口
    public class PabService {
      // 获取子账号余额
      public PabSubAccountBalance getSubAccountBalance(PabSubAccountBalanceParam param) {
        // 请求平安银行
        String s = this.request(param);
        // 响应消息序列化成对象
        return JSONUtil.toBean(s, PabSubAccountBalance.class);
      }
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    
    }
    
  • BizService:业务层调用

    public class BizService {
      // 获取子账号余额
      public String getSubAccountBalance(AccountParam param) {
        // 判断银行类型
        if("CMB".equals(param.getBankType())) {
          // 请求参数转换成招行接口的入参
          CmbSubAccountBalanceParam cmbParam = new CmbSubAccountBalanceParam(param);
          // 请求招商银行获取子账户余额
          CmbSubAccountBalance accountBalance = cmbSubAccountBalance.getSubAccountBalance(cmbParam);
          return accountBalance.getBalance();
        }
        if("PAB".equals(param.getBankType())) {
          // 请求参数转换成平安接口的入参
          PabSubAccountBalanceParam pabParam = new PabSubAccountBalanceParam(param);
          // 请求平安银行获取子账户余额
          PabSubAccountBalance accountBalance = pabSubAccountBalance.getSubAccountBalance(pabParam);
          return accountBalance.getBalance();
        }    
      }
    }
    

上述代码,快速的实现了业务功能。很直观的把业务需求,转换成最直观的代码。

优点:

  1. 快速实现功能
  2. 代码易读,直观

缺点:

  1. 无法统一请求银行的共有方法
  2. 业务层调用时,都要写条件判断且参数需转换成特定银行的参数字段,导致代码冗余
  3. 新增一个其他银行通道时,需在每一个业务层增加判断,不易维护

接口

所谓的「接口」就是把公共的请求,抽象成一个接口。统一管理,不同的银行通道实现接口。再通过设计模式(策略模式、工厂模式)获取具体的实现。业务层调用工厂类获取具体接口实现即可。

  • BankTemplate:银行统一接口

    // 银行统一接口
    public interface BankTemplate {
      // 获取子账号余额
      SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param);
    }
    
  • CmbBankTemplate:招商银行接口实现

    // 招商银行接口实现
    public class CmbBankTemplate implements BankTemplate {
      // 获取子账号余额
      public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
        // 入参转换成招行请求参数字段
        Map<String, String> paramMap = this.buildMap(param);
        // 请求银行
        String response = this.request(paramMap);
        // 把 response 响应转换成统一的 SubAccountBalance 对象
        return this.buildSubAccountBalance(response);
      }
    
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    }
    
  • PabBankTemplate:平安银行接口实现

    // 平安银行接口实现
    public class PabBankTemplate implements BankTemplate {
      // 获取子账号余额
      public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
        // 入参转换成平安请求参数字段
        Map<String, String> paramMap = this.buildMap(param);
        // 请求银行
        String response = this.request(paramMap);
        // 把 response 响应转换成统一的 SubAccountBalance 对象
        return this.buildSubAccountBalance(response);
      }
    
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    }
    
  • BankTemplateFactory:BankTemplate工厂类

    public class BankTemplateFactory {
      public static BankTemplate getTemplate(String type) {
        // 招商银行
        if ("CMB".equals(type)) {
          return new CmbBankTemplate();
        }
        // 平安银行
        if ("PAB".equals(type)) {
          return new PabBankTemplate();
        }
      }
    }
    
  • BizService:业务层调用

    public class BizService {
      // 获取子账号余额
      public String getSubAccountBalance(SubAccountBalanceParam param) {
        // 根据银行类型获取具体BankTemplate实现
        BankTemplate bankTemplate = BankTemplateFactory.getTemplate(param.getBankType());
        // 请求银行获取子账户余额
        SubAccountBalance subAccountBalance = bankTemplate.getSubAccountBalance(param);
        return subAccountBalance.getBalance();
      }
    }
    

上述代码,统一了接口,方便了扩展和后期维护;

优点:

  1. 统一了请求银行的方法
  2. 业务层调用时,使用 BankTemplateFactory 工厂类,获取具体实现
  3. 新增一个其他银行通道时,需要实现 BankTemplate 接口,以及在 BankTemplateFactory 工厂类中加判断(可以优化成不需要手动加判断,不在此次谈论范围内)

第二套「接口」方案,已经解决了第一套「基础实现」方案中的不足的方面,满足了业务系统后续扩展和后续维护等实际需求。

高级抽象

所谓的「高级抽象」就是分析业务后,把业务模型抽象化。那么对接银行的业务如何抽象化呢?

通过分析发现,业务需求是请求银行接口获取相应的数据,那么抽象出来就是需要一个请求银行的接口( BankInterface )。

不同银行请求入参不同、加解密方式不同,所以需要有一个适配器去转换( BankInterfaceAdapter )。

两个银行都提供了「查询子账号余额」的接口,但请求的 URL 是不同的,为了简化URL设置,可以自定义注解( @BankApi(path = “/V1.0/bedl/SubAccountBalanceQuery”, method = HttpMethod.POST) )。

大致代码逻辑如下:

  • BankInterface:银行接口

    /**
     * 银行接口
     */
    public interface BankInterface {
    
        /**
         * 银行接口适配器
         * @return
         */
        BankInterfaceAdapter adapter();
    
        /**
         * 子账号余额查询
         * @param param
         * @return
         */
        JSON subAccountBalanceQuery(BankApiParam param);
    
    }
    
  • BankInterfacePab:平安银行接口

    /**
     * 平安银行接口
     */
    @Type(value = BankType.PAB)
    public interface BankInterfacePab extends BankInterface {
    
        @BankApi(path = "/V1.0/bedl/SubAccountBalanceQuery", method = HttpMethod.POST)
        @Override
        JSON subAccountBalanceQuery(BankApiParam param);
    }
    
  • BankInterfaceAdapter:银行适配器

    /**
     * 银行适配器
     */
    public interface BankInterfaceAdapter {
    
        /**
         * 构建请求参数
         * @param obj
         * @return
         */
        BankApiParam buildParam(Object obj);
    
        /**
         * 前置处理
         * @param param
         */
        void preProcessor(BankApiParam param);
    
        /**
         * 后置处理
         * @return
         */
        String postProcessor(String msg);
    
        /**
         * 发送请求
         * @param param
         * @return
         */
        String process(BankApiParam param);
    
        /**
         * 子账号余额适配
         * @param json
         * @return
         */
        SubAccountBalance subAccountBalanceQuery(JSON json);
    
    }
    
  • BankInterfaceAdapterPab:平安银行适配

    /**
     * 平安银行适配
     */
    public class BankInterfaceAdapterPab implements BankInterfaceAdapter {
    
        public final static BankInterfaceAdapterPab INSTANCE = new BankInterfaceAdapterPab();
    
        @Override
        public BankApiParam buildParam(Object obj) {
            return null;
        }
    
        @Override
        public void preProcessor(BankApiParam param) {
    
        }
    
        @Override
        public String postProcessor(String msg) {
            return null;
        }
    
        @Override
        public String process(BankApiParam param) {
            return null;
        }
    
        @Override
        public SubAccountBalance subAccountBalanceQuery(JSON json) {
            return null;
        }
    }
    
  • BankProxy:银行代理类

    /**
     * 银行代理类
     */
    public interface BankProxy {
    
        /**
         * 获取子账号流水
         * @param param
         * @return
         */
        SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param);
    }
    
  • BankProxyImpl:银行代理类实现

    /**
     * 银行代理类实现
     */
    public class BankProxyImpl implements BankProxy {
    
        private final BankInterface bankInterface;
        private final BankInterfaceAdapter bankInterfaceAdapter;
    
        public BankProxyImpl(BankInterface bankInterface) {
            this.bankInterface = bankInterface;
            this.bankInterfaceAdapter = bankInterface.adapter();
        }
    
        @Override
        public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
            BankApiParam bankApiParam = bankInterfaceAdapter.buildParam(param);
            JSON json = bankInterface.subAccountBalanceQuery(bankApiParam);
            return bankInterfaceAdapter.subAccountBalanceQuery(json);
        }
    }
    
  • RpcClientProxy:请求银行RPC,反射

    /**
     * 请求银行RPC,反射
     */
    public class RpcClientProxy implements InvocationHandler  {
    
        @Getter
        private final RpcClientProxyFactoryBean factory;
    
        public RpcClientProxy(RpcClientProxyFactoryBean factoryBean) {
            this.factory = factoryBean;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }
    
  • RpcClientProxyFactoryBean:RPC请求代理工厂

    /**
     * RPC请求代理工厂
     */
    public class RpcClientProxyFactoryBean implements FactoryBean<BankInterface> {
    
        private final Class<? extends BankInterface> serviceInterface;
    
        public RpcClientProxyFactoryBean(Class<? extends BankInterface> clz) {
            this.serviceInterface = clz;
        }
    
        @Override
        public BankInterface getObject() throws Exception {
            return (BankInterface) Proxy.newProxyInstance(this.serviceInterface.getClassLoader(),
                    new Class[]{this.serviceInterface},
                    new RpcClientProxy(this));
        }
    
        @Override
        public Class<?> getObjectType() {
            return this.serviceInterface;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    
  • BankInterfaceManager:统一银行接口管理

    /**
     * 统一银行接口管理
     */
    public class BankInterfaceManager {
    
        /**
         * 获取具体的银行代理类
         * @param bankType
         * @return
         */
        public BankProxy getBankProxy(BankType bankType) {
            BankInterface bankInterface = null;
            return new BankProxyImpl(bankInterface);
        }
    }
    

总结

这段时间,每晚入睡了,大脑中总在思考当前的业务应该如何抽象,如何设计。自己已经魔怔了!

其实我们写代码,本质是让现实世界的需求,在计算机世界中被理解和执行。如果只是这样,我可能不会要求自己的代码写的像 一样!

编码的最终形态,表达了对业务理解的抽象能力,也表达了对使用语音特性的理解(比如面向对象、面向接口、面向抽象)。有时候固执的要求自己写出所谓的 完美 代码,那只会让自己陷入痛苦之中。

在时间、成本、维护性等方面寻找平衡,放弃过渡思考、过渡设计。

相关文章