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

推荐订阅源

S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
A
Arctic Wolf
T
Tor Project blog
T
Threatpost
NISL@THU
NISL@THU
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Apple Machine Learning Research
Apple Machine Learning Research
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
K
Kaspersky official blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
小众软件
小众软件
Recorded Future
Recorded Future
B
Blog RSS Feed
H
Help Net Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
L
Lohrmann on Cybersecurity
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
爱范儿
爱范儿
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Hacker News: Front Page
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
N
News and Events Feed by Topic
云风的 BLOG
云风的 BLOG
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术

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);
        }
    }
    

总结

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

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

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

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

相关文章