Java 聲明式 Http 接口對接框架
1、簡介
一個(gè)聲明式的Http接口對接框架,能以極快的方式完成對一個(gè)第三方Http接口的對接和使用,之后就像調(diào)用本地方法一樣自動(dòng)去發(fā)起Http請求,不需要開發(fā)者去關(guān)注如何發(fā)送一個(gè)請求,如何去傳遞Http請求參數(shù),以及如何對請求結(jié)果進(jìn)行處理和反序列化,這些框架都幫你一一實(shí)現(xiàn)
就像配置 Spring的Controller 那樣簡單,只不過相當(dāng)于是反向配置而已
該框架更注重于如何保持高內(nèi)聚和可讀性高的代碼情況下與快速第三方渠道接口進(jìn)行對接和集成,而非像傳統(tǒng)編程式的Http請求客戶端(比如HttpClient、Okhttp)那樣專注于如何去發(fā)送Http請求,雖然底層也是用的Okhttp去發(fā)送請求。
與其說的是對接的Http接口,不如說是對接的第三方渠道,UniHttp可支持自定義接口渠道方HttpAPI注解以及一些自定義的對接和交互行為 ,為此擴(kuò)展了發(fā)送和響應(yīng)和反序列化一個(gè)Http請求的各種生命周期鉤子,開發(fā)者可自行去擴(kuò)展實(shí)現(xiàn)。
2、快速開始
2.1、引入依賴
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>uniapi-http</artifactId>
<version>0.0.4</version>
</dependency>
2.2、對接接口
首先隨便創(chuàng)建一個(gè)接口,然后在接口上標(biāo)記@HttpApi注解,然后指定請求的域名url, 然后就可以在方法上去配置對接哪個(gè)接口。
比如下面兩個(gè)方法的配置則對接了以下兩個(gè)接口
- GET http://localhost:8080/getUser
- POST http://localhost:8080/addUser
方法返回值定義成Http響應(yīng)body對應(yīng)的類型即可,默認(rèn)會(huì)使用fastjson反序列化Http響應(yīng)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);
}
- @QueryPar 表示將參數(shù)值放到Http請求的查詢參數(shù)內(nèi)
- @HeaderPar 表示將參數(shù)值放到Http請求的請求頭里
- @BodyJsonPar 表示將參數(shù)值放到Http請求body內(nèi),并且content-type是application/json
1)getUser方法最終構(gòu)建的Http請求報(bào)文為
GET http://localhost:8080/getUser?name=param
Header:
userId: id
2)addUser最終構(gòu)建的Http請求報(bào)文為
POST: http://localhost:8080/addUser
Header:
Content-Type: application/json
Body:
{"id":1,"name":"jay"}
2.3、聲明定義的HttpAPI的包掃描路徑
在spring的配置類上使用@UniAPIScan注解標(biāo)記定義的@HttpAPI的包掃描路徑,會(huì)自動(dòng)為標(biāo)記了@HttpApi接口生成代理對象并且注入到Spring容器中,之后只需要像使用Spring的其他bean一樣,依賴注入使用即可
@UniAPIScan("com.xxx.demo.api")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
2.4、依賴注入使用即可
@Service
class UserAppService {
@Autowired
private UserHttpApi userHttpApi;
public void doSomething(){
userHttpApi.getUser("jay",3);
}
}
3、說明介紹
3.1、@HttpApi注解
用于標(biāo)記接口上,該接口上的方法會(huì)被代理到對應(yīng)的Http請求接口,可指定請求的域名,也可指定自定義的Http代理邏輯等等。
3.2、@HttpInterface注解
用于配置一個(gè)接口的參數(shù),包括請求方式、請求路徑、請求頭、請求cookie、請求查詢參數(shù)等等
并且內(nèi)置了以下請求方式的@HttpInterface,不必再每次手動(dòng)指定請求方式
- @PostHttpInterface
- @PutHttpInterface
- @DeleteHttpInterface
- @GetHttpInterface
@PostHttpInterface(
// 請求路徑
path = "/getUser",
// 請求頭
headers = {"clientType:sys-app","userId:99"},
// url查詢參數(shù)
params = {"name=周杰倫","age=1"},
// url查詢參數(shù)拼接字符串
paramStr = "a=1&b=2&c=3&d=哈哈&e=%E7%89%9B%E9%80%BC",
// cookie 字符串
cookie = "name=1;sessionId=999"
)
BaseRsp<String> getUser();
3.3、@Par注解
以下各種Par后綴的注解,主要用于方法參數(shù)上,用于指定在發(fā)送請求時(shí)將參數(shù)值放到Http請求體的哪部分上。
為了方便描述,下文描述的普通值就是表示String,基本類型、基本類型的包裝類型等類型.
簡單復(fù)習(xí)下Http協(xié)議報(bào)文
圖片
@QueryPar注解
標(biāo)記Http請求url的查詢參數(shù)
支持以下方法參數(shù)類型的標(biāo)記: 普通值、普通值集合、對象、Map
@PostHttpInterface
BaseRsp<String> getUser(@QueryPar("id") String id, // 普通值
@QueryPar("ids") List<Integer> idsList, // 普通值集合
@QueryPar User user, // 對象
@QueryPar Map<String,Object> map); // Map
如果類型是普通值或者普通值集合需要手動(dòng)指定參數(shù)名,因?yàn)槭钱?dāng)成單個(gè)查詢參數(shù)傳遞
如果類型是對象或者M(jìn)ap是當(dāng)成多個(gè)查詢參數(shù)傳遞,字段名或者map的key名就是參數(shù)名,字段值或者map的value值就是參數(shù)值。
如果是對象,參數(shù)名默認(rèn)是字段名,由于用的是fastjson序列化可以用@JSONField指定別名
@PathPar注解
標(biāo)記Http請求路徑變量參數(shù),僅支持標(biāo)記普通值類型
@PostHttpInterface("/getUser/{userId}/detail")
BaseRsp<String> getUser(@PathPar("userId") String id); // 普通值
@HeaderPar注解
標(biāo)記Http請求頭參數(shù)
支持以下方法參數(shù)類型:對象、Map、普通值
@PostHttpInterface
BaseRsp<String> getUser(@HeaderPar("id") String id, // 普通值
@HeaderPar User user, // 對象
@HeaderPar Map<String,Object> map); // Map
如果類型是普通值類型需要手動(dòng)指定參數(shù)名,當(dāng)成單個(gè)請求頭參數(shù)傳遞. 如果是對象或者M(jìn)ap當(dāng)成多個(gè)請求頭參數(shù)。
@CookiePar注解
用于標(biāo)記Http請求的cookie請求頭
支持以下方法參數(shù)類型: Map、Cookie對象、字符串
@PostHttpInterface
BaseRsp<String> getUser(@CookiePar("id") String cookiePar, // 普通值 (指定name)當(dāng)成單個(gè)cookie鍵值對處理
@CookiePar String cookieString, // 普通值 (不指定name),當(dāng)成完整的cookie字符串處理
@CookiePar com.burukeyou.uniapi.http.support.Cookie cookieObj, // 單個(gè)Cookie對象
@CookiePar List<com.burukeyou.uniapi.http.support.Cookie> cookieList // Cookie對象列表
@CookiePar Map<String,Object> map); // Map
如果類型是字符串時(shí),當(dāng)指定參數(shù)名時(shí),當(dāng)成單個(gè)cookie鍵值對處理,如果不指定參數(shù)名時(shí)當(dāng)成完整的cookie字符串處理比如a=1;b=2;c=3 這樣
如果是Map當(dāng)成多個(gè)cookie鍵值對處理。
如果類型是內(nèi)置的 com.burukeyou.uniapi.http.support.Cookie對象當(dāng)成單個(gè)cookie鍵值對處理
@BodyJsonPar注解
用于標(biāo)記Http請求體內(nèi)容為json形式: 對應(yīng)content-type為 application/json
支持以下方法參數(shù)類型: 對象、對象集合、Map、普通值、普通值集合
@PostHttpInterface
BaseRsp<String> getUser(@BodyJsonPar String id, // 普通值
@BodyJsonPar String[] id // 普通值集合
@BodyJsonPar List<User> userList, // 對象集合
@BodyJsonPar User user, // 對象
@BodyJsonPar Map<String,Object> map); // Map
序列化和反序列化默認(rèn)用的是fastjson,所以如果想指定別名,可以在字段上標(biāo)記 @JSONField 注解取別名
@BodyFormPar注解
用于標(biāo)記Http請求體內(nèi)容為普通表單形式: 對應(yīng)content-type為 application/x-www-form-urlencoded
支持以下方法參數(shù)類型:對象、Map、普通值
@PostHttpInterface
BaseRsp<String> getUser(@BodyFormPar("name") String value, // 普通值
@BodyFormPar User user, // 對象
@BodyFormPar Map<String,Object> map); // Map
如果類型是普通值類型需要手動(dòng)指定參數(shù)名,當(dāng)成單個(gè)請求表單鍵值對傳遞
@BodyMultiPartPar注解
用于標(biāo)記Http請求體內(nèi)容為復(fù)雜形式: 對應(yīng)content-type為 multipart/form-data
支持以下方法參數(shù)類型: 對象、Map、普通值、File對象
@PostHttpInterface
BaseRsp<String> getUser(@BodyMultiPartPar("name") String value, // 單個(gè)表單文本值
@BodyMultiPartPar User user, // 對象
@BodyMultiPartPar Map<String,Object> map, // Map
@BodyMultiPartPar("userImg") File file); // 單個(gè)表單文件值
如果參數(shù)類型是普通值或者File類型,當(dāng)成單個(gè)表單鍵值對處理,需要手動(dòng)指定參數(shù)名。
如果參數(shù)類型是對象或者M(jìn)ap,當(dāng)成多個(gè)表單鍵值對處理。如果字段值或者map的value參數(shù)值是File類型,則自動(dòng)當(dāng)成是文件表單字段傳遞處理
@BodyBinaryPar注解
用于標(biāo)記Http請求體內(nèi)容為二進(jìn)制形式: 對應(yīng)content-type為 application/octet-stream
支持以下方法參數(shù)類型: InputStream、File、InputStreamSource
@PostHttpInterface
BaseRsp<String> getUser(@BodyBinaryPar InputStream value,
@BodyBinaryPar File user,
@BodyBinaryPar InputStreamSource map);
@ComposePar注解
這個(gè)注解本身不是對Http請求內(nèi)容的配置,僅用于標(biāo)記一個(gè)對象,然后會(huì)對該對象內(nèi)的所有標(biāo)記了其他@Par注解的字段進(jìn)行嵌套解析處理, 目的是減少方法參數(shù)數(shù)量,支持都內(nèi)聚到一起傳遞
支持以下方法參數(shù)類型: 對象
@PostHttpInterface
BaseRsp<String> getUser(@ComposePar UserReq req);
比如UserReq里面的字段可以嵌套標(biāo)記其他@Par注解,具體支持的標(biāo)記類型和處理邏輯與前面一致
class UserReq {
@QueryPar
private Long id;
@HeaderPar
private String name;
@BodyJsonPar
private Add4DTO req;
@CookiePar
private String cook;
}
3.4、原始的HttpResponse
HttpResponse表示Http請求的原始響應(yīng)對象,如果業(yè)務(wù)需要關(guān)注拿到完整的Http響應(yīng),只需要在方法返回值包裝返回即可。
如下面所示,此時(shí)HttpResponse<Add4DTO>里的泛型Add4DTO才是代表接口實(shí)際返回的響應(yīng)內(nèi)容,后續(xù)可直接手動(dòng)獲取
@PostHttpInterface("/user-web/get")
HttpResponse<Add4DTO> get();
通過它我們就可以拿到響應(yīng)的Http狀態(tài)碼、響應(yīng)頭、響應(yīng)cookie等等,當(dāng)然也可以拿到我們的響應(yīng)body的內(nèi)容通過getBodyResult方法
3.5、處理文件下載接口
對于若是下載文件的類型的接口,可將方法返回值定義為 HttpBinaryResponse、HttpFileResponse、HttpInputStreamResponse 的任意一種,這樣就可以拿到下載后的文件。
- HttpBinaryResponse: 表示下載的文件內(nèi)容以二進(jìn)制形式返回,如果是大文件請謹(jǐn)慎處理,因?yàn)闀?huì)存放在內(nèi)存中
- HttpFileResponse: 表示下載的文件內(nèi)容以File對象返回,這時(shí)文件已經(jīng)被下載到了本地磁盤
- HttpInputStreamResponse: 表示下載的文件內(nèi)容輸入流的形式返回,這時(shí)文件其實(shí)還沒被下載到客戶端,調(diào)用者可以自行讀取該輸入流進(jìn)行文件的下載
3.6、HttpApiProcessor 生命周期鉤子
HttpApiProcessor是一個(gè)Http請求接口的各種生命周期鉤子,開發(fā)者可以實(shí)現(xiàn)它在里面自定義編寫各種對接邏輯。然后可以配置到@HttpApi注解或者@HttpInterface注解上, 然后框架內(nèi)部默認(rèn)會(huì)從SpringContext獲取,獲取不到則手動(dòng)new一個(gè)。
通常一個(gè)Http請求需要經(jīng)歷 構(gòu)建請求參數(shù)、發(fā)送Http請求時(shí),Http響應(yīng)后獲取響應(yīng)內(nèi)容、反序列化Http響應(yīng)內(nèi)容成具體對象。
目前提供了4種鉤子,執(zhí)行順序流程如下:
postBeforeHttpMetadata (請求發(fā)送前)在發(fā)送請求之前,對Http請求體后置處理
|
V
postSendingHttpRequest (請求發(fā)送時(shí))在Http請求發(fā)送時(shí)處理
|
V
postAfterHttpResponseBodyString (請求響應(yīng)后)對響應(yīng)body文本字符串進(jìn)行后置處理
|
V
postAfterHttpResponseBodyResult (請求響應(yīng)后)對響應(yīng)body反序列化后的結(jié)果進(jìn)行后置處理
|
V
postAfterMethodReturnValue (請求響應(yīng)后)對代理的方法的返回值進(jìn)行后置處理,類似aop的后置處理
- postBeforeHttpMetadata: 可在發(fā)送http請求之前對請求體進(jìn)行二次處理,比如加簽之類
- postSendHttpRequest: Http請求發(fā)送時(shí)會(huì)回調(diào)該方法,可以在該方法執(zhí)行自定義的發(fā)送邏輯或者打印發(fā)送日志
- postAfterHttpResponseBodyString: Http請求響應(yīng)后,對響應(yīng)body字符串進(jìn)行進(jìn)行后置處理,比如如果是加密數(shù)據(jù)可以進(jìn)行解密
- postAfterHttpResponseBodyResult: Http請求響應(yīng)后,對響應(yīng)body反序列化后的對象進(jìn)行后置處理,比如填充默認(rèn)返回值
- postAfterMethodReturnValue: Http請求響應(yīng)后,對代理的方法的返回值進(jìn)行后置處理,類似aop的后置處理
回調(diào)參數(shù)說明:
- HttpMetadata: 表示此次Http請求的請求體,包含請求url,請求頭、請求方式、請求cookie、請求體、請求參數(shù)等等。
- HttpApiMethodInvocation: 繼承自MethodInvocation, 表示被代理的方法調(diào)用上下文,可以拿到被代理的類,被代理的方法,被代理的HttpAPI注解、HttpInterface注解等信息
3.7、配置自定義的Http客戶端
默認(rèn)使用的是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();
}
}
4、企業(yè)級渠道對接實(shí)戰(zhàn)
案例背景:
假設(shè)現(xiàn)在需要對接一個(gè)某天氣服務(wù)的所有接口,需要在請求cookie帶上一個(gè)token字段和sessionId字段,這兩個(gè)字段的值需要每次接口調(diào)用前先手動(dòng)調(diào)渠道方的一個(gè)特定的接口申請獲取,token值在該接口返回值中返回,sessionId在該接口的響應(yīng)頭中返回。
然后還需要在請求頭上帶上一個(gè)sign簽名字段, 該sign簽名字段生成規(guī)則需要用渠道方提供的公鑰對所有請求體和請求參數(shù)進(jìn)行加簽生成。
然后還需要在每個(gè)接口的查詢參數(shù)上都帶上一個(gè)渠道方分配的客戶端appId。
4.1、在application.yml中配置對接渠道方的信息
channel:
mtuan:
# 請求域名
url: http://127.0.0.1:8999
# 分配的渠道appId
appId: UUU-asd-01
# 分配的公鑰
publicKey: fajdkf9492304jklfahqq
4.2、自定義該渠道方的HttpAPI注解
假設(shè)現(xiàn)在對接的是某團(tuán),所以自定義注解叫@MTuanHttpApi吧,然后需要在該注解上標(biāo)記@HttpApi注解,并且需要配置processor字段,需要去自定義實(shí)現(xiàn)一個(gè)HttpApiProcessor這個(gè)具體實(shí)現(xiàn)后續(xù)講。
有了這個(gè)注解后就可以自定義該注解與對接渠道方相關(guān)的各種字段配置,當(dāng)然也可以不定義。
注意這里url的字段是使用 @AliasFor(annotation = HttpApi.class),這樣構(gòu)建的HttpMetadata中會(huì)默認(rèn)解析填充要請求體,不標(biāo)記則也可自行處理。
@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
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {
}
注意實(shí)現(xiàn)的HttpApiProcessor泛型要指定為剛才定義的注解@MTuanHttpApi類型,因?yàn)檫@個(gè)HttpApiProcessor配置到它上面,如果需要通用處理可以定義為Annocation類型
4.3、對接接口
有了@MTuanHttpApi注解之后就可以開始對接接口了,比如假設(shè)有兩個(gè)接口要對接。一個(gè)就是前面說的獲取令牌的接口。一個(gè)是獲取天氣情況的接口。
為什么getToken方法返回值是 HttpResponse,這是UniHttp內(nèi)置的原始Http響應(yīng)對象,方便我們?nèi)ツ玫皆糎ttp響應(yīng)體的一些內(nèi)容(比如響應(yīng)狀態(tài)碼、響應(yīng)cookie)。
其中的泛型BaseRsp才是實(shí)際的Http響應(yīng)體反序列化后的內(nèi)容。而getCityWeather方法沒有使用HttpResponse包裝,BaseRsp只是單純Http響應(yīng)體反序列化后的內(nèi)容,這是兩者的區(qū)別。
前面介紹過 HttpResponse,其實(shí)大部份接口是不關(guān)注HttpResponse的可以不用去配置。
@MTuanHttpApi
public interface WeatherApi {
/**
* 根據(jù)城市名獲取天氣情況
*/
@GetHttpInterface("/getCityByName")
BaseRsp<WeatherDTO> getCityWeather(@QueryPar("city") String cityName);
/**
* 根據(jù)appId和公鑰獲取令牌
*/
@PostHttpInterface("/getToken")
HttpResponse<BaseRsp<TokenDTO>> getToken(@HeaderPar("appId") String appId, @HeaderPar("publicKey")String publicKey);
}
4.4、自定義HttpApiProcessor
在之前我們自定義了一個(gè)@MTuanHttpApi注解上指定了一個(gè)MTuanHttpApiProcessor,接下來我們?nèi)?shí)現(xiàn)他的具體內(nèi)容為了實(shí)現(xiàn)我們案例背景里描述的功能。
@Slf4j
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {
/**
* 渠道方分配的公鑰
*/
@Value("${channel.mtuan.publicKey}")
private String publicKey;
@Value("${channel.mtuan.appId}")
private String appId;
@Autowired
private Environment environment;
@Autowired
private WeatherApi weatherApi;
/** 實(shí)現(xiàn)-postBeforeHttpMetadata: 發(fā)送Http請求之前會(huì)回調(diào)該方法,可對Http請求體的內(nèi)容進(jìn)行二次處理
*
* @param httpMetadata 原來的請求體
* @param methodInvocation 被代理的方法
* @return 新的請求體
*/
@Override
public HttpMetadata postBeforeHttpMetadata(HttpMetadata httpMetadata, HttpApiMethodInvocation<MTuanHttpApi> methodInvocation) {
/**
* 在查詢參數(shù)中添加提供的appId字段
*/
// 獲取MTuanHttpApi注解
MTuanHttpApi apiAnnotation = methodInvocation.getProxyApiAnnotation();
// 獲取MTuanHttpApi注解的appId,由于該appId是環(huán)境變量所以我們從environment中解析取出來
String appIdVar = apiAnnotation.appId();
appIdVar = environment.resolvePlaceholders(appIdVar);
// 添加到查詢參數(shù)中
httpMetadata.putQueryParam("appId",appIdVar);
/**
* 生成簽名sign字段
*/
// 獲取所有查詢參數(shù)
Map<String, Object> queryParam = httpMetadata.getHttpUrl().getQueryParam();
// 獲取請求體參數(shù)
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、將查詢參數(shù)拼接成字符串
String queryParamString = queryParam.entrySet()
.stream().map(e -> e.getKey() + "="+e.getValue())
.collect(Collectors.joining(";"));
// 2、將請求體參數(shù)拼接成字符串
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);
}
}
/**
* 實(shí)現(xiàn)-postBeforeHttpMetadata: 發(fā)送Http請求時(shí),可定義發(fā)送請求的行為 或者打印請求和響應(yīng)日志。
*/
@Override
public HttpResponse<?> postSendHttpRequest(HttpSender httpSender, HttpMetadata httpMetadata) {
// 忽略 weatherApi.getToken的方法回調(diào),否則該方法也會(huì)回調(diào)此方法會(huì)遞歸死循環(huán)。 或者該接口指定自定義的HttpApiProcessor重寫postSendingHttpRequest
Method getTokenMethod = ReflectionUtils.findMethod(WeatherServiceApi.class, "getToken",String.class,String.class);
if (getTokenMethod == null || getTokenMethod.equals(methodInvocation.getMethod())){
return httpSender.sendHttpRequest(httpMetadata);
}
// 1、動(dòng)態(tài)獲取token和sessionId
HttpResponse<String> httpResponse = weatherApi.getToken(appId, publicKey);
// 從響應(yīng)體獲取令牌token
String token = httpResponse.getBodyResult();
// 從響應(yīng)頭中獲取sessionId
String sessionId = httpResponse.getHeader("sessionId");
// 把這兩個(gè)值放到此次的請求cookie中
httpMetadata.addCookie(new Cookie("token",token));
httpMetadata.addCookie(new Cookie("sessionId",sessionId));
log.info("開始發(fā)送Http請求 請求接口:{} 請求體:{}",httpMetadata.getHttpUrl().toUrl(),httpMetadata.toHttpProtocol());
// 使用框架內(nèi)置工具實(shí)現(xiàn)發(fā)送請求
HttpResponse<?> rsp = httpSender.sendHttpRequest(httpMetadata);
log.info("開始發(fā)送Http請求 響應(yīng)結(jié)果:{}",rsp.toHttpProtocol());
return rsp;
}
/**
* 實(shí)現(xiàn)-postAfterHttpResponseBodyResult: 反序列化后Http響應(yīng)體的內(nèi)容后回調(diào),可對該結(jié)果進(jìn)行二次處理返回
* @param bodyResult Http響應(yīng)體反序列化后的結(jié)果
* @param rsp 原始Http響應(yīng)對象
* @param method 被代理的方法
* @param httpMetadata Http請求體
*/
@Override
public Object postAfterHttpResponseBodyResult(Object bodyResult, HttpResponse<?> rsp, Method method, HttpMetadata httpMetadata) {
if (bodyResult instanceof BaseRsp){
BaseRsp baseRsp = (BaseRsp) bodyResult;
// 設(shè)置
baseRsp.setCode(999);
}
return bodyResult;
}
}
上面我們分別重寫了postBeforeHttpMetadata、postSendHttpRequest、postAfterHttpResponseBodyResult三個(gè)生命周期的鉤子方法去完成我們的需求,在發(fā)送請求前對請求體進(jìn)行加簽、在發(fā)送請求時(shí)動(dòng)態(tài)獲取令牌重新構(gòu)建請求體和打印日志、在發(fā)送請求后給響應(yīng)對象設(shè)置code為999。