最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

又解鎖了一種OpenFeign的使用方式!

2023-02-15 10:57 作者:Anyin灬  | 我要投稿

引言

Hello 大家好,這里是Anyin。

關(guān)于OpenFeign那點事兒 - 使用篇 中和大家分享了關(guān)于OpenFeign在某些場景下的一些處理和使用方法,而今天Anyin再次解鎖了OpenFeign的又一個使用場景,只能說真香。

在我們?nèi)粘i_發(fā)中,相信大家都會接觸過對接第三方系統(tǒng)。對接第三方系統(tǒng)最煩人的工作可能就是剛開始對接的時候關(guān)于認(rèn)證、加密、驗簽、JSON正反序列化等一系列的操作了。

我們知道OpenFeign它其實是一個http的客戶端,主要的應(yīng)用場景就是在微服務(wù)體系內(nèi)進(jìn)行微服務(wù)之間的相互調(diào)用;那么它是不是也可以實現(xiàn)第三方調(diào)用? ?

很明顯是可以的!??!

需求分析

在驗證我們的觀點:OpenFeign可以實現(xiàn)第三方系統(tǒng)的調(diào)用之前,我們先找一個公開的第三方系統(tǒng)協(xié)議進(jìn)行一波簡單的需求分析吧。

這里我們使用中電聯(lián)(中國電力企業(yè)聯(lián)合標(biāo)準(zhǔn))的協(xié)議文檔為例。這里附上下載地址,有需要的同學(xué)可以自取。

中國電力企業(yè)聯(lián)合標(biāo)準(zhǔn)

以下為協(xié)議文檔對于密鑰的要求。

image.png

通過查看協(xié)議文檔,我們知道整個對接過程會設(shè)計到以下幾個需求:

  1. 調(diào)用方式統(tǒng)一使用POST方式

  2. 傳輸格式使用JSON

  3. 傳輸過程業(yè)務(wù)數(shù)據(jù)需要進(jìn)行加密

  4. 傳輸過程整包數(shù)據(jù)需要生成簽名,因為服務(wù)端會進(jìn)行驗簽,保證數(shù)據(jù)沒有被篡改

  5. 在進(jìn)行第三方調(diào)用的時候需要像調(diào)用其他本地的Service一樣絲滑(行為一致)

業(yè)務(wù)實現(xiàn)

為了通過OpenFeign實現(xiàn)以上需求,我們首先定義一個配置類,用于自定義客戶端的配置類。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CECOperatorProperties.class)
public class CECFeignClientConfig implements RequestInterceptor {
? ?@Autowired
? ?private CECOperatorProperties properties;
? ?@Override
? ?public void apply(RequestTemplate requestTemplate) {} ? ?
}

  1. 實現(xiàn)RequestInterceptor接口,這里是為了在進(jìn)行認(rèn)證拿到access_token之后,可以通過攔截器在header頭放入對應(yīng)的token信息

  2. 注入CECOperatorProperties屬性,對于加解密、驗簽等操作需要的一些秘鑰信息,從配置中心獲取后,注入該屬性類中

  3. @Configuration(proxyBeanMethods = false) 配置該類配置類,并且不會在RootApplicationContext當(dāng)中注冊,只會在使用的時候才會進(jìn)行相關(guān)配置。

這里注意哈,在這個類配置的@Bean實例,只有在當(dāng)前的FeignClient實例的ApplicaitonContext當(dāng)中可以訪問到,其他地方訪問不到。具體可以看

關(guān)于OpenFeign那點事兒 - 源碼篇

接著,我們需要2個基本的數(shù)據(jù)傳輸對象:RequestResponse

@Data
public class CECRequest<T> {
? ?@JsonProperty("OperatorID")
? ?private String operatorID;
? ?@JsonProperty("Data")
? ?private T data;
? ?@JsonProperty("TimeStamp")
? ?private String timeStamp;
? ?@JsonProperty("Seq")
? ?private String seq;
? ?@JsonProperty("Sig")
? ?private String sig;
}
@Data
public class CECResponse<T> {
? ?private Integer Ret;
? ?private T Data;
? ?private String Msg;
}

這里使用@JsonProperty的原因是協(xié)議文檔字段的首字母都是大寫的,而我們一般的Java字段都是駝峰,為了在進(jìn)行JSON轉(zhuǎn)換的時候避免無法正常轉(zhuǎn)換。

