国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

catface,使用Interface定義Controller,實(shí)現(xiàn)基于Http協(xié)議的RPC調(diào)用

這篇具有很好參考價(jià)值的文章主要介紹了catface,使用Interface定義Controller,實(shí)現(xiàn)基于Http協(xié)議的RPC調(diào)用。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。






前言

關(guān)鍵詞:Interface定義Controller;feign服務(wù)端;feign interface;Http RPC;cat-client;cat-server;catface;

概???要:catface,使用類似FeignClient的Interface作為客戶端發(fā)起Http請(qǐng)求,然后在服務(wù)端使用實(shí)現(xiàn)了這些Interface的類作為Controller角色,將客戶端、服務(wù)端通過Interface耦合在一起,實(shí)現(xiàn)無感知調(diào)用的輕量級(jí)組件。其底層通訊協(xié)議依舊是Http,支持Http的所有特性:證書、負(fù)載均衡、熔斷、路由轉(zhuǎn)發(fā)、報(bào)文日志、swagger等;

如果使用過dubbo就比較好理解,和dubbo調(diào)用非常類似。只不過catface只支持Http協(xié)議,并且目前只有spring項(xiàng)目可以使用。


catface的靈感來于FeignClient,想必其他同學(xué)在看見feignClient的Interface的時(shí)候,應(yīng)該都有一個(gè)想法:feign interface使用的注解,和Controller有很是多共用,那么能不能直接用使用這些Interface來定義Controller呢?


舉個(gè)例子
先粗略看一下feign的接口IDemoService
為了后續(xù)避免歧義,此處我們將類似于IDemoService的Interface,稱呼為feign-interface

public interface IDemoService {

    @RequestMapping(value = "/server/demo41", method = RequestMethod.POST)
    Demo demo1(@RequestBody Demo req);
    
    @RequestMapping(value = "/server/demo43", method = RequestMethod.GET)
    List<Demo> demo3(@ModelAttribute Demo req);
    
    @RequestMapping(value = "/server/demo44", method = RequestMethod.POST)
    ResponseEntity<Demo> demo4(@RequestParam("userName") String name, @RequestParam("userMark") String mark);
    
    @RequestMapping(value = "/server/demo46/{uid}", method = RequestMethod.GET)
    Void demo6(@PathVariable("uid") Long userId, @RequestParam("userName") String name);
    
    @RequestMapping(value = "/server/demo47", method = RequestMethod.PUT)
    Demo demo7(@RequestHeader("token") String token);
}

FeignClient客戶端,在spring項(xiàng)目中,只需要把IDemoService作為一個(gè)普通的Service類使用,自動(dòng)注入到其他Bean中,就可以直接執(zhí)行IDemoService類中的方法,F(xiàn)eignClient便會(huì)自動(dòng)根據(jù)方法上的注解信息,發(fā)起Http請(qǐng)求;

FeignClient服務(wù)端:沒有嚴(yán)格意義上的FeignClient服務(wù)端!在spring項(xiàng)目中一般是Controller提供的接口,返回相應(yīng)的報(bào)文;



如果是公司內(nèi)部系統(tǒng)相互調(diào)用,例如:clientA調(diào)用serverB
在clientA中定義若干feign-interface作為客戶端,并定義接口方法的入?yún)?、響?yīng)類;
在serverB中定義Controller、以及對(duì)應(yīng)DTO、VO模型;

可以發(fā)現(xiàn):
:: serverB中的DTO、VO,其數(shù)據(jù)結(jié)構(gòu),和feign-interface方法上入?yún)?、響?yīng)類是一致的;
:: clientA與serverB,在代碼層面上沒有任何聯(lián)系。無論是feign-interface的方法入?yún)ⅰ㈨憫?yīng)類增減字段,都不會(huì)影響到DTO、VO模型;反之也是一樣;


如果存在feign-interface可以直接定義Controller這個(gè)功能,那么上述一般流程,可以修改成如下模式:
新增一個(gè)接口層:serverB-facede,表示由serverB模塊對(duì)外提供服務(wù);
其中:
:: serverB-facede中包含:feign-interface、方法的入?yún)?、響?yīng)類;
:: clientA依賴serverB-facede模塊,在clientA中依舊可以使用feign-interface客戶端;
:: serverB同樣依賴serverB-facede模塊,在serverB中實(shí)現(xiàn)feign-interface接口,將實(shí)現(xiàn)類注冊(cè)成Controller;

如此以來,clientA和serverB,便通過serverB-facede耦合在一起??瓷先ゾ拖袷窃赾lientA模塊中,自動(dòng)注入了一個(gè)feign-interface類,執(zhí)行其中的方法,就可以得到serverB模塊中實(shí)現(xiàn)類返回的數(shù)據(jù)!Http調(diào)用對(duì)于消費(fèi)者、提供者,都是無感知的;



  clientA                   調(diào)用                     serverB	                   
 服務(wù)消費(fèi)者   ===================================>   服務(wù)提供者           
	 │                                                 │ 
	 │  注入                                            │         
	 │                                                 │ 
	 └──────────────   feign-interface                 │                                 
       	                       │                       │   
                               └───────────────────────┤   
                                                       │
	                                              實(shí)現(xiàn)  │
                                                       │
	                                        feign-interface 實(shí)現(xiàn)類



如果了解spring掃描Bean的原理,在服務(wù)端根據(jù)feign-interface上的注解,手動(dòng)注冊(cè)Controller不難;而且在熟悉動(dòng)態(tài)代理的情況下,自己根據(jù)feign-interface寫一個(gè)http RPC也不是不可能;

于是在技術(shù)實(shí)現(xiàn)都不難的情況下,catface就出現(xiàn)辣~:

項(xiàng)目地址:https://github.com/bugCats/cat-client-all



catface基于spring框架開發(fā)的 (不會(huì)吧不會(huì)吧,在如今spring大有一統(tǒng)江湖的趨勢(shì),還會(huì)有新項(xiàng)目沒有使用spring全家桶的吧?),通過Interface、以及其方法的輸入返回模型,耦合客戶端與服務(wù)端,實(shí)現(xiàn)無感知Http調(diào)用。
對(duì)于開發(fā)者而言,眼前沒有客戶端、服務(wù)端,只有Interface調(diào)用者和Interface的實(shí)現(xiàn)類:

  1. 精細(xì)化到每個(gè)接口的輸入輸出報(bào)文記錄方案;
    比如某些API是核心流程,需要記錄詳細(xì)的輸入輸出報(bào)文,以便如果后續(xù)出現(xiàn)問題,可以查閱日志內(nèi)容;而有些API不是那么重要,但調(diào)用又非常頻繁,此時(shí)我們不希望打印它們,以免浪費(fèi)系統(tǒng)性能、浪費(fèi)存儲(chǔ)空間;對(duì)于一般的API,平時(shí)的時(shí)候就記錄一下調(diào)用記錄,如果發(fā)生Http異常、或返回了業(yè)務(wù)異常,就記錄詳細(xì)的輸入輸出報(bào)文;

  2. 精細(xì)化到每個(gè)接口的Http等待時(shí)間:
    對(duì)于中臺(tái)而言,調(diào)用后臺(tái)接口,不可能無限等待后臺(tái)接口響應(yīng);和日志一樣的邏輯,有的API等待時(shí)間可以長(zhǎng)點(diǎn),有的又不行;

  3. 自動(dòng)加、拆響應(yīng)包裝器類
    當(dāng)服務(wù)端的所有響應(yīng),為統(tǒng)一的一個(gè)數(shù)據(jù)模型、具體的業(yè)務(wù)數(shù)據(jù),是該模型通過泛型確認(rèn)的屬性時(shí),例如:HttpEntity<User>、ResponesDTO<User>,稱這種似于HttpEntity、ResponesDTO的數(shù)據(jù)結(jié)構(gòu)為響應(yīng)包裝器類;

    public class ResponesDTO<T> {
    	private String code; 	// code為1表示成功,非1表示失敗
    	private String message;	//異常原因說明
    	private T data;			//業(yè)務(wù)數(shù)據(jù)
    }
    

    當(dāng)客戶端獲取到響應(yīng)對(duì)象ResponesDTO<User>之后:
    1. 需要先判斷對(duì)象是否為null;
    2. 再判斷code是否為1,不為1需要進(jìn)入異常流程;
    3. 最后才能獲取到業(yè)務(wù)數(shù)據(jù)User;

    catface支持自動(dòng)拆響應(yīng)包裝器類。當(dāng)啟用后,客戶端feign-interface的方法返回?cái)?shù)據(jù)類型:
    :: 可以是ResponesDTO<User>,保持原流程不變;
    :: 直接為User,而服務(wù)端無需做任何修改。當(dāng)feign-interface的方法成功執(zhí)行完并返回了User對(duì)象,表示Http調(diào)用成功,并且響應(yīng)的code一定是1;如果調(diào)用失敗、或者code不為1,會(huì)自動(dòng)進(jìn)入預(yù)先設(shè)置好的異常流程;

    加響應(yīng)包裝器類,針對(duì)服務(wù)端而言,是拆包裝器類的逆操作。當(dāng)服務(wù)端啟用之后,feign-interface和與之對(duì)應(yīng)的Controller方法響應(yīng)的數(shù)據(jù)類型,可以直接是User,catface會(huì)自動(dòng)在最外層加上ResponesDTO;

    另外,對(duì)于使用繼承實(shí)現(xiàn)公共屬性code、message和業(yè)務(wù)數(shù)據(jù)并列的情況,也同樣適用于此功能;

  4. 自定義標(biāo)記功能:
    在feign-interface上添加自定義標(biāo)記、還可以為單個(gè)API接口添加標(biāo)記。結(jié)合攔截器、springEL動(dòng)態(tài)解析入?yún)?,便可以靈活實(shí)現(xiàn)各種各樣的業(yè)務(wù)邏輯;

  5. 在服務(wù)端,通過繼承實(shí)現(xiàn)API接口升級(jí):
    在Java中,繼承本身就可以增強(qiáng)父類功能。此特性也適用于catface,通過繼承特性對(duì)服務(wù)端API接口升級(jí)增強(qiáng),而客戶端無需任何修改;

  6. 通過feign-interface生成的Controller,依舊支持swagger;

  7. 其他比較一般的功能:攔截器、修改http Jar包、負(fù)載均衡、熔斷、異常重試、Mock測(cè)試等等;






