反射加载SDK完成统一调用

news2024/11/19 19:23:07

文章目录

  • 1、需求背景
  • 2、接口+抽象类+具体实现类
  • 3、疑问
  • 4、存在的问题
  • 5、通过反射加载SDK并完成调用
  • 5、补充:关于业务网关
  • 7、补充:关于SDK的开发

关键点:

  • 接口+抽象类(半抽象半实现)+具体实现类
  • 业务网关
  • 反射加载SDK,完成统一调用

在这里插入图片描述

半路接手一个需求,需要从自己系统出发,经过业务网关的统一校验和转发,来请求第三方供应商系统的接口,整理下看同事代码学到的一点思路。

1、需求背景

第三方供应商需要上架自己的产品到公司的交易平台,但用户使用产品时,最后一步请求的自然是供应商自己的服务器资源和API。关于这个需求的实现思路,大致是在交易平台需要做接口有效性校验、服务实例有效性校验等,以及消费数据记录落库,最后转发到供应商接口去请求资源(既然是请求别人的系统,那就涉及到怎么通过人家的鉴权系统)。

@PostMapping("/data/{platform}/{apiId}")
public Object redirect(@PathVariable String platform, @PathVariable String apiId,
                       @RequestBody Map<String, Object> parameterMap, HttpServletRequest request) {
    //直接把API的ID放进请求参数里,后面用完了,再调三方API时去掉就行
    parameterMap.put("apiId", apiId);
    return redirectHandler.redirect(platform, ServletUtils.getHeaders(request), parameterMap);
}

这里给访问所有三方系统接口一个统一的入口,做为业务网关(后面展开说),接口传参中:

  • paltform:确定是哪个第三方系统
  • apiID:用来标识想请求第三方系统哪个的API接口,通过这个ID,可以在库里查到API的路径、三方系统的host、密钥、以及后面会提到的SDK的存储路径、SDK里的核心方法名等信息
  • parameterMap:用户传入的请求参数
  • request:Http请求对象

其中用工具类获取下HTTP请求的全部请求头信息存入Map。

public class ServletUtils{
	
	/**
     * 获取Http请求的请求头信息
     */
	public static Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        if (enumeration != null) {
            while (enumeration.hasMoreElements()) {
                String key = enumeration.nextElement();
                String value = request.getHeader(key);
                map.put(key, value);
            }
        }
        return map;
    }
 
}

2、接口+抽象类+具体实现类

既然需要对接很多第三方供应商系统,去调用第三方系统的API,那就考虑定义一个接口,里面抽象出一个做鉴权、转发的方法,对接不同的供应商系统时,去实现这个接口,然后走不同的实现。

public interface ApiRedirectHandler {

    /**
     * @param headerMap 请求头参数Map
     * @param paramMap 对第三方接口的请求参数
     * @return 返回第三方接用调用的结果
     */
    Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap);


}

前面提到,在交易平台要做一些校验和消费记录落库的操作,这些是对接所有三方系统的公共步骤,而后面请求第三方系统接口肯定要做的鉴权认证以及转发或者调用,则属于各个三方系统的定制化行为(因为一个系统有一个系统的认证方式,A系统用APP密钥、B系统可能用sign验签)。因此,考虑在接口下面垫一个抽象类,抽象类中,实现接口中的转发方法,里面做校验、记录落库等操作,同时调用本抽象类自己的抽象request方法(这个方法里做第三方系统的定制化的认证和转发或调用)。这样,对接不同的三方系统,只需就继承这个抽象类,实现里面的request方法,做自己的认证和转发即可。

总结:全抽象的接口,过渡到半抽象的抽象类,抽象类中实现接口的抽象方法时,方法体中写一部分公共逻辑 + 调用本抽象类自己的一个抽象方法B,这个抽象方法B就给以后的普通类去继承和重写。

@Slf4j
public abstract class AbstractRedirectHandler implements ApiRedirectHandler {

    //抽象类中实现接口的方法
    @Override
    public Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap) {

        //todo: 1.请求有效性验证

       //从请求参数paramMap中拿到你要调用APIId,然后查到的三方系统接口的路径、host等信息
       ApiInfo  apiDetailVo = queryApiInfo(paramMap);
       //API的ID用完了,它不是三方系统接口需要的请求参数,移除
       paramMap.remove("apiId"); 

        //todo: 2.服务实例有效性验证
        
        //request中去写不同三方系统的鉴权、转发或调用逻辑
        val responseData = request(headerMap, paramMap, apiDetailVo);

        //todo: 3.记录消费记录
   		
   		//返回第三方接口的响应结果
        return responseData;
    }

    /**
     * API转发请求,对接时,针对不同的三方系统去定制化实现
     *
     * @param headerMap 头信息
     * @param paramMap  请求参数
     * @Param apiDetailVo 接口信息,如接口路径、服务器host
     * @return 返回第三方接用调用的结果
     */
    protected abstract Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo);



}