然后,我們開始自定義編解碼器。這里不得不推薦下Hutool 這個類庫,是真的強大,因為涉及到的加解密和簽名生成,都是現(xiàn)成的。真香?。?!

編碼器

@Slf4j
public class CECEncoder extends SpringEncoder {
? ?private final CECOperatorProperties properties;
? ?private final HMac mac;
? ?private final AES aes;
? ?public CECEncoder(ObjectFactory<HttpMessageConverters> messageConverters,
? ? ? ? ? ? ? ? ? ? ?CECOperatorProperties properties) {
? ? ? ?super(messageConverters);
? ? ? ?this.properties = properties;
? ? ? ?this.mac = new HMac(HmacAlgorithm.HmacMD5,
? ? ? ? ? ? ? ?properties.getSigSecret().getBytes(StandardCharsets.UTF_8));
? ? ? ?this.aes = new AES(Mode.CBC, Padding.PKCS5Padding,
? ? ? ? ? ? ? ?properties.getDataSecret().getBytes(),
? ? ? ? ? ? ? ?properties.getDataIv().getBytes());
? ?}

? ?@Override
? ?public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
? ? ? ?// 數(shù)據(jù)加密
? ? ? ?String data = this.getEncrypt(requestBody);
? ? ? ?CECRequest<String> req = new CECRequest<>();
? ? ? ?req.setData(data);
? ? ? ?req.setSeq("0001");
? ? ? ?req.setTimeStamp(DateUtil.formatDate(DateUtil.now(), DateEnum.YYYYMMDDHHMMSS));
? ? ? ?req.setOperatorID(properties.getOperatorID());
? ? ? ?// 簽名計算
? ? ? ?String sig = this.getSig(req);
? ? ? ?req.setSig(sig.toUpperCase());
? ? ? ?super.encode(req, CECRequest.class.getGenericSuperclass(), request);
? ?}
? ?private String getEncrypt(Object requestBody){
? ? ? ?String json = JsonUtil.toJson(requestBody);
? ? ? ?return Base64.encode(aes.encrypt(json.getBytes()));
? ?}
? ?private String getSig(CECRequest<String> req){
? ? ? ?String str = req.getOperatorID() + req.getData() + req.getTimeStamp() + req.getSeq();
? ? ? ?return mac.digestHex(str);
? ?}
}

可以看到,我們的編碼器其實是繼承了SpringEncoder,因為在最終編碼之后,還是需要轉(zhuǎn)換為JSON發(fā)送給服務(wù)端,所以在繼承SpringEncoder之后,構(gòu)造器還需要注入ObjectFactory<HttpMessageConverters>的實例。另外,在構(gòu)造器我們也初始化了HMacAES兩個實例,一個為了生成簽名,一個為了加密業(yè)務(wù)數(shù)據(jù)。

encode方法,我們把傳遞進(jìn)來的requestBody包裝了下,先對其進(jìn)行加密,然后放在CECRequest實例的data字段內(nèi),并且生成對應(yīng)的簽名,最終請求服務(wù)端的時候是一個CECRequest實例的JSON化的結(jié)果。

可能有人會疑惑,為什么這里的requestBody就直接是業(yè)務(wù)數(shù)據(jù)了,而不是CECRequest<T>實例? 想想我們的第5點需求:在進(jìn)行第三方調(diào)用的時候需要像調(diào)用其他本地的Service一樣絲滑(行為一致)。為了實現(xiàn)這個需求,我們不會把非業(yè)務(wù)的參數(shù)暴露給業(yè)務(wù)調(diào)用放,而是在編解碼的過程中進(jìn)行處理。

解碼器

@Slf4j
public class CECDecoder extends SpringDecoder {
? ?private final AES aes;
? ?public CECDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
? ? ? ? ? ? ? ? ? ? ?CECOperatorProperties properties) {
? ? ? ?super(messageConverters);
? ? ? ?this.aes = new AES(Mode.CBC, Padding.PKCS5Padding,
? ? ? ? ? ? ? ?properties.getDataSecret().getBytes(),
? ? ? ? ? ? ? ?properties.getDataIv().getBytes());
? ?}
? ?@Override
? ?public Object decode(Response response, Type type) throws IOException, FeignException {
? ? ? ?CECResponse<String> resp = this.getCECResponse(response);
? ? ? ?// TODO 應(yīng)該做對應(yīng)的異常判斷然后拋出異常
? ? ? ?String json = this.aes.decryptStr(resp.getData());
? ? ? ?Response newResp = response.toBuilder().body(json, StandardCharsets.UTF_8).build();
? ? ? ?return super.decode(newResp, type);
? ?}
? ?private CECResponse<String> getCECResponse(Response response) throws IOException{
? ? ? ?try (InputStream inputStream = response.body().asInputStream()) {
? ? ? ? ? ?String json = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
? ? ? ? ? ?TypeReference<CECResponse<String>> reference = new TypeReference<CECResponse<String>>() {};
? ? ? ? ? ?return JSONUtil.toBean(json, reference.getType(), true);
? ? ? ?}
? ?}
}