cat-client 模塊

此模塊可以單獨(dú)使用,使用方式和feignclient非常類似;以feign-interface為模板動(dòng)態(tài)生成Http調(diào)用實(shí)現(xiàn)類,在應(yīng)用層自動(dòng)注入feign-interface對(duì)象,執(zhí)行feign-interface中的方法,即可發(fā)起http請(qǐng)求,并最終將Http響應(yīng)結(jié)果,轉(zhuǎn)換成方法返回對(duì)象數(shù)據(jù)類型,返回給應(yīng)用層;


@EnableCatClient

這個(gè)注解表示啟用CatClient客戶端。該注解依賴于spring容器,置于任何spring bean之上,例如:springboot項(xiàng)目啟動(dòng)類上、或者任意包含@Component注解的類上;

  • value: 掃描的包路徑,處于這個(gè)目錄中的feign-interface都會(huì)被注冊(cè)成客戶端。默認(rèn)值是被標(biāo)記類的同級(jí)目錄;
  • classes: 指定客戶端class進(jìn)行注冊(cè),優(yōu)先度高于包路徑。class值分為2大類:
    :: 普通的class: 當(dāng)類上包含@CatClient注解時(shí),將該class注冊(cè)成客戶端;
    :: CatClientProvider的子類: 解析該子類中包含@CatClient注解的方法,將方法返回對(duì)象的class注冊(cè)成客戶端;
  • configuration: 生成客戶端的一些配置項(xiàng)和默認(rèn)值;

@CatClient

該注解用于定義某個(gè)feign-interface為客戶端,包含:客戶端別名、遠(yuǎn)程服務(wù)端host、攔截器、異?;卣{(diào)、http等待時(shí)間、默認(rèn)的API日志記錄方案;定義CatClient客戶端有2種方式:

方式1: 在feign-interface上添加@CatClient;系統(tǒng)啟動(dòng)時(shí),會(huì)根據(jù)@EnableCatClient的配置,自動(dòng)掃描并注冊(cè)成客戶端:

@CatClient(host = "${userService.remoteApi}", connect = 3000, logs = RequestLogs.All2)
public interface IUserService {

    ResponseEntity<PageInfo<UserInfo>> userPage(@RequestHeader("token") String token, @ModelAttribute UserPageVi vi);
    
    UserInfo userInfo(@PathVariable("uid") String uid, @RequestParam("type") String type);
    
    ResponseEntity<Void> userSave(@RequestBody UserSaveVi vi);
}

方式2: 通過CatClientProvider的子類,集中批量定義。將包含@CatClient注解的方法其返回對(duì)象的class注冊(cè)成客戶端;

public interface RemoteProvider extends CatClientProvider {

    @CatClient(host = "${userService.remoteApi}", connect = 3000, socket = 3000)
    IUserService userService(); //實(shí)際上將IUserService注冊(cè)成客戶端
    
    @CatClient(host = "${orderService.remoteApi}")
    IOrderService orderService(); //將IOrderService注冊(cè)成客戶端
}
  • value: 客戶端組件的別名,默認(rèn)首字母小寫。最終客戶端會(huì)注冊(cè)到spring容器中,可以通過@Autowired實(shí)現(xiàn)自動(dòng)注入;
  • host: http請(qǐng)求的主機(jī)地址??梢允?IP+端口,也可以是域名、cloud服務(wù)名:
    :: 字面量: https://www.bugcat.cc
    :: 配置文件值: ${xxx.xxx}
    :: 服務(wù)名,配合注冊(cè)中心: http://myserver-name/ctx
  • interceptor: http請(qǐng)求攔截器;用來修改入?yún)ⅰ⒄?qǐng)求url、修改參數(shù)簽名、添加token等處理;默認(rèn)值受CatClientConfiguration控制;
  • factory: 負(fù)責(zé)創(chuàng)建發(fā)送Http請(qǐng)求相關(guān)對(duì)象的工廠類;一般如果僅需修改入?yún)ⅰ⒒蛘咛砑雍灻?,可以使用攔截器修改。如果有比較大的Http流程調(diào)整,才考慮修改CatClientFactory,例如:添加負(fù)載均衡;默認(rèn)值受CatClientConfiguration控制;
  • fallback: 異常處理類;當(dāng)接口發(fā)生http異常(40x、50x),執(zhí)行的回調(diào)方法。如果在回調(diào)方法中,繼續(xù)拋出異常,或者關(guān)閉回調(diào)模式,則會(huì)執(zhí)行CatResultProcessor#onHttpError進(jìn)行最終兜底處理;其值可以取以下類型:
    :: Object.class: 嘗試使用feign-interface默認(rèn)方法,如果feign-interface沒有默認(rèn)實(shí)現(xiàn),再執(zhí)行兜底方法;
    :: Void.class: 關(guān)閉回調(diào)模式,直接執(zhí)行兜底方法;
    :: 其他class值: 必須實(shí)現(xiàn)該feign-interface。當(dāng)發(fā)生異常之后,執(zhí)行實(shí)現(xiàn)類的對(duì)應(yīng)方法;
  • socket: http讀值超時(shí)毫秒,-1 代表不限制;默認(rèn)值受CatClientConfiguration控制;
  • connect: http鏈接超時(shí)毫秒,-1 代表不限制;默認(rèn)值受CatClientConfiguration控制;
  • logsMod: 記錄日志方案;默認(rèn)值受CatClientConfiguration控制;
  • tags: 分組標(biāo)記,給客戶端添加自定義標(biāo)記;詳細(xì)使用見CatNote;

@CatMethod

定義客戶端feign-interface中的方法,為API接口;

  • value: 具體的url;
    :: 字面量: /qq/972245132
    :: 配置文件值: ${xxx.xxx}
    :: uri類型參數(shù): /qq/{pathVariable}
  • method: Http請(qǐng)求方式,默認(rèn)使用POST發(fā)送表單;
  • notes: 自定義參數(shù)、或標(biāo)記;當(dāng)方法上還存在@CatNotes注解時(shí),會(huì)忽略這個(gè)屬性值!詳細(xì)使用見CatNotes;
  • socket: http讀值超時(shí)毫秒;-1 不限;0 同當(dāng)前feign-interface配置;其他數(shù)值,超時(shí)的毫秒數(shù);
  • connect: http鏈接超時(shí)毫秒;-1 不限;0 同當(dāng)前feign-interface配置;其他數(shù)值,超時(shí)的毫秒數(shù);
  • logsMod: 日志記錄方案;Def 同當(dāng)前feign-interface配置;

對(duì)于常用的GET、POST請(qǐng)求方式,還有@CatGet、@CatPost2個(gè)便捷組合注解;


@CatNote

自定義標(biāo)記注解;有2大類使用場(chǎng)景:

  1. 為feign-interface、方法、參數(shù),添加標(biāo)記;
    • @CatNote(key=“name”, value=“bugcat”): 字面量;創(chuàng)建了一個(gè)標(biāo)記,標(biāo)記名=name,標(biāo)記值=bugcat;
    • @CatNote(“bugcat”): 字面量;省略key屬性,最終key與value值相同,即標(biāo)記名=標(biāo)記值=bugcat;
    • @CatNote(key=“host”, value=“${orderhost}”): 取配置文件值;創(chuàng)建了一個(gè)標(biāo)記,標(biāo)記名=host,標(biāo)記值從配置中獲取orderhost對(duì)應(yīng)的值;
    • @CatNote(key=“userId”, value=“#{req.userId}”): 取方法入?yún)⒅担皇褂胹pringEL表達(dá)式,從方法入?yún)?duì)象中獲取標(biāo)記值。此種方法,必須要為入?yún)⑷e名;

  1. 為方法入?yún)⑷e名;一般配合@RequestBody使用,可以實(shí)現(xiàn) #{參數(shù)別名.屬性} 動(dòng)態(tài)獲取入?yún)⒌膶傩灾?;可為入?yún)⑷e名注解有:
    • @ModelAttribute(“paramName”): 為GET、POST表單對(duì)象取別名;
    • @PathVariable(“paramName”): PathVariable類型參數(shù)別名;
    • @RequestParam(“paramName”): 鍵值對(duì)參數(shù)別名;
    • @RequestHeader(“paramName”): 請(qǐng)求頭參數(shù)別名;
    • @CatNote(“paramName”): 通用類型參數(shù)別名,一般結(jié)合@RequestBody使用;

@CatResponesWrapper

為當(dāng)前feign-interface,配置加、拆響應(yīng)包裝器類;由于這個(gè)注解是標(biāo)記在feign-interface接口上,因此如果feign-interface是作為客戶端,那么@CatResponesWrapper便是啟用拆響應(yīng)包裝器類功能;如果是作為服務(wù)端Controller,則是加響應(yīng)包裝器類

  • value: Http響應(yīng)包裝器類處理類;

CatClientConfiguration

生成客戶端的一些配置項(xiàng)和默認(rèn)值。配合@EnableCatClient使用,可用于修改@CatClient、@CatResponesWrapper注解的實(shí)際默認(rèn)值;

例如:@CatClient#socket的默認(rèn)值為1000。如果需要統(tǒng)一修改成3000,而不想在每個(gè)feign-interface客戶端上修改socket=3000,可以重寫CatClientConfiguration#getSocket方法,使其返回3000即可;

  • getSocket: http讀值超時(shí),默認(rèn)1000毫秒;對(duì)應(yīng)@CatClient#socket;
  • getConnect: http鏈接超時(shí),默認(rèn)1000毫秒;對(duì)應(yīng)@CatClient#connect;
  • getLogsMod: API接口輸入輸出報(bào)文記錄方案,默認(rèn)是當(dāng)發(fā)生Http異常時(shí)記錄;對(duì)應(yīng)@CatClient#logsMod
  • getWrapper: 拆包裝器類處理類,對(duì)應(yīng)@CatResponesWrapper#value;
  • getMethodInterceptor: http請(qǐng)求攔截器,對(duì)應(yīng)@CatClient#interceptor;
  • getClientFactory: 創(chuàng)建客戶端發(fā)送Http請(qǐng)求相關(guān)對(duì)象的工廠類,對(duì)應(yīng)@CatClient#factory;
  • getCatHttp: Http請(qǐng)求發(fā)送工具類,默認(rèn)使用RestTemplate
  • getPayloadResolver: Http請(qǐng)求輸入輸出對(duì)象,序列化與反序列化處理類;默認(rèn)使用Jackson框架;
  • getLoggerProcessor: 打印Http日志類;默認(rèn)使用logback框架。如果需要修改日志打印格式,可以實(shí)現(xiàn)CatLoggerProcessor接口;