比如现在对接001号系统:按它们系统支持的方式做认证,比如header中添加APP密钥,然后组装请求URL成一个HttpRequest对象,发送Http请求即可完成对三方系统API的调用。

public class System001RedirectHandler extends AbstractRedirectHandler {

    @Override
    public Object request(Map<String, String> headerParam, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {
        //拿到三方系统的服务器HOST以及接口路径
        String url = apiDetailVo.getHost() + apiDetailVo.getPath();
        //拿到三方系统接口的请求方式,POST还是GET...
        val method = Method.valueOf(apiDetailVo.getRequestMethod());
        //使用Hutool工具类的HTTP请求对象,方便后面调用现成的方法来发送HTTP请求
        HttpRequest request = null;
       //如果是GET
        if (method.equals(Method.GET)) {
            String headerBody = "";
            StringBuffer body = new StringBuffer();
            StringBuffer param = new StringBuffer();
            for (String key : paramMap.keySet()) {
                body.append(key).append("=").append(paramMap.get(key)).append("&");
                param.append(key).append("=").append(URLEncoder.encode((String) paramMap.get(key), StandardCharsets.UTF_8)).append("&");
            }
            //拼接出一个GET请求的完整路径
            if (param.length() > 0) {
                headerBody = body.substring(0, body.length() - 1);
                url = url + "?" + param.substring(0, param.length() - 1);
            }
            //创建request请求对象
            request = HttpUtil.createGet(url);
            //请求头中加入001系统的认证秘钥,以便通过认证
            request.addHeaders(getHeader(headerBody, apiDetailVo.getAppSecret()));
        } else {
            //POST请求
            request = HttpUtil.createPost(url).contentType("application/json");
            String body = JSON.toJSONString(paramMap);
            //组装请求头和请求体
            request.addHeaders(getHeader(body, apiDetailVo.getAppSecret())).body(body);
        }
        //库里存的API有要求超时时间
        if (apiDetailVo.getTimeout() > 0) {
            request.timeout(apiDetailVo.getTimeout());
        }
        //发送HTTP请求,拿到响应
        val httpResponse = request.execute();
        return JSON.parseObject(httpResponse.body());
    }

3、疑问

给所有三方系统接口的调用一个统一的请求入口,怎么实现根据传入的第三方系统类型platformType,来选择不同的实现类对象:考虑把转发接口ApiRedirectHandler的所有实现类放进一个List,遍历去匹配传入的platformType,匹配,则找到了三方系统对应的处理器实现类。找不到,就给个默认的处理器实现类。

@RequiredArgsConstructor
public class CompositeRedirectHandler {

    private ArrayList<ApiRedirectHandler> handlers = new ArrayList<>();

    public CompositeRedirectHandler(ArrayList<ApiRedirectHandler> redirectHandlerList) {
        handlers = redirectHandlerList;
    }

    public Object redirect(String platform, Map<String, String> headerMap, Map<String, Object> paramMap) {
        //给一个默认的通用执行器实现类对象
        ApiRedirectHandler execHandler = handlers.get(0);
       //根据平台信息匹配到ApiRedirectHandler接口的三方系统的实现类
        for (ApiRedirectHandler handler : handlers) {
            if (handler.isMatched(platform)) {
                execHandler = handler;
                break;
            }
        }
       //用实现类去调用转发方法  ==> 抽象类(包含抽象方法request) ==> 各个三方系统对抽象类的实现 ==> 完成三方系统API的请求
        return execHandler.redirect(headerMap, paramMap);
    }



}

上面的接口中注入这个CompositeRedirectHandler对象,调用它的redirect方法,即可全部串起来。

private final CompositeRedirectHandler redirectHandler;

@PostMapping("/data/{platform}/{apiId}")
public Object redirect(@PathVariable String platform, @PathVariable String apiId,
                       @RequestBody Map<String, Object> parameterMap, HttpServletRequest request) {
    parameterMap.put("apiId", apiId);
    return redirectHandler.redirect(platform, ServletUtils.getHeaders(request), parameterMap);
}

4、存在的问题

如此,以后每对接一个三方系统,就得开发一个新的实现类,去按照他们系统支持的认证方式来做认证以及转发或调用。相当繁琐,现在考虑把这个认证的事交给三方系统自己去完成,比如让他们开发一个SDK,SDK里他们按照自己系统支持的认证方式,做能通过鉴权的操作(到底是header里放密钥还是做验签,我就不再关心了),以及组装HTTP请求,而我们只需要load这个SDK里的内容,传入请求参数和路径,做一个调用即可。


@Slf4j
public class CommonRedirectHandler extends AbstractRedirectHandler {


     @Override
     public Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {
          //根据apiDetailInfo加载对应的SDK,完成调用
          //....
     }
}

如此,我就只需要一个通用的实现类CommonRedirectHandler就可以实现对所有三方系统的对接,这个通用类中也实现了上面的抽象类的request方法,request方法中只需load SDK里三方系统开发者写的方法,传入请求路径和请求参数即可完成三方系统接口的调用。

5、通过反射加载SDK并完成调用

现在问题成了如何加载SDK,完成调用。 ⇒ 通过反射拿到核心类的对象,以及负责认证和转发请求的核心方法,最后完成调用即可。这里的反射直接用hutool这个强大的第三方依赖库。

<!--引入hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.9</version>
</dependency>

加载SDK的示意代码:

@Slf4j
public class CommonRedirectHandler extends AbstractRedirectHandler {


    /**
     * 动态加载sdk,调用里面已经完成鉴权和转发的方法,以实现转发请求
     *
     * @param headerMap 头信息
     * @param paramMap  请求参数
     * @return 三方系统接口的返回数据
     */
    @Override
    public Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {
    	//SDK的路径、类名、核心方法名
        val jarFilePath = apiDetailVo.getSdkJarFilePath();   
        val classFullName = apiDetailVo.getClassFullName();
        val invokeMethodName = apiDetailVo.getInvokeMethodName();
        val httpMethod = apiDetailVo.getRequestMethod().toUpperCase();
        //拼接完整的三方系统接口的URL
        String apiUrl = apiDetailVo.getHost() + apiDetailVo.getPath();

        log.info("file = {}", new File(jarFilePath));
        //hutool工具类加载SDK成class对象
        Class<?> clazz = ClassLoaderUtil.loadClass(new File(jarFilePath), classFullName);
		//反射拿到构造方法对象
        final val constructors = ReflectUtil.getConstructor(clazz);
        Object instance = null;
        try {
        	//SDK核心类的对象
            instance = constructors.newInstance();
            final val requestMethod = ReflectUtil.getMethodByName(clazz, invokeMethodName);
            //调用
            return requestMethod.invoke(instance, apiUrl, httpMethod, headerMap, paramMap);
        } catch (InstantiationException e) {
            log.error(e.getMessage());
            throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage());
            throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);
        } catch (InvocationTargetException e) {
            log.error(e.getCause().getMessage());
            throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);
        }
    }
}

