一个申明式的Http接口对接框架,能以极快的方式成功对一个第三方Http接口的对接和经常使用,之后就像调用本中央法一样智能去动员Http恳求,不须要开发者去关注如何发送一个恳求,如何去传递Http恳求参数,以及如何对恳求结果启动处置和反序列化,这些框架都帮你逐一成功
就像性能 Spring的Controller 那样繁难,只不过相当于是反向性能而已
该框架更器重于如何坚持高内聚和可读性高的代码状况下与极速第三方渠道接口启动对接和集成,而非像传统编程式的Http恳求客户端(比如HttpClient、Okhttp)那样专一于如何去发送Http恳求,只管底层也是用的Okhttp去发送恳求。
与其说的是对接的Http接口,不如说是对接的第三方渠道,UniHttp可支持自定义接口渠道方HttpAPI注解以及一些自定义的对接和交互行为 ,为此裁减了发送和照应和反序列化一个Http恳求的各种生命周期钩子,开发者可自行去裁减成功。
<dependency><groupId>io.github.burukeyou</groupId><artifactId>uniapi-http</artifactId><version>0.0.4</version></dependency>
首先随意创立一个接口,而后在接口上标志@HttpApi注解,而后指定恳求的域名url, 而后就可以在方法上去性能对接哪个接口。
比如上方两个方法的性能则对接了以下两个接口
方法前往值定义成Http照应body对应的类型即可,自动会经常使用fastjson反序列化Http照应body的值为该类型对象。
@HttpApi(url = "http://localhost:8080")interface UserHttpApi {@GetHttpInterface("/getUser")BaseRsp<String> getUser(@QueryPar("name") String param,@HeaderPar("userId") Integer id);@PostHttpInterface("/addUser")BaseRsp<Add4DTO> addUser(@BodyJsonPar Add4DTO req);}
GETid
POST:{"id":1,"name":"jay"}
在spring的性能类上经常使用@UniAPIScan注解标志定义的@HttpAPI的包扫描门路,会智能为标志了@HttpApi接口生成代理对象并且注入到Spring容器中,之后只有要像经常使用Spring的其余bean一样,依赖注入经常使用即可
@UniAPIScan("com.xxx.demo.api")@SpringBootApplicationpublic class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class,args);}}
@Serviceclass UserAppService {@Autowiredprivate UserHttpApi userHttpApi;public void doSomething(){userHttpApi.getUser("jay",3);}}
用于标志接口上,该接口上的方法会被代理到对应的Http恳求接口,可指定恳求的域名,也可指定自定义的Http代理逻辑等等。
用于性能一个接口的参数,包括恳求方式、恳求门路、恳求头、恳求cookie、恳求查问参数等等
并且内置了以下恳求方式的@HttpInterface,不用再每次手动指定恳求方式
@PostHttpInterface(// 恳求门路path = "/getUser",// 恳求头headers = {"clientType:sys-app","userId:99"},// url查问参数params = {"name=周杰伦","age=1"},// url查问参数拼接字符串paramStr = "a=1&b=2&c=3&d=哈哈&e=%E7%89%9B%E9%80%BC",// cookie 字符串cookie = "name=1;sessionId=999")BaseRsp<String> getUser();
以下各种Par后缀的注解,关键用于方法参数上,用于指定在发送恳求时将参数值放到Http恳求体的哪局部上。
为了繁难形容,下文形容的个别值就是示意String,基本类型、基本类型的包装类型等类型.
繁难温习下Http协定报文
标志Http恳求url的查问参数
支持以下方法参数类型的标志: 个别值、个别值汇合、对象、Map
@PostHttpInterfaceBaseRsp<String> getUser(@QueryPar("id")String id,//个别值@QueryPar("ids") List<Integer> idsList, //个别值汇合@QueryPar User user,// 对象@QueryPar Map<String,Object> map); // Map
假设类型是个别值或许个别值汇合须要手动指定参数名,由于是当成单个查问参数传递
假设类型是对象或许Map是当成多个查问参数传递,字段名或许map的key名就是参数名,字段值或许map的value值就是参数值。
假设是对象,参数名自动是字段名,由于用的是fastjson序列化可以用@JSONField指定别名
标志Http恳求门路变量参数,仅支持标志个别值类型
@PostHttpInterface("/getUser/{userId}/detail")BaseRsp<String> getUser(@PathPar("userId")String id);//个别值
标志Http恳求头参数
支持以下方法参数类型:对象、Map、个别值
@PostHttpInterfaceBaseRsp<String> getUser(@HeaderPar("id")String id,//个别值@HeaderPar User user,// 对象@HeaderPar Map<String,Object> map); // Map
假设类型是个别值类型须要手动指定参数名,当成单个恳求头参数传递. 假设是对象或许Map当成多个恳求头参数。
用于标志Http恳求的cookie恳求头
支持以下方法参数类型: Map、Cookie对象、字符串
@PostHttpInterfaceBaseRsp<String> getUser(@CookiePar("id")String cookiePar,//个别值 (指定name)当成单个cookie键值对处置@CookiePar String cookieString,//个别值 (不指定name),当成完整的cookie字符串处置@CookiePar com.burukeyou.uniapi.http.support.Cookie cookieObj,// 单个Cookie对象@CookiePar List<com.burukeyou.uniapi.http.support.Cookie> cookieList // Cookie对象列表@CookiePar Map<String,Object> map); // Map
假设类型是字符串时,当指定参数名时,当成单个cookie键值对处置,假设不指定参数名时当成完整的cookie字符串处置比如a=1;b=2;c=3 这样
假设是Map当成多个cookie键值对处置。
假设类型是内置的 com.burukeyou.uniapi.http.support.Cookie对象当成单个cookie键值对处置
用于标志Http恳求体内容为json方式: 对应content-type为 application/json
支持以下方法参数类型: 对象、对象汇合、Map、个别值、个别值汇合
@PostHttpInterfaceBaseRsp<String> getUser(@BodyJsonParString id,//个别值@BodyJsonParString[] id//个别值汇合@BodyJsonPar List<User> userList,// 对象汇合@BodyJsonPar User user,// 对象@BodyJsonPar Map<String,Object> map);// Map
序列化和反序列化自动用的是fastjson,所以假构想指定别名,可以在字段上标志 @JSONField 注解取别名
用于标志Http恳求体内容为个别表双方式: 对应content-type为 application/x-www-form-urlencoded
支持以下方法参数类型:对象、Map、个别值
@PostHttpInterfaceBaseRsp<String> getUser(@BodyFormPar("name") String value,//个别值@BodyFormPar User user,// 对象@BodyFormPar Map<String,Object> map);// Map
假设类型是个别值类型须要手动指定参数名,当成单个恳求表单键值对传递
用于标志Http恳求体内容为复杂方式: 对应content-type为 multipart/form-data
支持以下方法参数类型: 对象、Map、个别值、File对象
@PostHttpInterfaceBaseRsp<String> getUser(@BodyMultiPartPar("name") String value,//单个表单文本值@BodyMultiPartPar User user,// 对象@BodyMultiPartPar Map<String,Object> map,// Map@BodyMultiPartPar("userImg") File file);// 单个表单文件值
假设参数类型是个别值或许File类型,当成单个表单键值对处置,须要手动指定参数名。
假设参数类型是对象或许Map,当成多个表单键值对处置。假设字段值或许map的value参数值是File类型,则智能当成是文件表单字段传递处置
用于标志Http恳求体内容为二进制方式: 对应content-type为 application/octet-stream
支持以下方法参数类型: InputStream、File、InputStreamSource
@PostHttpInterfaceBaseRsp<String> getUser(@BodyBinaryPar InputStream value,@BodyBinaryPar File user,@BodyBinaryPar InputStreamSource map);
这个注解自身不是对Http恳求内容的性能,仅用于标志一个对象,而后会对该对象内的一切标志了其余@Par注解的字段启动嵌套解析处置, 目标是缩小方法参数数量,支持都内聚到一同传递
支持以下方法参数类型: 对象
@PostHttpInterfaceBaseRsp<String> getUser(@ComposePar UserReq req);
比如UserReq外面的字段可以嵌套标志其余@Par注解,详细支持的标志类型和处置逻辑与前面分歧
class UserReq {@QueryParprivate Long id;@HeaderParprivate String name;@BodyJsonParprivate Add4DTO req;@CookieParprivate String cook;}
HttpResponse示意Http恳求的原始照应答象,假设业务须要关注拿到完整的Http照应,只有要在方法前往值包装前往即可。
如上方所示,此时HttpResponse<Add4DTO>里的泛型Add4DTO才是代表接口实践前往的照应内容,后续可间接手动失掉
@PostHttpInterface("/user-web/get")HttpResponse<Add4DTO> get();
经过它咱们就可以拿到照应的Http形态码、照应头、照应cookie等等,当然也可以拿到咱们的照应body的内容经过getBodyResult方法
关于若是下载文件的类型的接口,可将方法前往值定义为 HttpBinaryResponse、HttpFileResponse、HttpInputStreamResponse 的恣意一种,这样就可以拿到下载后的文件。
HttpApiProcessor是一个Http恳求接口的各种生命周期钩子,开发者可以成功它在外面自定义编写各种对接逻辑。而后可以性能到@HttpApi注解或许@HttpInterface注解上, 而后框架外部自动会从SpringContext失掉,失掉不到则手动new一个。
通常一个Http恳求须要阅历 构建恳求参数、发送Http恳求时,Http照应后失掉照应内容、反序列化Http照应内容成详细对象。
目前提供了4种钩子,口头顺序流程如下:
postBeforeHttpMetadata(恳求发送前)在发送恳求之前,对Http恳求体后置处置|VpostSendingHttpRequest(恳求发送时)在Http恳求发送时处置|VpostAfterHttpResponseBodyString(恳求照应后)对照应body文本字符串启动后置处置|VpostAfterHttpResponseBodyResult(恳求照应后)对照应body反序列化后的结果启动后置处置|VpostAfterMethodReturnValue(恳求照应后)对代理的方法的前往值启动后置处置,相似aop的后置处置
自动经常使用的是Okhttp客户端,假设要从新性能Okhttp客户端,注入spring的bean即可,如下
@Configurationpublic class CusotmConfiguration {@Beanpublic OkHttpClient myOHttpClient(){return new OkHttpClient.Builder().readTimeout(50, TimeUnit.SECONDS).writeTimeout(50, TimeUnit.SECONDS).connectTimeout(10, TimeUnit.SECONDS).connectionPool(new ConnectionPool(20,10, TimeUnit.MINUTES)).build();}}
案例背景:
假定如今须要对接一个某天气服务的一切接口,须要在恳求cookie带上一个token字段和sessionId字段,这两个字段的值须要每次接口调用前后手动调渠道方的一个特定的接口放开失掉,token值在该接口前往值中前往,sessionId在该接口的照应头中前往。
而后还须要在恳求头上带上一个sign签名字段, 该sign签名字段生成规定须要用渠道方提供的公钥对一切恳求体和恳求参数启动加签生成。
而后还须要在每个接口的查问参数上都带上一个渠道方调配的客户端appId。
channel:mtuan:# 恳求域名url:调配的渠道appIdappId: UUU-asd-01# 调配的公钥publicKey: fajdkf9492304jklfahqq
假定如今对接的是某团,所以自定义注解叫@MTuanHttpApi吧,而后须要在该注解上标志@HttpApi注解,并且须要性能processor字段,须要去自定义成功一个HttpApiProcessor这个详细成功后续讲。
有了这个注解后就可以自定义该注解与对接渠道方关系的各种字段性能,当然也可以不定义。
留意这里url的字段是经常使用 @AliasFor(annotation = HttpApi.class),这样构建的HttpMetadata中会自动解析填充要恳求体,不标志则也可自行处置。
@Inherited@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@HttpApi(processor = MTuanHttpApiProcessor.class)public @interface MTuanHttpApi {/*** 渠道方域名地址*/@AliasFor(annotation = HttpApi.class)String url() default "${channel.mtuan.url}";/*** 渠道方调配的appId*/String appId() default "${channel.mtuan.appId}";}@Slf4j@Componentpublic class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {}
有了@MTuanHttpApi注解之后就可以开局对接接口了,比如假定有两个接口要对接。一个就是前面说的失掉令牌的接口。一个是失掉天气状况的接口。
为什么getToken方法前往值是 HttpResponse,这是UniHttp内置的原始Http照应答象,繁难咱们去拿到原始Http照应体的一些内容(比如照应形态码、照应cookie)。
其中的泛型BaseRsp才是实践的Http照应体反序列化后的内容。而getCityWeather方法没有经常使用HttpResponse包装,BaseRsp只是单纯Http照应体反序列化后的内容,这是两者的区别。
前面引见过 HttpResponse,其实大部份接口是不关注HttpResponse的可以不用去性能。
@MTuanHttpApipublic interface WeatherApi {/*** 依据市区名失掉天气状况*/@GetHttpInterface("/getCityByName")BaseRsp<WeatherDTO> getCityWeather(@QueryPar("city") String cityName);/***依据appId和公钥失掉令牌*/@PostHttpInterface("/getToken")HttpResponse<BaseRsp<TokenDTO>> getToken(@HeaderPar("appId") String appId, @HeaderPar("publicKey")String publicKey);}
在之前咱们自定义了一个@MTuanHttpApi注解上指定了一个MTuanHttpApiProcessor,接上去咱们去成功他的详细内容为了成功咱们案例背景里形容的性能。
@Slf4j@Componentpublic class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {/***渠道方调配的公钥*/@Value("${channel.mtuan.publicKey}")private String publicKey;@Value("${channel.mtuan.appId}")private String appId;@Autowiredprivate Environment environment;@Autowiredprivate WeatherApi weatherApi;/** 成功-postBeforeHttpMetadata: 发送Http恳求之前会回调该方法,可对Http恳求体的内容启动二次处置** @param httpMetadata原来的恳求体* @param methodInvocation被代理的方法* @return新的恳求体*/@Overridepublic HttpMetadata postBeforeHttpMetadata(HttpMetadata httpMetadata, HttpApiMethodInvocation<MTuanHttpApi> methodInvocation) {/*** 在查问参数中增加提供的appId字段*/// 失掉MTuanHttpApi注解MTuanHttpApi apiAnnotation = methodInvocation.getProxyApiAnnotation();// 失掉MTuanHttpApi注解的appId,由于该appId是环境变量所以咱们从environment中解析取进去String appIdVar = apiAnnotation.appId();appIdVar = environment.resolvePlaceholders(appIdVar);// 增加到查问参数中httpMetadata.putQueryParam("appId",appIdVar);/***生成签名sign字段*/// 失掉一切查问参数Map<String, Object> queryParam = httpMetadata.getHttpUrl().getQueryParam();// 失掉恳求体参数HttpBody body = httpMetadata.getBody();// 生成签名String signKey = createSignKey(queryParam,body);// 将签名增加到恳求头中httpMetadata.putHeader("sign",signKey);return httpMetadata;}private String createSignKey(Map<String, Object> queryParam, HttpBody body) {// todo 伪代码// 1、将查问参数拼接成字符串String queryParamString = queryParam.entrySet().stream().map(e -> e.getKey() + "="+e.getValue()).collect(Collectors.joining(";"));// 2、将恳求体参数拼接成字符串String bodyString = "";if (body instanceof HttpBodyJSON){// application/json类型的恳求体bodyString = body.toStringBody();}else if (body instanceof HttpBodyFormData){// application/x-www-form-urlencoded 类型的恳求体bodyString = body.toStringBody();}else if (body instanceof HttpBodyMultipart){// multipart/form-data 类型的恳求体bodyString =body.toStringBody();}// 经常使用公钥publicKey 加密拼接起来String sign = publicKey + queryParamString + bodyString;try {MessageDigest md = MessageDigest.getInstance("SHA-256");byte[] digest = md.digest(sign.getBytes());return new String(digest);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}/***成功-postBeforeHttpMetadata: 发送Http恳求时,可定义发送恳求的行为 或许打印恳求和照应日志。*/@Overridepublic HttpResponse<?> postSendHttpRequest(HttpSender httpSender, HttpMetadata httpMetadata) {//疏忽 weatherApi.getToken的方法回调,否则该方法也会回调此方法会递归死循环。 或许该接口指定自定义的HttpApiProcessor重写postSendingHttpRequestMethod getTokenMethod = ReflectionUtils.findMethod(WeatherServiceApi.class, "getToken",String.class,String.class);if (getTokenMethod == null || getTokenMethod.equals(methodInvocation.getMethod())){return httpSender.sendHttpRequest(httpMetadata);}// 1、灵活失掉token和sessionIdHttpResponse<String> httpResponse = weatherApi.getToken(appId, publicKey);// 从照应体失掉令牌tokenString token = httpResponse.getBodyResult();// 从照应头中失掉sessionIdString sessionId = httpResponse.getHeader("sessionId");// 把这两个值放到此次的恳求cookie中httpMetadata.addCookie(new Cookie("token",token));httpMetadata.addCookie(new Cookie("sessionId",sessionId));log.info("开局发送Http恳求 恳求接口:{} 恳求体:{}",httpMetadata.getHttpUrl().toUrl(),httpMetadata.toHttpProtocol());// 经常使用框架内置工具成功发送恳求HttpResponse<?> rsp =httpSender.sendHttpRequest(httpMetadata);log.info("开局发送Http恳求 照应结果:{}",rsp.toHttpProtocol());return rsp;}/***成功-postAfterHttpResponseBodyResult: 反序列化后Http照应体的内容后回调,可对该结果启动二次处置前往* @param bodyResultHttp照应体反序列化后的结果* @param rsp原始Http照应答象* @param method被代理的方法* @param httpMetadataHttp恳求体*/@Overridepublic Object postAfterHttpResponseBodyResult(Object bodyResult, HttpResponse<?> rsp, Method method, HttpMetadata httpMetadata) {if (bodyResult instanceof BaseRsp){BaseRsp baseRsp = (BaseRsp) bodyResult;// 设置baseRsp.setCode(999);}return bodyResult;}}
上方咱们区分重写了postBeforeHttpMetadata、postSendHttpRequest、postAfterHttpResponseBodyResult三个生命周期的钩子方法去成功咱们的需求,在发送恳求前对恳求体启动加签、在发送恳求时灵活失掉令牌从新构建恳求体和打印日志、在发送恳求后给照应答象设置code为999。
本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载联系作者并注明出处:http://clwxseo.com/wangluoyouhua/7611.html