CatClientProvider

批量注冊(cè)客戶端類;

public interface RemoteProvider extends CatClientProvider {

    @CatClient(host = "${userService.remoteApi}", connect = 3000, socket = 3000)
    IUserService userService(); //實(shí)際上將IUserService注冊(cè)成客戶端
    
    @CatClient(host = "${orderService.remoteApi}")
    IOrderService orderService(); //將IOrderService注冊(cè)成客戶端
}

采用此方法定義客戶端,將@CatClient置于CatClientProvider子類方法之上,使得@CatClient注解與feign-interface類在物理上分隔開,避免注解污染feign-interface,以便可以多次復(fù)用feign-interface;

配合@EnableCatClient#classes使用,可以集中處理,按需加載使用。特別適用于多模塊、多客戶端feign-interface的場(chǎng)景;

例如:在serverB-facede模塊中,與非常多的feign-interface,其中若干個(gè)feign-interface實(shí)現(xiàn)一個(gè)完整的業(yè)務(wù)流程。
為了避免多個(gè)消費(fèi)端,需要多次手動(dòng)注冊(cè)多個(gè)feign-interface客戶端,可以在serverB-facede模塊中創(chuàng)建一個(gè)CatClientProvider子類,將相關(guān)feign-interface在其子類中預(yù)先定義好。
消費(fèi)端在@EnableCatClient#classes中指定該子類,即可實(shí)現(xiàn)批量注冊(cè)feign-interface客戶端;


CatClientFactory

創(chuàng)建客戶端發(fā)送Http請(qǐng)求相關(guān)對(duì)象的工廠類。CatClientConfiguration中的參數(shù)適用于全局的默認(rèn)配置。如果存在部分feign-interface客戶端,需要特別的個(gè)性化配置,就需要使用自定義CatClientFactory,返回個(gè)性化的配置項(xiàng);可以實(shí)現(xiàn)CatClientFactory接口,或者繼承SimpleClientFactory,再修改@CatClient#factory參數(shù)值;

@CatClient(host = “${orderhost}”, factory = TokenFactory.class)

  • getCatHttp: 自定義Http發(fā)送類;
  • getPayloadResolver: 自定義入?yún)㈨憫?yīng)序列化與反序列類;
  • getLoggerProcessor: 自定義日志格式打印類;
  • getResultHandler: 自定義http響應(yīng)處理類;
  • newSendHandler: 自定義http發(fā)送流程類;返回對(duì)象必須是多例!

CatSendInterceptor

Http發(fā)送請(qǐng)求流程中的攔截器;可以重寫CatClientConfiguration#getMethodInterceptor方法修改全局默認(rèn)的攔截器;也可以通過@CatClient#interceptor為指定的feign-interface修改;

全局?jǐn)r截器,和自定義攔截器只能生效一個(gè)!(支持多個(gè)攔截器下下下個(gè)版本再加)


攔截器有4個(gè)切入點(diǎn),對(duì)應(yīng)CatSendProcessor的四個(gè)方法:

  1. executeConfigurationResolver: 處理http配置項(xiàng)前后;對(duì)應(yīng)CatSendProcessor#doConfigurationResolver方法,處理Http請(qǐng)求相關(guān)配置:host、url、讀取超時(shí)、請(qǐng)求方式、請(qǐng)求頭參數(shù)、解析自定義標(biāo)記;
  2. executeVariableResolver: 處理入?yún)?shù)據(jù)前后;對(duì)應(yīng)CatSendProcessor#doVariableResolver、CatSendProcessor#postVariableResolver2個(gè)方法:如果在調(diào)用遠(yuǎn)程API,需要額外處理參數(shù)、或添加簽名等,可以在此處添加;
    :: doVariableResolver,入?yún)⑥D(zhuǎn)字符串、或表單對(duì)象;
    :: postVariableResolver,入?yún)⑥D(zhuǎn)換之后處理,這是給子類提供重寫的方法;
  3. executeHttpSend: 發(fā)送Http請(qǐng)求前后;對(duì)應(yīng)CatSendProcessor#postHttpSend方法;如果啟用重連,那么該方法會(huì)執(zhí)行多次!
  4. postComplete: 在成功、異常回調(diào)方法之后、拆響應(yīng)包裝器之前執(zhí)行;此處可以再次對(duì)響應(yīng)對(duì)象進(jìn)行修改;對(duì)于異常流程,可以繼續(xù)拋出異常;

調(diào)用流程示意

CatMethodAopInterceptor#intercept
						│
		CatClientContextHolder#executeConfigurationResolver
						│
				CatSendProcessor#doConfigurationResolver  <-------  CatSendInterceptor#executeConfigurationResolver
						│
		CatClientContextHolder#executeVariableResolver
						│
				CatSendProcessor#doVariableResolver  <----┬----  CatSendInterceptor#executeVariableResolver
						│                                 :
				CatSendProcessor#postVariableResolver  <--┘
						│
		CatClientContextHolder#executeRequest
						│
				CatSendProcessor#postHttpSend  <-------  CatSendInterceptor#executeHttpSend
						│
	    [[CatResultProcessor#onHttpError]]
						│
		CatResultProcessor#resultToBean
						│
		CatClientContextHolder#postComplete  <------- CatSendInterceptor#postComplete
						│
		CatResultProcessor#onFinally
						│
return  <───────────────┘


CatHttp

Http發(fā)送請(qǐng)求工具類,默認(rèn)實(shí)現(xiàn)類CatRestHttp,底層使用RestTemplate發(fā)送請(qǐng)求。優(yōu)先是從spring容器中獲取RestTemplate,如果spring容器中沒有,才會(huì)自動(dòng)創(chuàng)建。

因此,如果spring容器中的RestTemplate配置了負(fù)載均衡,那么對(duì)應(yīng)的CatRestHttp同樣也有負(fù)載均衡特性!

如果需要修改成其他Http框架,可以如下操作:首先需要實(shí)現(xiàn)CatHttp接口;再將實(shí)現(xiàn)類編織到客戶端Http發(fā)送流程中:
方案1:將CatHttp實(shí)現(xiàn)類對(duì)象,注冊(cè)到spring容器中;適用于全局;
方案2:重寫CatClientConfiguration#getCatHttp方法,使其返回指定CatHttp對(duì)象;適用于全局;
方案3:對(duì)于特定的API接口,可以利用CatClientFactory。在定義feign-interface客戶端時(shí),修改@CatClient#factory值為指定CatClientFactory子類,再重寫CatClientFactory#getCatHttp方法,返回自定義CatHttp實(shí)現(xiàn)類對(duì)象;


CatPayloadResolver

針對(duì)于使用POST方式發(fā)送IO流情況,將輸入輸出對(duì)象,序列化與反序列化的處理類。如果是GET、POST發(fā)送表單數(shù)據(jù),應(yīng)該在CatHttp層進(jìn)行統(tǒng)一的uri編碼。

默認(rèn)使用Jackson框架,catface中還內(nèi)置了Fastjson框架處理類。如果需要使用其他框架、或者使用xml,可以自行實(shí)現(xiàn)CatPayloadResolver接口,實(shí)現(xiàn)類編織到Http發(fā)送流程中方式,和自定義CatHttp一致;


CatObjectResolver

將feign-interface方法上的復(fù)雜數(shù)據(jù)類型入?yún)ⅲD(zhuǎn)成表單對(duì)象;

catface不建議使用POST、GET發(fā)送太過于復(fù)雜的表單對(duì)象,推薦使用POST + Json這種一般方式。

雖然內(nèi)置了入?yún)?shù)據(jù)轉(zhuǎn)表單對(duì)象的處理類,但是對(duì)于怪異的場(chǎng)景如果出現(xiàn)不支持情況,就需要自行編寫轉(zhuǎn)換類。實(shí)現(xiàn)CatObjectResolver接口,再執(zhí)行CatSendProcessor#setObjectResolverSupplier方法手動(dòng)賦值;CatSendProcessor對(duì)象可以在攔截器中獲取得到;


CatLoggerProcessor

調(diào)用API接口的日志記錄處理類??梢宰孕锌刂迫罩敬蛴〖?jí)別,以及日志格式;


CatResultProcessor

Http響應(yīng)處理類:

  • onHttpError: 當(dāng)發(fā)生Http異常后默認(rèn)執(zhí)行流程,最終進(jìn)行兜底的異常處理;
  • canRetry: 異常是否需要重連,如果開啟重連,并且發(fā)送Http異常,判斷是否需要重新請(qǐng)求。一般結(jié)合熔斷器或者注冊(cè)中心使用,可以重新選擇一個(gè)健康的服務(wù)端實(shí)例;
  • resultToBean: 響應(yīng)報(bào)文轉(zhuǎn)結(jié)果對(duì)象,默認(rèn)使用Jackson框架。如果是通過xml傳輸信息,需要自行實(shí)現(xiàn)CatPayloadResolver接口;
  • onFinally: 自動(dòng)拆響應(yīng)包裝器類,結(jié)合響應(yīng)包裝器類使用,可以使API接口方法,直接返回業(yè)務(wù)對(duì)象;

只能通過自定義CatClientFactory#getResultHandler修改,為單例;


CatSendProcessor

Http發(fā)送請(qǐng)求核心處理類;該對(duì)象可以通過CatClientFactory#newSendHandler自動(dòng)創(chuàng)建,也支持手動(dòng)創(chuàng)建后,作為feign-interface方法的入?yún)魅搿?/p>