5、补充:关于业务网关

本需求里,给请求第三方系统接口资源提供了一个统一的API入口,比如:

@POSTMapping(/data/{platformType}/{API_ID})
public Object redirect(@PathVariable String platformType, @PathVariable String API_ID, 
					   @RequestBody Map<String, Object> requestParam, HttpServletRequset requset){

	//.....
}

有了这个统一入口,请求三方系统资源就都从这个接口过,前面说的各种合法性、有效性校验、记录落库、转发等就可以在这里完成了,由此可见,其虽然不比常规的Gateway服务,比如SpringCloudGateway,但干的活儿是类似的,即校验和转发(路由),因此,称业务网关。

思路:给所有三方系统的api调用提供一个统一的入口(Api)

7、补充:关于SDK的开发

SDK,Software Development Kit,即软件开发工具包。简单说就是造轮子,实现一个小功能,别人引入,就能使用。往大了说,如Java开发工具包JDK,使用import引入相关的包:

import java.util.*;

往小了说,如文件上传的SDK,其他系统引入后就可用。关于SDK的开发,需要注意:

  • 易用性:提供统一调用,用户不用关心内部实现的细节,只需知道调谁、传什么、能返回什么即可
//常见方式1.直接调用
FileManage.upload(String filePath);

//常见方式2.需要new对象
FileManage fileManage = new FileManage();
fileManage.upload(String filePath);
  • 轻量依赖:尽量减少SDK本身对其他类库的依赖,以减少用户项目中的已有依赖和SDK依赖的冲突
  • 结构清晰:如maven项目下,service包下编写业务逻辑、constant包下存放常量、utils包下放工具类
  • 见名知意:不用看说明文档也知道这个方法是干啥用的
  • 可扩展:提供接口或者抽象类对外,支持用户自己继承和按需写实现类,如密码相关SDK,做加密解密,起名PasswordHandler,其加密方法encode需要传入一个密码,一个加密器,这个加密器就可以提供成接口,用户可通过实现这个接口来自定义加密方式。
