从企业级项目来说,如果你项目里还在用传统的编程式Http客户端比如HttpClient、Okhttp去直接对接第三方Http接口, 那么你项目一定充斥着大量的对接逻辑和代码,并且针对不同的对接渠道方需要每次封装一次调用的简化,一旦封装不好系统将会变得难以维护,难以阅读,甚至不同的开发同学会用自己的方式用不同的Http客户端用不同的封装逻辑去对接接口,这种情况一般发生于项目换了维护者,技术负责人也没把控代码质量和规范所导致
如果你的项目里也存在这样的问题或者需要解决这样的问题, 那么UniHttp就是你的版本答案。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
一个声明式的Http接口对接框架,能以极快的方式完成对一个第三方Http接口的对接和使用,之后就像调用本地方法一样自动去发起Http请求,不需要开发者去关注如何发送一个请求,如何去传递Http请求参数,以及如何对请求结果进行处理和反序列化,这些框架都帮你一一实现
就像配置 Spring的Controller 那样简单,只不过相当于是反向配置而已
该框架更注重于如何保持高内聚和可读性高的代码情况下与快速第三方渠道接口进行对接和集成,而非像传统编程式的Http请求客户端(比如HttpClient、Okhttp)那样专注于如何去发送Http请求,虽然底层也是用的Okhttp去发送请求。
与其说的是对接的Http接口,不如说是对接的第三方渠道,UniHttp可支持自定义接口渠道方HttpAPI注解以及一些自定义的对接和交互行为 ,为此扩展了发送和响应和反序列化一个Http请求的各种生命周期钩子,开发者可自行去扩展实现。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
<dependency > <groupId > io.github.burukeyougroupId > <artifactId > uniapi-httpartifactId > <version > 0.0.4version >dependency >
首先随便创建一个接口,然后在接口上标记@HttpApi注解,然后指定请求的域名url, 然后就可以在方法上去配置对接哪个接口。
比如下面两个方法的配置则对接了以下两个接口
GET http://localhost:8080/getUser
POST http://localhost:8080/addUser
方法返回值定义成Http响应body对应的类型即可,默认会使用fastjson反序列化Http响应body的值为该类型对象。
@HttpApi (url = 'http://localhost:8080' )interface UserHttpApi { @GetHttpInterface ('/getUser' ) BaseRsp getUser (@QueryPar('name' ) String param,@HeaderPar ('userId' ) Integer id) ; @PostHttpInterface ('/addUser' ) BaseRsp addUser (@BodyJsonPar Add4DTO req) ; }
@QueryPar
表示将参数值放到Http请求的查询参数内
@HeaderPar
表示将参数值放到Http请求的请求头里
@BodyJsonPar
表示将参数值放到Http请求body内,并且
content-type
是
application/json
GET http://localhost:8080/getUser?name=param Header: userId: id
POST: http://localhost:8080/addUser Header: Content-Type: application/json Body: {'id':1,'name':'jay'}
在spring的配置类上使用
@UniAPIScan
注解标记定义的
@HttpAPI
的包扫描路径,会自动为标记了
@HttpApi
接口生成代理对象并且注入到Spring容器中,之后只需要像使用Spring的其他bean一样,依赖注入使用即可
@UniAPIScan ('com.xxx.demo.api' )@SpringBootApplication public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class ,args ) ; } }
@Service class UserAppService { @Autowired private 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 getUser () ;
以下各种Par后缀的注解,主要用于方法参数上,用于指定在发送请求时将参数值放到Http请求体的哪部分上。
为了方便描述,下文描述的普通值就是表示String,基本类型、基本类型的包装类型等类型.
简单复习下Http协议报文
标记Http请求url的查询参数
支持以下方法参数类型的标记: 普通值、普通值集合、对象、Map
@PostHttpInterface BaseRsp getUser (@QueryPar('id' ) String id, // 普通值 @QueryPar ('ids' ) List idsList, // 普通值集合 @QueryPar User user, // 对象 @QueryPar Map map) ; // Map
如果类型是普通值或者普通值集合需要手动指定参数名,因为是当成单个查询参数传递
如果类型是对象或者Map是当成多个查询参数传递,字段名或者map的key名就是参数名,字段值或者map的value值就是参数值。
如果是对象,参数名默认是字段名,由于用的是fastjson序列化可以用@JSONField指定别名
标记Http请求路径变量参数,仅支持标记普通值类型
@PostHttpInterface ('/getUser/{userId}/detail' )BaseRsp getUser (@PathPar('userId' ) String id) ; // 普通值
标记Http请求头参数
支持以下方法参数类型:对象、Map、普通值
@PostHttpInterface BaseRsp getUser (@HeaderPar('id' ) String id, // 普通值 @HeaderPar User user, // 对象 @HeaderPar Map map) ; // Map
如果类型是普通值类型需要手动指定参数名,当成单个请求头参数传递. 如果是对象或者Map当成多个请求头参数。
用于标记Http请求的cookie请求头
支持以下方法参数类型: Map、Cookie对象、字符串
@PostHttpInterface BaseRsp getUser (@CookiePar('id' ) String cookiePar, // 普通值 (指定name)当成单个cookie键值对处理 @CookiePar String cookieString, // 普通值 (不指定name),当成完整的cookie字符串处理 @CookiePar com.burukeyou.uniapi.http.support.Cookie cookieObj, // 单个Cookie对象 @CookiePar List cookieList // Cookie对象列表 @CookiePar Map 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、普通值、普通值集合
@PostHttpInterface BaseRsp getUser (@BodyJsonPar String id, // 普通值 @BodyJsonPar String[] id // 普通值集合 @BodyJsonPar List userList, // 对象集合 @BodyJsonPar User user, // 对象 @BodyJsonPar Map map) ; // Map
序列化和反序列化默认用的是fastjson,所以如果想指定别名,可以在字段上标记
@JSONField
注解取别名
用于标记Http请求体内容为普通表单形式: 对应
content-type
为
application/x-www-form-urlencoded
支持以下方法参数类型:对象、Map、普通值
@PostHttpInterface BaseRsp getUser (@BodyFormPar('name' ) String value, // 普通值 @BodyFormPar User user, // 对象 @BodyFormPar Map map) ; // Map
如果类型是普通值类型需要手动指定参数名,当成单个请求表单键值对传递
用于标记Http请求体内容为复杂形式: 对应
content-type
为
multipart/form-data
支持以下方法参数类型: 对象、Map、普通值、File对象
@PostHttpInterface BaseRsp getUser (@BodyMultiPartPar('name' ) String value, // 单个表单文本值 @BodyMultiPartPar User user, // 对象 @BodyMultiPartPar Map map, // Map @BodyMultiPartPar ('userImg' ) File file) ; // 单个表单文件值
如果参数类型是普通值或者File类型,当成单个表单键值对处理,需要手动指定参数名。
如果参数类型是对象或者Map,当成多个表单键值对处理。如果字段值或者map的value参数值是File类型,则自动当成是文件表单字段传递处理
用于标记Http请求体内容为二进制形式: 对应
content-type
为
application/octet-stream
支持以下方法参数类型:
InputStream
、
File
、
InputStreamSource
@PostHttpInterface BaseRsp getUser (@BodyBinaryPar InputStream value, @BodyBinaryPar File user, @BodyBinaryPar InputStreamSource map) ;
这个注解本身不是对Http请求内容的配置,仅用于标记一个对象,然后会对该对象内的所有标记了其他@Par注解的字段进行嵌套解析处理, 目的是减少方法参数数量,支持都内聚到一起传递
支持以下方法参数类型: 对象
@PostHttpInterface BaseRsp getUser (@ComposePar UserReq req) ;
比如UserReq里面的字段可以嵌套标记其他@Par注解,具体支持的标记类型和处理逻辑与前面一致
class UserReq { @QueryPar private Long id; @HeaderPar private String name; @BodyJsonPar private Add4DTO req; @CookiePar private String cook; }
HttpResponse
表示Http请求的原始响应对象,如果业务需要关注拿到完整的Http响应,只需要在方法返回值包装返回即可。
如下面所示,此时
HttpResponse
里的泛型Add4DTO才是代表接口实际返回的响应内容,后续可直接手动获取
@PostHttpInterface ('/user-web/get' )HttpResponse get () ;
通过它我们就可以拿到响应的Http状态码、响应头、响应cookie等等,当然也可以拿到我们的响应body的内容通过
getBodyResult
方法
对于若是下载文件的类型的接口,可将方法返回值定义为
HttpBinaryResponse
、
HttpFileResponse
、
HttpInputStreamResponse
的任意一种,这样就可以拿到下载后的文件。
HttpBinaryResponse:
表示下载的文件内容以二进制形式返回,如果是大文件请谨慎处理,因为会存放在内存中
HttpFileResponse:
表示下载的文件内容以File对象返回,这时文件已经被下载到了本地磁盘
HttpInputStreamResponse:
表示下载的文件内容输入流的形式返回,这时文件其实还没被下载到客户端,调用者可以自行读取该输入流进行文件的下载
HttpApiProcessor
是一个Http请求接口的各种生命周期钩子,开发者可以实现它在里面自定义编写各种对接逻辑。然后可以配置到
@HttpApi
注解或者
@HttpInterface
注解上, 然后框架内部默认会从
SpringContext
获取,获取不到则手动new一个。
通常一个Http请求需要经历 构建请求参数、发送Http请求时,Http响应后获取响应内容、反序列化Http响应内容成具体对象。
目前提供了4种钩子,执行顺序流程如下:
postBeforeHttpMetadata (请求发送前)在发送请求之前,对Http请求体后置处理 | VpostSendingHttpRequest (请求发送时) 在Http请求发送时处理 | VpostAfterHttpResponseBodyString (请求响应后) 对响应body文本字符串进行后置处理 | VpostAfterHttpResponseBodyResult (请求响应后) 对响应body反序列化后的结果进行后置处理 | VpostAfterMethodReturnValue (请求响应后) 对代理的方法的返回值进行后置处理,类似aop的后置处理
postBeforeHttpMetadata:
可在发送http请求之前对请求体进行二次处理,比如加签之类
postSendHttpRequest:
Http请求发送时会回调该方法,可以在该方法执行自定义的发送逻辑或者打印发送日志
postAfterHttpResponseBodyString:
Http请求响应后,对响应body字符串进行进行后置处理,比如如果是加密数据可以进行解密
postAfterHttpResponseBodyResult:
Http请求响应后,对响应body反序列化后的对象进行后置处理,比如填充默认返回值
postAfterMethodReturnValue:
Http请求响应后,对代理的方法的返回值进行后置处理,类似aop的后置处理
HttpMetadata:
表示此次Http请求的请求体,包含请求url,请求头、请求方式、请求cookie、请求体、请求参数等等。
HttpApiMethodInvocation:
继承自
MethodInvocation
, 表示被代理的方法调用上下文,可以拿到被代理的类,被代理的方法,被代理的HttpAPI注解、
HttpInterface
注解等信息
默认使用的是Okhttp客户端,如果要重新配置Okhttp客户端,注入spring的bean即可,如下
@Configuration public class CusotmConfiguration { @Bean public 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: http://127.0.0.1:8999 # 分配的渠道appId appId: UUU-asd-01 # 分配的公钥 publicKey: fajdkf9492304jklfahqq
假设现在对接的是某团,所以自定义注解叫
@MTuanHttpApi
吧,然后需要在该注解上标记
@HttpApi
注解,并且需要配置
processor
字段,需要去自定义实现一个
HttpApiProcessor
这个具体实现后续讲。
有了这个注解后就可以自定义该注解与对接渠道方相关的各种字段配置,当然也可以不定义。
注意这里url的字段是使用
@AliasFor(annotation = HttpApi.class)
,这样构建的
HttpMetadata
中会默认解析填充要请求体,不标记则也可自行处理。