CatSendProcessor類雖然提供了擴(kuò)展的入口,但一般情況下無需修改,如果對(duì)Http請(qǐng)求整體流程有比較大的修改,才考慮覆蓋重寫。例如:搭配注冊(cè)中心、負(fù)載均衡器使用、或者換成Socket協(xié)議等。(負(fù)載均衡也可以使用RestTemplate實(shí)現(xiàn))

若僅僅是修改入?yún)ⅰ⑻砑觮oken、簽名,可以直接使用更輕量級(jí)的攔截器實(shí)現(xiàn)。

  • doConfigurationResolver: 初始化http相關(guān)配置??尚薷捻?xiàng)包括:
    :: Http連接讀取超時(shí):
    :: Http請(qǐng)求方式: 雖然在@CatClient中聲明是使用POST發(fā)送Json字符串,但是此處也可以修改成POST發(fā)送表單方式;
    :: 遠(yuǎn)程服務(wù)端host、API調(diào)用的url: 可以使用@PathVariable、取環(huán)境配置參數(shù)${xxx}等方式動(dòng)態(tài)給url賦值,也可以在此方法中修改host、url;
    :: 自定義的標(biāo)記數(shù)據(jù): 根據(jù)@CatNote標(biāo)記,獲取到環(huán)境配置參數(shù)、動(dòng)態(tài)取入?yún)⒌膶傩灾?,可以?duì)Host、url、入?yún)?、?qǐng)求方式等,執(zhí)行更自由修改;例如,集成負(fù)載均衡、自定義路由規(guī)則;
  • doVariableResolver: 請(qǐng)求入?yún)⒌哪J(rèn)處理。如果是POST、GET表單方式,將方法入?yún)⑥D(zhuǎn)成表單對(duì)象;如果POST發(fā)送IO流,則將入?yún)?duì)象序列化成字符串;此方法可以將入?yún)⒛P停薷某杀韱螌?duì)象;轉(zhuǎn)成Json字符串;添加公共參數(shù);添加Token;計(jì)算簽名;記錄請(qǐng)求信息;
  • doVariableResolver: 處理入?yún)⒑筇幚?。默認(rèn)是個(gè)空方法,提供給子類重寫。
  • postHttpSend: 全部數(shù)據(jù)準(zhǔn)備充分之后,發(fā)起Http請(qǐng)求;如果需要換成其他協(xié)議,如Socket,重寫此方法,將最終響應(yīng)結(jié)果存儲(chǔ)到CatClientContextHolder#setResponseObject中;

AbstractResponesWrapper

響應(yīng)包裝器類處理類;

  • getWrapperClass: 獲取響應(yīng)包裝器類的class;用于判斷API接口方法的響應(yīng)對(duì)象,是包裝器類、還是直接業(yè)務(wù)數(shù)據(jù)類;
  • getWrapperType: 將業(yè)務(wù)數(shù)據(jù)類型Type,組裝到響應(yīng)包裝器類中,返回標(biāo)準(zhǔn)響應(yīng)Type的引用;例如,包裝器類是ResponseDTO<T>,業(yè)務(wù)數(shù)據(jù)類型為User,API接口返回的原始數(shù)據(jù)類型應(yīng)該為ResponseDTO<User>。對(duì)于使用拆包裝器類的API接口方法,需要將方法的響應(yīng)類(即業(yè)務(wù)數(shù)據(jù)類型),組裝到包裝器類中。
    public <T> CatTypeReference getWrapperType(Type type){
    	//type 為業(yè)務(wù)數(shù)據(jù)的類型,可以是User、List<Order>、Long、String[]等
    	//ResponseDTO是響應(yīng)包裝器類,type最終會(huì)替換掉T的位置,最終結(jié)果是ResponseDTO<Type>
    	//注意后面的一對(duì)花括號(hào)不能少!
        return new CatTypeReference<ResponseDTO<T>>(type){};  
    }
    
  • checkValid: 校驗(yàn)返回的業(yè)務(wù)數(shù)據(jù)是否正確。需要注意異常分為兩大類:
    :: Http異常 由http請(qǐng)求造成的異常,例如:403 404 500 503,讀取超時(shí)等,此類異常可以重新連接,或者換遠(yuǎn)程服務(wù)實(shí)例調(diào)用;
    :: 業(yè)務(wù)異常 此類為服務(wù)端接收到請(qǐng)求,并且通過業(yè)務(wù)邏輯判斷,得出不處理該請(qǐng)求,并將消息成功返回給Http調(diào)用者。例如:調(diào)用取消訂單,服務(wù)端判斷該訂單已經(jīng)使用,不可以取消。API接口調(diào)用成功,但是響應(yīng)為邏輯處理失敗。
    checkValid方法主要是針對(duì)于業(yè)務(wù)異常場(chǎng)景,當(dāng)發(fā)生業(yè)務(wù)異常時(shí),程序該如何處理?可以選擇繼續(xù)拋出,由最頂層的@ControllerAdvice統(tǒng)一處理異常;也可以自行解析業(yè)務(wù)異常編碼,再修改返回默認(rèn)業(yè)務(wù)對(duì)象;
  • getValue: 從響應(yīng)包裝器類中獲取業(yè)務(wù)數(shù)據(jù);
  • createEntryOnSuccess: 服務(wù)器端加響應(yīng)包裝器類,當(dāng)成功執(zhí)行;
  • createEntryOnException: 服務(wù)器端加響應(yīng)包裝器類,當(dāng)異常執(zhí)行;

無論是CatResultProcessor#onHttpError繼續(xù)拋出、還是AbstractResponesWrapper#checkValid校驗(yàn)失敗拋出,都會(huì)造成應(yīng)用層調(diào)用feign-interface方法發(fā)生異常。

但是在定義feign-interface方法時(shí),方法可以不顯示拋出異常,因此在調(diào)用時(shí),應(yīng)當(dāng)清楚feign-interface方法會(huì)隱式拋出異常,需要注意如果發(fā)生異常該如何處理。

當(dāng)應(yīng)用層執(zhí)行feign-interface方法后,希望無論是成功還是失敗,都要有結(jié)果返回,然后應(yīng)用層再根據(jù)執(zhí)行結(jié)果,自行處理異常,那么不應(yīng)該使用自動(dòng)拆包裝器類!
僅當(dāng)應(yīng)用層執(zhí)行方法后,對(duì)于異常流程沒有嚴(yán)格要求時(shí),才會(huì)建議使用!


CatClientBuilders

靜態(tài)方法創(chuàng)建CatClient客戶端。可以在非spring環(huán)境中使用,也可以在運(yùn)行過程中手動(dòng)創(chuàng)建,或者單元測(cè)試時(shí)期使用;


CatHttpRetryConfigurer

當(dāng)發(fā)生Http異常時(shí),重新連接策略:

  • enable: 是否開啟重連;默認(rèn)false;
  • retries: 重連次數(shù),不包含第一次調(diào)用!默認(rèn)2,實(shí)際上最多會(huì)調(diào)用3次;
  • status: 重連的狀態(tài)碼:多個(gè)用逗號(hào)隔開;可以為500,501,401400-410,500-519,419*any,默認(rèn)500-520;
  • method: 需要重連的請(qǐng)求方式,多個(gè)用逗號(hào)隔開;可以為post,get*any,默認(rèn)any
  • exception: 需要重連的異常、或其子類;多個(gè)用逗號(hào)隔開;可以為java.io.IOException*any,默認(rèn)空;
  • tags: 需要重連的客戶端分組,在@CatClient#tags中配置;多個(gè)用逗號(hào)隔開,默認(rèn)空;
  • note: 需要重連的API方法標(biāo)記;多個(gè)用逗號(hào)隔開;會(huì)匹配@CatMethod#notes中的值;當(dāng)配置的note值,在@CatNote#value中存在時(shí),觸發(fā)重連;
    例如:note=bugcat匹配@CatNote(key=“name”, value=“bugcat”)、@CatNote(“bugcat”),不會(huì)匹配@CatNote(key=“bugcat”, value=“972245132”)
  • noteMatch: 需要重連的API方法標(biāo)記鍵值對(duì);在配置文件中,使用單引號(hào)包裹的Json字符串,默認(rèn)值'{}';當(dāng)noteMatch設(shè)置的鍵值對(duì),在@CatMethod#notes的鍵值對(duì)中完全匹配時(shí),觸發(fā)重連:
    note-match=‘{“name”:“bugcat”,“age”:“17”}’,會(huì)匹配notes={@CatNote(key=“name”, value=“bugcat”), @CatNote(key=“age”, value=“17”)};