//加密器对象:按照非对称算法加密
Encoder encoder = new SignEncoder();
String password = PasswordHandler.encode("daihao9527", encoder);
//用户自己实现加密器接口
public MyEncoder implements Encoder {
	
	@Override
	void doEncode(){
		//...用户自己写,如采用时间戳、或自定义的MD5工具类
		Calendar calendar = Calendar.getInstance();
		Long timestamp = calendar.getTime.getTime();
		String sign = MD5Utils.getMD5Str(timestamp + secretKey);
		//..
	}
}
//此时,用户可以自己指定加密器
String password = PasswordHandler.encode("daihao9527", new MyEncoder());

SDK中的内容一般包括:

  • 功能模块:实现功能
  • API:SDK的门面,调用和使用功能的入口
  • 文档:附相关使用说明和指引
  • Demo:使用示例,运行Demo,直观体验SDK功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1294472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++新经典模板与泛型编程:策略技术中的算法策略

策略技术中的算法策略 在之前博客中funcsum()函数模板中&#xff0c;实现了对数组元素的求和运算。求和在这里可以看作一种算法&#xff0c;扩展一下思路&#xff0c;对数组元素求差、求乘积、求最大值和最小值等&#xff0c;都可以看作算法。而当前的funcsum()函数模板中&…

H3.3K27M弥漫性中线胶质瘤的反义寡核苷酸治疗

今天给同学们分享一篇实验文章“Antisense oligonucleotide therapy for H3.3K27M diffuse midline glioma”&#xff0c;这篇文章发表在Sci Transl Med期刊上&#xff0c;影响因子为17.1。 结果解读&#xff1a; CRISPR-Cas9消耗H3.3K27M恢复了H3K27三甲基化&#xff0c;并延…

PHPstorm可选择版本的链接

PHPstorm可选择版本的链接 链接&#xff1a;https://www.jetbrains.com/phpstorm/download/other.html

class060 拓扑排序的扩展技巧【算法】

class060 拓扑排序的扩展技巧【算法】 算法讲解060【必备】拓扑排序的扩展技巧 2023-12-7 22:23:02 code1 P4017 最大食物链计数 // 最大食物链计数 // a -> b&#xff0c;代表a在食物链中被b捕食 // 给定一个有向无环图&#xff0c;返回 // 这个图中从最初级动物到最顶…

游戏被攻击怎么办

随着科技的进步和互联网的普及&#xff0c;游戏行业也正在经历前所未有的变革。玩家们不再满足于传统的线下游戏&#xff0c;而是转向了线上游戏。然而&#xff0c;随着游戏的线上化&#xff0c;游戏安全问题也日益凸显。游戏受到攻击是游戏开发者永远的痛点&#xff0c;谈“D“…

Linus:我休假的时候也会带着电脑,否则会感觉很无聊

目录 Linux 内核最新版本动态 关于成为内核维护者 代码好写&#xff0c;人际关系难处理 内核维护者老龄化 内核中 Rust 的使用 关于 AI 的看法 参考 12.5-12.6 日&#xff0c;Linux 基金会组织的开源峰会&#xff08;OSS&#xff0c;Open Source Summit&#xff09;在日…

Enterprise Architect 12版本使用教程

Enterprise Architect 12版本使用教程 1.下载安装Enterprise Architect 122.Enterprise Architect原始DDL模板配置及存在的问题1.DDL Column Definition原始模板&#xff08;没有default值&#xff1a;可忽略&#xff09;2.DDL Data Type原始模板&#xff08;timestamp等时间字…

Diffusion 公式推导

Diffusion&#xff1a;通过扩散和逆扩散过程生成图像的生成式模型 中已经对 diffusion 的原理进行了直观地梳理&#xff0c;本文对其中的数学推导进行讲解&#xff0c;还是基于 DDPM。 目录 一. 预备知识1. 重参数技巧2. 高斯分布的可加性3. 扩散递推式的由来 二. 扩散过程1. 背…