解碼器會比較簡單,只需要進(jìn)行數(shù)據(jù)的解密即可。所以我們從Response中拿到對應(yīng)的JSON字符串,然后通過反序列化拿到CECResponse實例,接著做對應(yīng)的異常判斷(這里我的代碼暫時未實現(xiàn)),然后再做數(shù)據(jù)的解碼,拿到真正的業(yè)務(wù)數(shù)據(jù)的JSON字符串,最后通過OpenFeign提供的toBuilder方法重新構(gòu)造一個新的Response實例交給SpringDecoder進(jìn)行下一步的處理。

下一步,我們把編解碼器注冊到配置類中。完整的配置類信息如下

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CECOperatorProperties.class)
public class CECFeignClientConfig implements RequestInterceptor {
? ?@Autowired
? ?private CECOperatorProperties properties;
? ?@Autowired
? ?private ObjectFactory<HttpMessageConverters> messageConverters;
? ?@Bean
? ?Logger.Level feignLoggerLevel() {
? ? ? ?return Logger.Level.FULL;
? ?}
? ?@Bean
? ?public Encoder encoder(){
? ? ? ?return new CECEncoder(messageConverters, properties);
? ?}
? ?@Bean
? ?public Decoder decoder(){
? ? ? ?return new CECDecoder(messageConverters, properties);
? ?}

? ?@Override
? ?public void apply(RequestTemplate requestTemplate) {
? ? ? ?// TODO 添加Token
? ?}
}

完整的配置類會注入從RootApplicationContext中拿到的ObjectFactory<HttpMessageConverters>實例,另外再多配置了一個日志實例Logger.Level,用于在debug的時候打印請求的具體日志。

最后,我們來測試下我們的程序是否正常。簡單測試用例如下:

@Slf4j
public class CECTest extends BaseTest{
? ?@Autowired
? ?private CECTokenService tokenService;
? ?@Autowired
? ?private CECStationService stationService;
? ?@Autowired
? ?private CECOperatorProperties properties;

? ?@Test
? ?public void test(){
? ? ? ?QueryTokenReq req = new QueryTokenReq();
? ? ? ?req.setOperatorID(properties.getOperatorID());
? ? ? ?req.setOperatorSecret(properties.getOperatorSecret());
? ? ? ?QueryTokenResp resp = tokenService.queryToken(req);
? ? ? ?log.info("resp: {}", JsonUtil.toJson(resp));
? ?}
}

看到吧,是不是和調(diào)用本地的Service一樣絲滑? 只需要構(gòu)造對應(yīng)的入?yún)?,即可返回對?yīng)的出參,無需關(guān)心加密、簽名等煩人的操作。相關(guān)日志如下:

image.png

最后

對于使用OpenFeign來對接第三方系統(tǒng)我發(fā)現(xiàn)還是挺簡單的,起碼比自己手動去寫基本的加密、解密、JSON轉(zhuǎn)換、認(rèn)證等待,你會發(fā)現(xiàn)自己寫了一坨的代碼,代碼量可能還比較多,而用這個方式就簡單很多。

最后 Hutool YYDS!!!


又解鎖了一種OpenFeign的使用方式!的評論 (共 條)

分享到微博請遵守國家法律
五峰| 探索| 新蔡县| 鸡东县| 安平县| 滨海县| 安阳市| 英吉沙县| 楚雄市| 泉州市| 枝江市| 建宁县| 大埔区| 阳春市| 长乐市| 辽阳县| 沅陵县| 额济纳旗| 遵义县| 张家口市| 大同市| 盘山县| 黄大仙区| 娱乐| 舞阳县| 德兴市| 临汾市| 盐池县| 青铜峡市| 巴林右旗| 阿合奇县| 偏关县| 绥阳县| 鹰潭市| 宁波市| 巧家县| 晋州市| 磴口县| 海安县| 磐安县| 锦州市|