如果@CatNote采用springEL表達(dá)式形式,可以實(shí)現(xiàn)運(yùn)行時(shí),根據(jù)入?yún)Q定是否需要重連!
例如:當(dāng)設(shè)置note=save,其中@CatMethod(notes = @CatNote(“#{req.methodName}”)),或者note-match=‘{“method”:“save”}’、對(duì)應(yīng)@CatMethod(notes = @CatNote(key=“method”, value=“#{req.methodName}”))時(shí),如果請(qǐng)求入?yún)eq的methodName值為save,會(huì)觸發(fā)重連,其他則不會(huì);


其他說明

1. 方法入?yún)⒆⒔?/h5>

@ModelAttribute、@RequestBody、@RequestParam在同一個(gè)方法中,只能三選一,可以和@RequestHeader、@PathVariable共存;

  • @ModelAttribute: 用于標(biāo)記復(fù)雜對(duì)象,只能存在一個(gè);表示使用表單方式發(fā)送參數(shù);
  • @RequestBody: 用于標(biāo)記復(fù)雜對(duì)象,只能存在一個(gè);表示使用POST IO流方式發(fā)送參數(shù);可以使用@CatNote為參數(shù)取別名;
  • @RequestParam: 用于標(biāo)記基礎(chǔ)數(shù)據(jù)類型、字符串、日期對(duì)象,可以有多組;表示使用表單方式發(fā)送參數(shù);
  • @RequestHeader: 表示請(qǐng)求頭參數(shù),可以有多組;
  • @PathVariable: 表示uri參數(shù),可以有多組;

默認(rèn)情況下@ModelAttribute、@RequestBody代表把對(duì)應(yīng)的對(duì)象轉(zhuǎn)換成表單、或者字符串。但是具體數(shù)據(jù)格式,需要參考攔截器、CatSendProcessor子類中的自定義邏輯;


2. 方法響應(yīng)類
  • 如果響應(yīng)是Object類型,那么會(huì)返回Http的原始響應(yīng),無論是否啟用了拆響應(yīng)包裝器類;
  • 如果響應(yīng)是Date類型,默認(rèn)日期格式為yyyy-mm-dd HH:mi:ss.SSS,可以使用@JsonFormat#pattern、JSONField#format修改格式;

3. 示例
/**
 * 定義一個(gè)客戶端;
 * 遠(yuǎn)程服務(wù)器地址為:${core-server.remoteApi},需要從環(huán)境變量中獲??;
 * 該客戶端定義了一個(gè)攔截器:TokenInterceptor;
 * 單獨(dú)配置了http鏈接、讀取超時(shí):3000ms;
 * 其他配置為默認(rèn)值,參考CatClientConfiguration;
 * 并且該客戶端,配置了自動(dòng)拆包裝器ResponseEntityWrapper,實(shí)際API接口返回?cái)?shù)據(jù)類型為ResponseEntity<T>;
 * 	  如果方法的返回類型不是ResponseEntity(除Object類型以外),一律推定需要使用自動(dòng)拆包裝器!
 * */
@CatResponesWrapper(ResponseEntityWrapper.class)
@CatClient(host = "${core-server.remoteApi}", interceptor = TokenInterceptor.class, connect = 3000, socket = 3000)
public interface TokenRemote {

    /**
     * CatSendProcessor手動(dòng)創(chuàng)建并且作為方法入?yún)魅耄?     * 定義了2個(gè)標(biāo)記:username、pwd,其標(biāo)記值從環(huán)境配置中獲取demo.username、demo.pwd對(duì)應(yīng)的參數(shù)值;
     * 方法有默認(rèn)實(shí)現(xiàn),當(dāng)發(fā)生Http異常后,會(huì)自動(dòng)執(zhí)行,并將結(jié)果作為Http請(qǐng)求的結(jié)果返回;
     * 雖然添加了自動(dòng)拆響應(yīng)包裝器類,但是該方法返回?cái)?shù)據(jù)類型仍然是ResponseEntity,
     * 	  所以依舊按正常流程解析,將原始響應(yīng),轉(zhuǎn)成ResponseEntity<String>對(duì)象后,再返回;
     * */
    @CatMethod(value = "/cat/getToken", method = RequestMethod.POST,  notes = {@CatNote(key = "username", value = "${demo.username}"), @CatNote(key = "pwd", value = "${demo.pwd}")})
    default ResponseEntity<String> getToken(CatSendProcessor sender) {
        return ResponseEntity.fail("-1", "當(dāng)前網(wǎng)絡(luò)異常!");
    }

    /**
     * 定義了1個(gè)標(biāo)記,標(biāo)記的key和value都是'needToken'這個(gè)字符串;
     * 方法返回?cái)?shù)據(jù)類型String,和配置的包裝器類型不一致,因此推定需要自動(dòng)拆包裝器,實(shí)際返回?cái)?shù)據(jù)類型應(yīng)該為ResponseEntity<String>;
     *    先將原始數(shù)據(jù)轉(zhuǎn)換ResponseEntity<String>,再獲取泛型屬性對(duì)應(yīng)值返回;
     * */
    @CatMethod(value = "/cat/sendDemo", method = RequestMethod.POST, notes = @CatNote("needToken"))
    String sendDemo1(@RequestBody Demo demo);

    /**
     * 將token參數(shù),作為請(qǐng)求頭參數(shù)傳輸;
     * 該方法返回類型為Object,為內(nèi)定的特定數(shù)據(jù)類型,直接返回最原始的響應(yīng)字符串;
     * */
    @CatMethod(value = "/cat/sendDemo", method = RequestMethod.POST)
    Object sendDemo2(@RequestBody Demo demo, @RequestHeader("token") String token);

    /**
     * 動(dòng)態(tài)url,具體訪問地址,由方法入?yún)rl確定;
     * */
    @CatMethod(value = "{sendurl}", method = RequestMethod.POST)
    default ResponseEntity<Void> sendDemo3(@PathVariable("sendurl") String url, @RequestHeader("token") String token, @RequestBody String req) {
        return ResponseEntity.fail("-1", "默認(rèn)異常!");
    }

    /**
     * 給入?yún)rderInfo取了別名:'order';
     * 自定義了一個(gè)標(biāo)記,標(biāo)記的key='routeId',其value為入?yún)rderInfo的oid屬性值;
     * 實(shí)際返回?cái)?shù)據(jù)類型應(yīng)該是ResponseEntity<Void>;
     *    如果返回的是基礎(chǔ)數(shù)據(jù)類型,對(duì)應(yīng)的ResponseEntity<基礎(chǔ)數(shù)據(jù)類型包裝類>;
     * */
    @CatMethod(value = "/order/edit", notes = @CatNote(key = "routeId", value = "#{order.oid}"), method = RequestMethod.POST)
    void sendDemo4(@CatNote("order") @RequestBody OrderInfo orderInfo);
}
/**
 * 攔截器
 * */
@Component
public class TokenInterceptor implements CatSendInterceptor {

    /**
     * 使用攔截器修改參數(shù)
     * */
    @Override
    public void executeVariableResolver(CatClientContextHolder context, Intercepting intercepting) throws Exception {
        CatSendProcessor sendHandler = context.getSendHandler();
        sendHandler.setTracerId(String.valueOf(System.currentTimeMillis())); //設(shè)置日志id,可以通過日志id查詢本次請(qǐng)求所有內(nèi)容。如果不指定,自動(dòng)使用uuid
        JSONObject notes = sendHandler.getNotes(); //所有的自定義標(biāo)記都存放在這里
        CatHttpPoint httpPoint = sendHandler.getHttpPoint();
        String need = notes.getString("needToken");//使用note標(biāo)記是否需要添加簽名
        if( CatToosUtil.isNotBlank(need)){
            String token = TokenInfo.getToken();
            httpPoint.getHeaderMap().put("token", token);//將token存入到請(qǐng)求頭中
            System.out.println(token);
        }
        intercepting.executeInternal(); // 執(zhí)行默認(rèn)參數(shù)處理
    }

    /**
     * token管理
     * */
    private static class TokenInfo {

        private static TokenInfo info = new TokenInfo();
        public static String getToken(){
            return info.getToken(System.currentTimeMillis());
        }

        private TokenRemote tokenRemote = CatClientUtil.getBean(TokenRemote.class);
        private long keepTime;
        private String value;
        private String getToken(long now){
            if( now > keepTime ){
                TokenSend sender = new TokenSend(); // 獲取token的時(shí)候,顯示使用指定CatSendProcessor實(shí)例
                ResponseEntity<String> bean = tokenRemote.getToken(sender);
                keepTime = System.currentTimeMillis() + 3600;
                value = bean.getData();
                return value;
            } else {
                return value;
            }
        }
    }

    /**
     * 獲取token的時(shí)候單獨(dú)處理器;
     * 一般情況使用攔截器即可,此處演示作用,使用繼承CatSendProcessor形式修改參數(shù)
     * */
    private static class TokenSend extends CatSendProcessor {
        @Override
        public void postVariableResolver(CatClientContextHolder context){
            String pwd = notes.getString("pwd"); //notes 已經(jīng)在postConfigurationResolver方法中解析完畢
            String username = notes.getString("username");
            MultiValueMap<String, Object> keyValueParam = this.getHttpPoint().getKeyValueParam();
            keyValueParam.add("username", username);
            keyValueParam.add("pwd", pwd);
            //注意feign-interface中的getToken方法,
            //原getToken方法沒有“有效的”入?yún)ⅲ菍?shí)際發(fā)送Http請(qǐng)求的時(shí)候,卻有2組請(qǐng)求參數(shù)!
            //此特性可以非常靈活調(diào)整feign-interface的入?yún)?shù)量、請(qǐng)求方式等
        }
    }
}
/**
 * http響應(yīng)包裝器類處理。包裝器類為:ResponseEntity<T>;
 * 如果在客戶端,則為拆包裝器;
 * 如果在服務(wù)端,則為加包裝器;
 *
 * @see AbstractResponesWrapper
 * @author bugcat
 * */
public class ResponseEntityWrapper extends AbstractResponesWrapper<ResponseEntity>{

    /**
     * 返回包裝器類class
     * */
    @Override
    public Class<ResponseEntity> getWrapperClass() {
        return ResponseEntity.class;
    }

    /**
     * 組裝包裝器類中的實(shí)際泛型
     * */
    @Override
    public <T> CatTypeReference getWrapperType(Type type){
        return new CatTypeReference<ResponseEntity<T>>(type){};
    }

    /**
     * 拆包裝器,并且自動(dòng)校驗(yàn)業(yè)務(wù)是否成功?
     * 本示例直接繼續(xù)拋出異常;
     * */
    @Override
    public void checkValid(ResponseEntity wrapper) throws Exception {
        if( ResponseEntity.succ.equals(wrapper.getErrCode())){
            //正常
        } else {
            //業(yè)務(wù)異常記錄日志
            CatClientContextHolder contextHolder = CatClientContextHolder.getContextHolder(); //CatClientContextHolder可以獲取到本次http請(qǐng)求相關(guān)上下文對(duì)象,里面包含請(qǐng)求相關(guān)的各種參數(shù)
            CatClientLogger lastCatLog = contextHolder.getSendHandler().getHttpPoint().getLastCatLog();
            lastCatLog.setErrorMessge("[" + wrapper.getErrCode() + "]" + wrapper.getErrMsg());
            //業(yè)務(wù)異常,可以直接繼續(xù)拋出,在公共的異常處理類中,統(tǒng)一處理
            throw new RuntimeException(lastCatLog.getErrorMessge());
        }
    }

    /**
     * 拆包裝器,獲取包裝器類中的業(yè)務(wù)對(duì)象
     * */
    @Override
    public Object getValue(ResponseEntity wrapper) {
        return wrapper.getData();
    }

    /**
     * 服務(wù)端成功之后加包裝器類
     * */
    @Override
    public ResponseEntity createEntryOnSuccess(Object value, Class methodReturnClass) {
        return ResponseEntity.ok(value);
    }

    /**
     * 服務(wù)端當(dāng)發(fā)生異常時(shí)加包裝器
     * */
    @Override
    public ResponseEntity createEntryOnException(Throwable throwable, Class methodReturnClass) {
        throwable.printStackTrace();
        return ResponseEntity.fail("-1", throwable.getMessage() == null ? "NullPointerException" : throwable.getMessage());
    }
}






cat-server 模塊

此模塊也可以單獨(dú)使用,但是更多是搭配feign-interface類型的客戶端使用;具體的業(yè)務(wù)類實(shí)現(xiàn)feign-interface,業(yè)務(wù)類的方法便可以通過Http模式調(diào)用;

cat-server為了支持自動(dòng)加響應(yīng)包裝器類,以及整合自家的cat-client客戶端,因此做了看似很復(fù)雜的邏輯,但是如果看到文檔最后catface部分,就會(huì)發(fā)現(xiàn)很合理了。


  1.  feign-interface -----------------------┐
            ↑                                : 
            │                                : 3. asm增強(qiáng)interface
            │                                :
            │                                ↓
            │                        4. Enhancer-Interface
            │                                ↑
            │                                │
            │                                │
            │                                │ 5. 使用cglib
            │                                │
     2. CatServer <═════════════════════╗    │ 
                                        ║    │
                                        ║    │
                                 6. cglib-controller <══════════  7. http調(diào)用
  1. feign-interface: 包含@PostMapping、@GetMapping、@RequestBody、@RequestParam等注解的Interface接口;
  2. CatServer: 具體的業(yè)務(wù)類,實(shí)現(xiàn)了feign-interface,其類上加有@CatServer注解;
  3. 使用asm對(duì)feign-interface增強(qiáng)處理:如果配置了加響應(yīng)包裝器類功能,修改interface的方法返回對(duì)象,統(tǒng)一為響應(yīng)包裝器類;如果使用了catface模式,將方法入?yún)⒘斜磙D(zhuǎn)成虛擬入?yún)?duì)象;并將feign-interface的、方法級(jí)、入?yún)⒓?jí)注解,轉(zhuǎn)移到新Interface上;
  4. Enhancer-Interface: 增強(qiáng)后的Interface,與原feign-interface沒有任何結(jié)構(gòu)上的關(guān)系;
  5. 使用cglib對(duì)Enhancer-Interface動(dòng)態(tài)代理,生成Controller角色的類,并注冊(cè)到spring容器;
  6. cglib-controller: 動(dòng)態(tài)代理生成的Controller對(duì)象,持有CatServer實(shí)現(xiàn)類的引用(對(duì)象適配模式)
  7. http訪問cglib-controller對(duì)象方法,cglib-controller預(yù)處理入?yún)⒅螅賵?zhí)行CatServer對(duì)應(yīng)的方法;