企业计算机服务器中了360勒索病毒如何解密,勒索病毒解密数据恢复

网络技术的不断应用与发展&#xff0c;为企业的生产运营提供了极大便利&#xff0c;但随之而来的网络安全威胁也不断增加。近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒攻击&#xff0c;导致企业的所有数据被加密&…

AtCoder ABC周赛2023 11/4 (Sat) E题题解

目录 原题截图&#xff1a; 原题翻译 题目大意&#xff1a; 主要思路&#xff1a; 代码&#xff1a; 原题截图&#xff1a; 原题翻译 题目大意&#xff1a; 给你一个数组&#xff0c;给你一个公式&#xff0c;让你选k个元素&#xff0c;用公式算出最终得分。 主要思路&am…

云数据库与自建数据库有什么不同?

「自购服务器搭建数据库服务」&#xff0c;涉及到云服务器和物理机服务器的选择。这两者之间存在一定的差别。首先&#xff0c;物理机服务器需要更多的部署及维护操作&#xff0c;而云服务器则通过虚拟化技术提供了更便捷的资源管理和弹性伸缩能力。 总体来说&#xff0c;部署在…

移动云荣获OpenInfra社区“算力基础设施技术突破奖”

近日&#xff0c;一年一度的 OpenInfra Days China在北京召开&#xff0c;大会汇聚了全球各大云厂商的技术专家&#xff0c;共同分享全球前沿基础设施技术的展望和实践经验。在本次大会上&#xff0c;移动云荣获OpenInfra社区颁发的“算力基础设施技术突破奖”&#xff0c;表明…

销售技巧培训之如何提升顾问式销售技巧

销售技巧培训之如何提升顾问式销售技巧 在销售行业中&#xff0c;传统的“推销”模式往往注重产品的特点和优点&#xff0c;而忽视了客户的实际需求和感受。然而&#xff0c;随着消费者意识的提高和市场竞争的加剧&#xff0c;这种“产品导向”的销售方式已经不再适用。取而代…

STM32的BKP与RTC简介

芯片的供电引脚 引脚表橙色的是芯片的供电引脚&#xff0c;其中VSS/VDD是芯片内部数字部分的供电&#xff0c;VSSA/VDDA是芯片内部模拟部分的供电&#xff0c;这4组以VDD开头的供电都是系统的主电源&#xff0c;正常使用时&#xff0c;全部都要接3.3V的电源上&#xff0c;VBAT是…

实现跨VLAN通信、以及RIP路由协议的配置

一、如下图片&#xff1a; 1. 按照拓扑图所示&#xff0c;将8台计算机分别配置到相应的VLAN中。&#xff08;20分&#xff09; 2. 配置实现同一VLAN中的计算机可以通信。&#xff08;22分&#xff09; 3. 配置实现PC1,PC2,PC3,PC4可以互相通信&#xff0c;PC5,PC6,PC7,PC8可以互…

JavaSE基础50题:19. 递归求斐波那契数列的第N项。

概述 用递归求斐波那契数列的第N项。 斐波那契数列&#xff1a; 1 1 2 3 5 8 …… f(n) f(n-1) f(n-2) 代码 public class P19 {public static int fibnacio(int n) {if (n 1 || n 2) {return 1;}int tmp fibnacio(n-1) fibnacio(n-2);return tmp;}public static void…

大数据技术4:Lambda和Kappa架构区别

前言&#xff1a;在大数据处理领域&#xff0c;两种突出的数据架构已成为处理大量数据的流行选择&#xff1a;Lambda 架构和 Kappa 架构。这些架构为实时处理和批处理提供了强大的技术解决方案&#xff0c;使组织能够从其数据中获得有价值的见解。随着互联网时代来临&#xff0…

No suitable driver found for jdbc:mysql://localhost:3306(2023/12/7更新)

有两种情况&#xff1a; 压根没安装下载了但没设为库或方法不对 大多数为第一种情况&#xff1a; 一. 下载jdbc 打开网址选择一个版本进行下载 https://nowjava.com/jar/version/mysql/mysql-connector-java.html 二.安装jdbc 在项目里建一个lib文件夹 在把之前下载的jar文…

2017下半年软工(桥接模式)

题目——桥接模式&#xff08;抽象调用实现部分&#xff09; package org.example.桥接模式;/*** 桥接模式的核心思想是将抽象部分与它的实现部分分离&#xff0c;使它们可以独立变化&#xff0c;就是说你在实现部分&#xff1a;WinImp、LinuxImp基础上还能加上RedHatImp&#…