Enhancer-Interface、cglib-controller 是自動(dòng)成的類;Http請(qǐng)求指向cglib-controller,cglib-controller做預(yù)處理之后,再執(zhí)行CatServer業(yè)務(wù)類的方法,看上去就好像Http請(qǐng)求是直接調(diào)用到CatServer業(yè)務(wù)類。待業(yè)務(wù)處理完畢之后,cglib-controller再判斷是否需要添加響應(yīng)包裝器類,最終返回結(jié)果;


@EnableCatServer

這個(gè)注解表示啟用CatServer服務(wù)端。該注解依賴于spring容器,置于任何spring bean之上,例如:springboot項(xiàng)目啟動(dòng)類上、或者任意包含@Component注解的類上;

  • value: 掃描的包路徑,處于這個(gè)目錄中的feign-interface都會(huì)被注冊(cè)成服務(wù)端。默認(rèn)值是被標(biāo)記類的同級(jí)目錄;
  • classes: 指定服務(wù)端class進(jìn)行注冊(cè),優(yōu)先度高于包路徑;
  • configuration: 生成服務(wù)端的一些配置項(xiàng)和默認(rèn)值;

@CatServer

該注解用于定義某個(gè)feign-interface為服務(wù)端接口。包含:服務(wù)端別名、攔截器
、自定義標(biāo)記、響應(yīng)處理類;

//feign-interface,@CatMethod也可以換成@RequestMapping
@Api(tags = "Catface - 用戶操作api")
@CatResponesWrapper(ResponseEntityWrapper.class)
public interface UserService {

    @ApiOperation("分頁查詢用戶")
    @CatMethod(value = "/user/userPage")
    ResponseEntity<PageInfo<UserInfo>> userPage(@ModelAttribute("vi") UserPageVi vi);

    @ApiOperation("根據(jù)用戶id查詢用戶信息")
    @CatMethod(value = "/user/get/{uid}", method = RequestMethod.GET, notes = @CatNote("user"))
    UserInfo userInfo(@PathVariable("uid") String uid);

    @ApiOperation("編輯用戶")
    @CatMethod(value = "/user/save", method = RequestMethod.POST, notes = @CatNote(key = "name", value = "#{vi.name}"))
    ResponseEntity<Void> userSave(@RequestBody @CatNote("vi") UserSaveVi vi) throws Exception;

    @ApiOperation("設(shè)置用戶狀態(tài)")
    @CatMethod(value = "/user/status", method = RequestMethod.GET)
    void status(@RequestParam("uid") String userId, @RequestParam("status") String status);

}
//服務(wù)端具體實(shí)現(xiàn)類
//此處可以添加事務(wù)注解,或其他AOP注解
@CatServer(interceptors = {UserInterceptor2.class, CatServerInterceptor.class, UserInterceptor.class, CatServerInterceptor.GroupOff.class}) //自定義攔截器 + 全局?jǐn)r截器,無攔截器組
public class UserServiceImpl implements UserService{

    @Override
    public ResponseEntity<PageInfo<UserInfo>> userPage(UserPageVi vi) {
		//具體實(shí)現(xiàn)
        return ResponseEntity.ok(page);
    }

    @Override
    public UserInfo userInfo(String uid) {
        //具體實(shí)現(xiàn)
        return info;
    }

    @Override
    public ResponseEntity<Void> userSave(UserSaveVi vi) {
        //具體實(shí)現(xiàn)
        return ResponseEntity.ok(null);
    }

    @Override
    public void status(String userId, String status) {
        System.out.println("userSave >>> userId=" + userId + ", status=" + status);
    }
}
  • value: 服務(wù)端組件的別名,默認(rèn)首字母小寫;
  • resultHandler: 結(jié)果處理類,如果配置了加響應(yīng)包裝器類,在此處添加;默認(rèn)值受CatServerConfiguration控制;
  • tags: 自定義標(biāo)記;用于攔截器組匹配;
  • interceptors: 攔截器;cglib-controller調(diào)用CatServer過程中的攔截器鏈。其值有4種類型:
    1. 為空: 表示啟用攔截器組,和全局默認(rèn)攔截器;默認(rèn)攔截器會(huì)被CatServerConfiguration#getServerInterceptor的返回對(duì)象替換;
    2. CatServerInterceptor.GroupOff.class: 關(guān)閉攔截器組;
    3. CatServerInterceptor.NoOp.class: 關(guān)閉自定義攔截器和全局默認(rèn)攔截器;
    4. 其他值: 表示啟用自定義攔截器;

? ? ? 攔截器規(guī)則:
? ? ? 1. 攔截器組,是在運(yùn)行中動(dòng)態(tài)匹配,除非在interceptors中配置了CatServerInterceptor.GroupOff.class,否則總是生效;
? ? ? 2. interceptors值如果為空、或者沒有自定義攔截器類型,則全局默認(rèn)攔截器生效,可以使用CatServerInterceptor.NoOp.class關(guān)閉這一功能;若存在任一一個(gè)自定義攔截器類型,則會(huì)忽略全局默認(rèn)攔截器;
? ? ? 3. 多個(gè)自定義攔截器,按配置的先后順序執(zhí)行;如果需要執(zhí)行全局默認(rèn)攔截器,可以使用CatServerInterceptor.class占位;

? ? ? :: @CatServer(): 啟用攔截器組,和全局默認(rèn)攔截器;
? ? ? :: @CatServer(interceptors = {A.class, CatServerInterceptor.class}): 啟用攔截器組、A攔截器、和全局默認(rèn)攔截器。此處CatServerInterceptor.class表示全局?jǐn)r截器的占位符,故A攔截器先于全局執(zhí)行;
? ? ? :: @CatServer(interceptors = {A.class}): 啟用攔截器組,和自定義攔截器;
? ? ? :: @CatServer(interceptors = {UserInterceptor.GroupOff.class}): 關(guān)閉攔截器組;僅全局默認(rèn)攔截器有效;
? ? ? :: @CatServer(interceptors = {UserInterceptor.NoOp.class, A.class}): 僅攔截器組有效,關(guān)閉全局?jǐn)r截器和自定義攔截器;
? ? ? :: @CatServer(interceptors = {CatServerInterceptor.class, A.class, UserInterceptor.GroupOff.class}): 關(guān)閉攔截器組;全局?jǐn)r截器、A攔截器有效;
? ? ? :: @CatServer(interceptors = {UserInterceptor.NoOp.class, UserInterceptor.GroupOff.class, A.class, B.class}): 關(guān)閉所有攔截器;


@CatBefore

配置入?yún)⑻幚砥黝?;在?zhí)行業(yè)務(wù)類方法之前執(zhí)行,用于驗(yàn)證、修改、打印方法入?yún)ⅲ?/p>

CatServerConfiguration

生成服務(wù)端的一些配置項(xiàng)和默認(rèn)值。配合@EnableCatServer使用,可用于修改@CatServer、@CatResponesWrapper注解的實(shí)際默認(rèn)值;

  • getWrapper: 加包裝器類處理類,對(duì)應(yīng)@CatResponesWrapper#value;
  • getResultHandler: 默認(rèn)的結(jié)果處理類;
  • getServerInterceptor: 全局的默認(rèn)攔截器;用于替換CatServer#interceptorsCatServerInterceptor.class位置;
  • getInterceptorGroup: 攔截器組;在運(yùn)行過程中動(dòng)態(tài)匹配;

CatParameterResolver

參入預(yù)處理類,通過@CatBefore配置。在執(zhí)行業(yè)務(wù)類方法之前執(zhí)行,用于驗(yàn)證、修改、打印方法入?yún)⒌龋?/p>

CatResultHandler

feign-interface實(shí)現(xiàn)類的返回值處理類。配合響應(yīng)包裝器使用,可以將返回對(duì)象、異常轉(zhuǎn)換成統(tǒng)一風(fēng)格的響應(yīng);


CatServerInterceptor

cglib-controller調(diào)用CatServer過程中的自定義攔截器;可以用于驗(yàn)證調(diào)用權(quán)限、必要緩存注入、記錄輸入輸出入?yún)⑷罩镜龋?/p>

  • CatServerInterceptor.NoOp.class: 特殊枚舉類,關(guān)閉自定義攔截器和全局默認(rèn)攔截器;
  • CatServerInterceptor.GroupOff.class: 特殊枚舉類,關(guān)閉攔截器組;
  • preHandle: 某個(gè)攔截器可以被多個(gè)服務(wù)端引用,在執(zhí)行攔截器內(nèi)容之前執(zhí)行,用于判斷是否滿足前置要求:
    :: false 表示不滿足,不執(zhí)行攔截器內(nèi)容;
    :: true 表示滿足,需要執(zhí)行攔截器;
    :: 拋出異常,默認(rèn)情況下表示立刻結(jié)束攔截器鏈,不執(zhí)行CatServer的對(duì)應(yīng)方法;以上2種布爾返回值,仍然會(huì)執(zhí)行CatServer方法!
  • postHandle: 攔截器內(nèi)容;

CatInterceptorGroup

攔截器組,在運(yùn)行過程中動(dòng)態(tài)匹配,優(yōu)先于自定義攔截器執(zhí)行;如果服務(wù)端沒有配置CatServerInterceptor.GroupOff.class,則總是執(zhí)行;一般用于記錄日志、驗(yàn)證權(quán)限使用;

  • matcher: 匹配方法;在運(yùn)行過程中,根據(jù)入?yún)⒑驼{(diào)用的上下文進(jìn)行匹配校驗(yàn);
  • getInterceptorFactory: 當(dāng)匹配方法返回true后執(zhí)行,返回滿足該分組要求的攔截器集合;
  • getOrder: 執(zhí)行順序,越小越先執(zhí)行;

其他說明

cglib-controller在調(diào)用CatServer業(yè)務(wù)類過程中,以及CatServer業(yè)務(wù)類內(nèi)部發(fā)生的異常,均可以通過CatResultHandler#onError統(tǒng)一處理。

但是對(duì)于Http請(qǐng)求cglib-controller過程中的異常(403、404、500、入?yún)Ⅱ?yàn)證不通過)等,只能通過@ControllerAdvice處理!
CatServer組件中內(nèi)置了CatControllerAssist異常處理類,可以通過cat-server.controller-assist.enable=false關(guān)閉;


如果feign-interface的實(shí)現(xiàn)類,被其他類繼承了,并且該子類上也存在@CatServer注解,那么Http請(qǐng)求會(huì)指向子類的方法!






cat-face 模塊

把cat-client和cat-server結(jié)合使用,好像就可以實(shí)現(xiàn)最開始提出的「客戶端、服務(wù)端用過Interface耦合實(shí)現(xiàn)無感知調(diào)用」?答案是,也不完全是!是「如是」.jpg

客戶端與服務(wù)端共享feign-interface、入?yún)⒑头祷貙?duì)象的數(shù)據(jù)類型。其中客戶端發(fā)起Http請(qǐng)求的url,是通過feign-interface方法上的@CatClient注解獲取,服務(wù)端注冊(cè)Controller的url,也是通過@CatClient注解獲取,也就是說@CatClient#value()無論返回什么值,客戶端總能找到對(duì)應(yīng)的服務(wù)端!同樣的,Http請(qǐng)求方式也是如此。

既然如此,何不固定請(qǐng)求方式為POST、url通過feign-interface的特征值自動(dòng)生成,那豈不是可以省下@CatClient注解不用寫了?

例如url生成規(guī)則:自定義命名空間 + feign-interface組件別名 + 方法名;


至于feign-interface方法入?yún)ⅲD(zhuǎn)Http請(qǐng)求報(bào)文這部分比較麻煩。POST請(qǐng)求只能傳輸form表單對(duì)象、或IO流,考慮到方法入?yún)⒌膹?fù)雜性,因此有2種轉(zhuǎn)換方案:

  1. 在客戶端,將方法的每個(gè)入?yún)ⅲ冀y(tǒng)一轉(zhuǎn)成字符串,然后使用“入?yún)⒚?= 字符串” 組成form表單對(duì)象。在服務(wù)端生成cglib-controller時(shí),Http入?yún)⑷渴褂米址邮?,然后再逐個(gè)轉(zhuǎn)成實(shí)際數(shù)據(jù)類型,并驗(yàn)證入?yún)⒂行裕?/p>

  2. 在客戶端,將方法的入?yún)?,組合成一個(gè)“入?yún)⒚? 入?yún)?duì)象” 的Map,再將Map序列化成一個(gè)Json字符串;在服務(wù)端生成cglib-controller時(shí),將原入?yún)⒘斜恚D(zhuǎn)成一個(gè)虛擬的入?yún)?duì)象,入?yún)?duì)象的屬性,就是原入?yún)⒚?;這樣Http請(qǐng)求的Json,可以直接轉(zhuǎn)成虛擬入?yún)?duì)象,并自動(dòng)執(zhí)行入?yún)Ⅱ?yàn)證框架;

但是由于Interface在編譯成class字節(jié)碼之后,參數(shù)名會(huì)被擦除(可以使用@CatNote為參數(shù)取別名),實(shí)際上的參數(shù)名應(yīng)該是:arg0、arg1、…、argX;故:

? ? ? 方案1示意

    UserInfo param8(@ApiParam("參數(shù)map") Map<String, Object> map,
                    @ApiParam("參數(shù)vi1") @Valid UserPageVi vi1,
                    @ApiParam("參數(shù)vi2") UserPageVi vi2,
                    @ApiParam("參數(shù)status") @NotNull(message = "status 不能為空") @CatNote("status") Boolean status,
                    @ApiParam("參數(shù)vi3") @Valid ResponseEntity<PageInfo<UserPageVi>> vi3);
	
	/**
     * 最后Http請(qǐng)求格式為
	 * url:/feign-interface別名/param8
	 * query:
	 * 	 arg0="{\"mapKey\":\"mapValue\"}"
	 *   arg1="{\"name\":\"vi1's name\"}"
	 *   arg2="{\"label\":\"vi2's label\"}"
	 *   arg3="false"
	 *   arg4={\"errCode\":\"1", \"data\":"{\"total\": \"12\", \"list\":"[{\"qname\":\"vi3\"}]"}"}"
	 * */

? ? ? 方案2示例:

    UserInfo param8(@ApiParam("參數(shù)map") Map<String, Object> map,
                    @ApiParam("參數(shù)vi1") @Valid UserPageVi vi1,
                    @ApiParam("參數(shù)vi2") UserPageVi vi2,
                    @ApiParam("參數(shù)status") @NotNull(message = "status 不能為空") @CatNote("status") Boolean status,
                    @ApiParam("參數(shù)vi3") @Valid ResponseEntity<PageInfo<UserPageVi>> vi3);
	
	/**
     * 最后服務(wù)端cglib-controller
	 * url:/feign-interface別名/param8
	 * class Virtual {
	 * 	 private Map<String, Object> arg0;
	 *   @Valid private UserPageVi vi1;
	 *   private UserPageVi vi2;
	 *   private Boolean status;
	 *   @Valid private ResponseEntity<PageInfo<UserPageVi>> arg4;
	 * }
	 * UserInfo param8(@Valid @RequestBody Virtual virtual);
	 * */

第1種方案,實(shí)現(xiàn)起來比較容易。缺點(diǎn)是:記錄入?yún)⑷罩緯r(shí),入?yún)⑷渴亲址诖蛴〉臅r(shí)候會(huì)出現(xiàn)引號(hào)轉(zhuǎn)義;服務(wù)端Controller的入?yún)⒍际亲址?,swagger生成的接口文檔沒有詳細(xì)的字段說明,不夠友好;
第2種方案,實(shí)現(xiàn)起來比較麻煩。不存在方案1的缺點(diǎn),但是由于在服務(wù)端生成一個(gè)虛擬的入?yún)?duì)象,因此在feign-interface中不能出現(xiàn)方法重載!

catface中主要使用方案2轉(zhuǎn)換參數(shù);


@Catface

標(biāo)記feign-interface為catface模式;表示將feign-interface中的所有方法都注冊(cè)成客戶端API,方法上、方法入?yún)⑸峡梢詻]有任何注解!

在客戶端,方法上的入?yún)⒘斜?,?huì)先轉(zhuǎn)換成Map,Map鍵為arg0~argX按順序自動(dòng)生成,值為入?yún)?duì)象;然后再轉(zhuǎn)換成Json字符串,POST + Json方式發(fā)起Http請(qǐng)求。請(qǐng)求的url為:配置的命名空間 + feign-interface別名 + 方法名,因此,這需要feign-interface中的方法名不能相同,即不能存在重載方法!

在服務(wù)端,會(huì)為每個(gè)方法自動(dòng)生成一個(gè)虛擬入?yún)?duì)象,方法入?yún)?huì)轉(zhuǎn)換成虛擬入?yún)?duì)象的屬性;這樣Http入?yún)son字符串,可以直接轉(zhuǎn)換成方法入?yún)?duì)應(yīng)的數(shù)據(jù)類型;

  • value: feign-interface別名;默認(rèn)是首字母小寫。
  • namespace: 命名空間;統(tǒng)一的url前綴,默認(rèn)空;

最終生成的url為:[/命名空間]/feign-interface別名/方法名


@CatNotes

在catface模式下,為feign-interface方法添加自定義標(biāo)記;

  • value: 自定義標(biāo)記;
  • scope: 自定義標(biāo)記適用范圍:
    :: All: 適用于客戶端、服務(wù)端;
    :: Cilent: 標(biāo)記僅在作為客戶端使用時(shí)生效;
    :: Server: 標(biāo)記僅在作為服務(wù)端使用時(shí)生效;

最后feign-interface可以簡(jiǎn)化成如下形式:

//@Api、@ApiOperation、@ApiParam是swagger框架的注解,如果沒有這方面需求,可以刪除;
//@NotBlank、@NotNull、@Valid、@Validated是springMVC驗(yàn)證框架注解;

@Api(tags = "Catface - 精簡(jiǎn)模式")
@Catface
@CatResponesWrapper(ResponseEntityWrapper.class)
public interface FaceDemoService{

    UserInfo queryById(@NotBlank(message = "userId不能為空") String userId);

    @ApiOperation("api - param2")
    ResponseEntity<UserInfo> enable(String userId, Integer status);

    @CatNotes(value = {@CatNote(key = "uname", value = "#{user.name}")}, scope = CatNotes.Scope.Cilent)
    @CatNotes(value = {@CatNote(key = "uid", value = "#{user.id}")}, scope = CatNotes.Scope.Server)
    UserPageVi query(@CatNote("user") UserInfo vi);
    
    PageInfo<UserPageVi> queryByBean(String userId, UserInfo  vi, @CatNote("isStatus")  Boolean status);

    int param8(@ApiParam("參數(shù)map") Map<String, Object> map,
                    @ApiParam("參數(shù)vi1") @Valid UserPageVi vi1,
                    @ApiParam("參數(shù)vi2") UserPageVi vi2,
                    @ApiParam("參數(shù)status") @NotNull(message = "status 不能為空") @CatNote("status") Boolean status,
                    @ApiParam("參數(shù)vi3") @Valid ResponseEntity<PageInfo<UserPageVi>> vi3);

    default void dosomething(@ApiParam("參數(shù)map") Map<String, Object> map, 
                    @ApiParam("參數(shù)vi1") @Validated UserPageVi vi1,
                    @ApiParam("參數(shù)date") Date date,
                    @ApiParam("參數(shù)status") Integer status,
                    @ApiParam("參數(shù)decimal") BigDecimal decimal,
                    @ApiParam("參數(shù)vi3") @Valid ResponseEntity<PageInfo<UserPageVi>> vi3) {
        CatClientContextHolder holder = CatClientContextHolder.getContextHolder();
        Throwable exception = holder.getException();
        System.out.println("異常:" + exception.getMessage());
        return null;
    }
}

其他說明

除了feign-interface中方法不能重載,還要注意一點(diǎn)的是:如果在生產(chǎn)環(huán)境上迭代升級(jí)feign-interface,假設(shè)將FaceDemoService#dosomething方法入?yún)⒂性鰷p,無論是先更新客戶端、還是先更新服務(wù)端,都會(huì)造成該API接口參數(shù)接收會(huì)錯(cuò)位!

一般這種情況,可以事先給入?yún)⑷e名,這樣在接收入?yún)r(shí),會(huì)根據(jù)參數(shù)名匹配,而不是參數(shù)順序;或者采用面向?qū)ο箝_發(fā),保持方法入?yún)⑸现挥幸粋€(gè)入?yún)?duì)象,增減參數(shù)數(shù)量,轉(zhuǎn)換成增減對(duì)象屬性多少的問題。





文章來源地址http://www.zghlxwxcb.cn/news/detail-697520.html

到了這里,關(guān)于catface,使用Interface定義Controller,實(shí)現(xiàn)基于Http協(xié)議的RPC調(diào)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Flutter如何使用mvi? bloc結(jié)合自定義http庫的實(shí)現(xiàn)

    Flutter如何使用mvi? bloc結(jié)合自定義http庫的實(shí)現(xiàn)

    提示:本篇并不算嚴(yán)謹(jǐn)?shù)目破瘴恼拢瑑H僅只是記錄使用bloc的思路 最近對(duì)kotlin的mvi使用比較嫻熟,但是關(guān)于flutter架構(gòu)相關(guān)的比較少,之前也有看過provider這些框架總覺得沒那么好使而且還挺麻煩的,現(xiàn)在也有大佬研究getx的mvvm,這里我就不展開了,我的本意是想使用getx作為路

    2024年02月11日
    瀏覽(17)
  • 使用nginx實(shí)現(xiàn)自定義大小預(yù)覽縮略圖,http_image_filter模塊的安裝使用

    使用nginx實(shí)現(xiàn)自定義大小預(yù)覽縮略圖,http_image_filter模塊的安裝使用

    本預(yù)覽圖功能使用的是nginx的http_image_filter模塊,可以自定義緩存大小解決占用服務(wù)器容量問題,建議配合fastDFS使用 http_image_filter不會(huì)改變圖片原始比例 例如原始圖片尺寸為16:9,那么輸入寬高值之后會(huì)以較小的一個(gè)參數(shù)展現(xiàn)預(yù)覽圖,比如原始圖為1600*900,傳遞參數(shù)320*90會(huì)拿到

    2023年04月24日
    瀏覽(26)
  • 基于Junit4+Mockito+PowerMock實(shí)現(xiàn)Controller+Service的單元測(cè)試

    基于Junit4+Mockito+PowerMock實(shí)現(xiàn)Controller+Service的單元測(cè)試

    一 導(dǎo)入的依賴 二 依賴版本 三 controller測(cè)試示例代碼 ? ? ? controller ? ? ? ? controllerTest ? ? ? ? 測(cè)試結(jié)果:覆蓋率100% ? ? ? ? 帶異常的Controller ? ? ? ? 帶異常提示的ControllerTest ? ? ? ? 測(cè)試結(jié)果,覆蓋率100% ? 三 service測(cè)試示例代碼 ? ? ? ? service ? ? ? ? serviceTest ???

    2024年02月14日
    瀏覽(28)
  • 使用Spring Boot實(shí)現(xiàn)基于HTTP的API

    使用Spring Boot實(shí)現(xiàn)基于HTTP的API

    Spring Boot是一個(gè)用于簡(jiǎn)化Spring應(yīng)用程序開發(fā)的框架,它提供了一系列的開箱即用的功能,使得快速構(gòu)建RESTful Web服務(wù)和基于HTTP的API變得簡(jiǎn)單。以下是使用Spring Boot實(shí)現(xiàn)基于HTTP的API的步驟: 添加依賴:在Maven項(xiàng)目中,將Spring Boot Web Starter依賴添加到pom.xml文件中。 java 復(fù)制代碼

    2024年01月25日
    瀏覽(33)
  • WebClient,HTTP Interface遠(yuǎn)程調(diào)用阿里云API

    WebClient,HTTP Interface遠(yuǎn)程調(diào)用阿里云API

    ?HTTP Interface Spring 允許我們通過定義接口的方式,給任意位置發(fā)送 http 請(qǐng)求,實(shí)現(xiàn)遠(yuǎn)程調(diào)用,可以用來簡(jiǎn)化 HTTP 遠(yuǎn)程訪問。需要webflux場(chǎng)景才可 定義接口 創(chuàng)建代理測(cè)試 生產(chǎn)模式----編寫配置類config/WeatherConfiguration,@config WeatherService

    2024年02月16日
    瀏覽(24)
  • Spring Boot 3.2 新特性之 HTTP Interface

    SpringBoot 3.2引入了新的 HTTP interface 用于http接口調(diào)用,采用了類似 openfeign 的風(fēng)格。 具體的代碼參照 示例項(xiàng)目 https://github.com/qihaiyan/springcamp/tree/master/spring-http-interface HTTP Interface 是一個(gè)類似于 openfeign 的同步接口調(diào)用方法,采用 Java interfaces 聲明遠(yuǎn)程接口調(diào)用的方法,理念上類

    2024年02月02日
    瀏覽(25)
  • 【pycharm】【自定義UI】【User Interface】(用戶界面)

    目錄 Customizing the UI? 自定義UI(用戶界面) 1 How to configure your UI exactly as you want it. 1 如何配置你的UI完全按照你想要的。 2 Themes 2?主題 3 Layout 3 布局 4 Keymap? 4 鍵映射 5 Other Customizations? 5 其他定制 6 Conclusion 6 結(jié)論 1 How to configure your UI exactly as you want it.

    2024年02月21日
    瀏覽(29)
  • ts中interface自定義結(jié)構(gòu)約束和對(duì)類的約束

    ts中interface自定義結(jié)構(gòu)約束和對(duì)類的約束

    一、interface自定義結(jié)構(gòu)約束對(duì)后端接口返回?cái)?shù)據(jù) 二、接口請(qǐng)求參數(shù)限制 ? 三、繼承? 四、類中的使用 1,類的寫法(必須限制useName,useAge的類型,否則報(bào)錯(cuò) 2、修飾符 2-1、readonly只讀 2-2、public(默認(rèn)值 ?公開的 ?都能訪問) 2-3、protected (受保護(hù)的,只能在當(dāng)前類和當(dāng)前類的

    2024年02月13日
    瀏覽(18)
  • IDEA直接請(qǐng)求controller,不用postman請(qǐng)求http接口

    IDEA直接請(qǐng)求controller,不用postman請(qǐng)求http接口

    generated-requests.http工具用法 第一步:點(diǎn)擊下面按鈕,HTTP Client ?第二步、生成generated-requests.http文件 ?第三步、更改服務(wù)的ip和端口,啟動(dòng)服務(wù) ?請(qǐng)求實(shí)例: 1、post請(qǐng)求,body傳參: 2、get請(qǐng)求 2.1 2.2

    2024年02月15日
    瀏覽(26)
  • Controller層自定義注解攔截request請(qǐng)求校驗(yàn)

    Controller層自定義注解攔截request請(qǐng)求校驗(yàn)

    一、背景 筆者工作中遇到一個(gè)需求,需要開發(fā)一個(gè)注解,放在controller層的類或者方法上,用以校驗(yàn)請(qǐng)求參數(shù)中(不管是url還是body體內(nèi),都要檢查,有token參數(shù),且符合校驗(yàn)規(guī)則就放行)是否傳了一個(gè)token的參數(shù),并且token符合一定的生成規(guī)則,符合就不予攔截,放行請(qǐng)求,否則

    2024年01月17日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包