English Document
適用於retrofit的spring-boot-starter,支援快速整合和功能增強。
專案持續優化迭代,歡迎大家提ISSUE和PR!麻煩大家能給一顆star,您的star是我們持續更新的動力!
github專案地址:https://github.com/LianjiaTech/retrofit-spring-boot-starter
gitee專案地址:https://gitee.com/lianjiatech/retrofit-spring-boot-starter
範例demo:https://github.com/ismart-yuxi/retrofit-spring-boot-demo
感謝
@ismart-yuxi
為本計畫所寫的範例demo
< dependency >
< groupId >com.github.lianjiatech</ groupId >
< artifactId >retrofit-spring-boot-starter</ artifactId >
< version >3.1.3</ version >
</ dependency >
如果啟動失敗,大機率是依賴衝突,煩請引入或排除相關依賴。
接口必須使用@RetrofitClient
註解標記! HTTP相關註解可參考官方文件:retrofit官方文件。
@ RetrofitClient ( baseUrl = "${test.baseUrl}" )
public interface UserService {
/**
* 根据id查询用户姓名
*/
@ POST ( "getName" )
String getName ( @ Query ( "id" ) Long id );
}
注意:方法請求路徑慎用
/
開頭。對於Retrofit
而言,如果baseUrl=http://localhost:8080/api/test/
,方法請求路徑如果是person
,則該方法完整的請求路徑是:http://localhost:8080/api/test/person
。而方法請求路徑如果是/person
,則該方法完整的請求路徑是:http://localhost:8080/person
。
將介面注入到其它Service中即可使用!
@ Service
public class BusinessService {
@ Autowired
private UserService userService ;
public void doBusiness () {
// call userService
}
}
預設情況下,自動使用SpringBoot
掃描路徑進行RetrofitClient
註冊。你也可以在配置類別加上@RetrofitScan
手動指定掃描路徑。
HTTP
請求相關註解,全部使用了Retrofit
原生註解,以下是簡單說明:
註解分類 | 支持的註解 |
---|---|
請求方式 | @GET @HEAD @POST @PUT @DELETE @OPTIONS @HTTP |
請求頭 | @Header @HeaderMap @Headers |
Query參數 | @Query @QueryMap @QueryName |
path參數 | @Path |
form-encoded參數 | @Field @FieldMap @FormUrlEncoded |
請求體 | @Body |
文件上傳 | @Multipart @Part @PartMap |
url參數 | @Url |
詳細資訊可參考官方文件:retrofit官方文檔
元件支援了多個可設定的屬性,用來應對不同的業務場景,具體可支援的配置屬性及預設值如下:
注意:套用只需要設定要變更的設定項!
retrofit :
# 全局转换器工厂
global-converter-factories :
- com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
- retrofit2.converter.jackson.JacksonConverterFactory
# 全局调用适配器工厂(组件扩展的调用适配器工厂已经内置,这里请勿重复配置)
global-call-adapter-factories :
# 全局日志打印配置
global-log :
# 启用日志打印
enable : true
# 全局日志打印级别
log-level : info
# 全局日志打印策略
log-strategy : basic
# 是否聚合打印请求日志
aggregate : true
# 全局重试配置
global-retry :
# 是否启用全局重试
enable : false
# 全局重试间隔时间
interval-ms : 100
# 全局最大重试次数
max-retries : 2
# 全局重试规则
retry-rules :
- response_status_not_2xx
- occur_io_exception
# 全局超时时间配置
global-timeout :
# 全局读取超时时间
read-timeout-ms : 10000
# 全局写入超时时间
write-timeout-ms : 10000
# 全局连接超时时间
connect-timeout-ms : 10000
# 全局完整调用超时时间
call-timeout-ms : 0
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : none
# 全局sentinel降级配置
global-sentinel-degrade :
# 是否开启
enable : false
# 各降级策略对应的阈值。平均响应时间(ms),异常比例(0-1),异常数量(1-N)
count : 1000
# 熔断时长,单位为 s
time-window : 5
# 降级策略(0:平均响应时间;1:异常比例;2:异常数量)
grade : 0
# 全局resilience4j降级配置
global-resilience4j-degrade :
# 是否开启
enable : false
# 根据该名称从#{@link CircuitBreakerConfigRegistry}获取CircuitBreakerConfig,作为全局熔断配置
circuit-breaker-config-name : defaultCircuitBreakerConfig
# 自动设置PathMathInterceptor的scope为prototype
auto-set-prototype-scope-for-path-math-interceptor : true
如果只需要修改OkHttpClient
的逾時時間,可以透過@RetrofitClient
相關欄位修改,或是全域逾時配置修改。
如果需要修改OkHttpClient
其它配置,可以透過自訂OkHttpClient
來實現,步驟如下:
實作SourceOkHttpClientRegistrar
接口,呼叫SourceOkHttpClientRegistry#register()
方法註冊OkHttpClient
@ Component
public class CustomOkHttpClientRegistrar implements SourceOkHttpClientRegistrar {
@ Override
public void register ( SourceOkHttpClientRegistry registry ) {
// 注册customOkHttpClient,超时时间设置为1s
registry . register ( "customOkHttpClient" , new OkHttpClient . Builder ()
. connectTimeout ( Duration . ofSeconds ( 1 ))
. writeTimeout ( Duration . ofSeconds ( 1 ))
. readTimeout ( Duration . ofSeconds ( 1 ))
. addInterceptor ( chain -> chain . proceed ( chain . request ()))
. build ());
}
}
透過@RetrofitClient.sourceOkHttpClient
指定目前介面要使用的OkHttpClient
@ RetrofitClient ( baseUrl = "${test.baseUrl}" , sourceOkHttpClient = "customOkHttpClient" )
public interface CustomOkHttpUserService {
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
User getUser ( @ Query ( "id" ) Long id );
}
注意:元件不會直接使用指定的
OkHttpClient
,而是基於該OkHttpClient
建立一個新的。
元件提供了註解式攔截器,支援基於url路徑比對攔截,使用的步驟如下:
BasePathMatchInterceptor
@Intercept
註解指定要使用的攔截器如果需要使用多個攔截器,在介面上標註多個
@Intercept
註解即可。
BasePathMatchInterceptor
編寫攔截處理器 @ Component
public class PathMatchInterceptor extends BasePathMatchInterceptor {
@ Override
protected Response doIntercept ( Chain chain ) throws IOException {
Response response = chain . proceed ( chain . request ());
// response的Header加上path.match
return response . newBuilder (). header ( "path.match" , "true" ). build ();
}
}
預設情況下,元件會自動將BasePathMatchInterceptor
的scope
設定為prototype
。 可透過retrofit.auto-set-prototype-scope-for-path-math-interceptor=false
關閉此功能。關閉之後,需要手動將scope
設定為prototype
。
@ Component
@ Scope ( "prototype" )
public class PathMatchInterceptor extends BasePathMatchInterceptor {
}
@Intercept
進行標註 @ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Intercept ( handler = PathMatchInterceptor . class , include = { "/api/user/**" }, exclude = "/api/user/getUser" )
// @Intercept() 如果需要使用多个路径匹配拦截器,继续添加@Intercept即可
public interface InterceptorUserService {
/**
* 根据id查询用户姓名
*/
@ POST ( "getName" )
Response < String > getName ( @ Query ( "id" ) Long id );
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
Response < User > getUser ( @ Query ( "id" ) Long id );
}
上面的@Intercept
配置表示:攔截InterceptorUserService
介面下/api/user/**
路徑下(排除/api/user/getUser
)的請求,攔截處理器使用PathMatchInterceptor
。
有的時候,我們需要在"攔截註解"動態傳入一些參數,然後在攔截的時候使用這些參數。 這時候,我們可以使用"自訂攔截註解",步驟如下:
@InterceptMark
標記,且註解中必須包含include、exclude、handler
欄位。BasePathMatchInterceptor
編寫攔截處理器例如,我們需要"在請求頭裡面動態加入accessKeyId
、 accessKeySecret
簽章資訊才能再發起HTTP請求",這時候可以自訂@Sign
註解來實現。
@Sign
註解 @ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . TYPE )
@ Documented
@ InterceptMark
public @interface Sign {
String accessKeyId ();
String accessKeySecret ();
String [] include () default { "/**" };
String [] exclude () default {};
Class <? extends BasePathMatchInterceptor > handler () default SignInterceptor . class ;
}
在@Sign
註解中指定了使用的攔截器是SignInterceptor
。
SignInterceptor
@ Component
@ Setter
public class SignInterceptor extends BasePathMatchInterceptor {
private String accessKeyId ;
private String accessKeySecret ;
@ Override
public Response doIntercept ( Chain chain ) throws IOException {
Request request = chain . request ();
Request newReq = request . newBuilder ()
. addHeader ( "accessKeyId" , accessKeyId )
. addHeader ( "accessKeySecret" , accessKeySecret )
. build ();
Response response = chain . proceed ( newReq );
return response . newBuilder (). addHeader ( "accessKeyId" , accessKeyId )
. addHeader ( "accessKeySecret" , accessKeySecret ). build ();
}
}
注意:
accessKeyId
和accessKeySecret
欄位必須提供setter
方法。
攔截器的accessKeyId
和accessKeySecret
欄位值會依據@Sign
註解的accessKeyId()
和accessKeySecret()
值自動注入,如果@Sign
指定的是佔位符號形式的字串,則會取配置屬性值進行注入。
@Sign
@ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Sign ( accessKeyId = "${test.accessKeyId}" , accessKeySecret = "${test.accessKeySecret}" , include = "/api/user/getAll" )
public interface InterceptorUserService {
/**
* 查询所有用户信息
*/
@ GET ( "getAll" )
Response < List < User >> getAll ();
}
元件支援支援全域日誌列印和聲明式日誌列印。
預設情況下,全域日誌列印是開啟的,預設配置如下:
retrofit :
# 全局日志打印配置
global-log :
# 启用日志打印
enable : true
# 全局日志打印级别
log-level : info
# 全局日志打印策略
log-strategy : basic
# 是否聚合打印请求日志
aggregate : true
# 日志名称,默认为{@link LoggingInterceptor} 的全类名
logName : com.github.lianjiatech.retrofit.spring.boot.log.LoggingInterceptor
四種日誌列印策略意義如下:
NONE
:No logs.BASIC
:Logs request and response lines.HEADERS
:Logs request and response lines and their respective headers.BODY
:Logs request and response lines and their respective headers and bodies (if present). 如果只需要部分請求才列印日誌,可以在相關介面或方法上使用@Logging
註解。
如果需要修改日誌列印行為,可以繼承LoggingInterceptor
,並將其配置成Spring bean
。
元件支援支援全域重試和聲明式重試。
全域重試預設關閉,預設配置項目如下:
retrofit :
# 全局重试配置
global-retry :
# 是否启用全局重试
enable : false
# 全局重试间隔时间
interval-ms : 100
# 全局最大重试次数
max-retries : 2
# 全局重试规则
retry-rules :
- response_status_not_2xx
- occur_io_exception
重試規則支援三種配置:
RESPONSE_STATUS_NOT_2XX
:回應狀態碼不是2xx
時執行重試OCCUR_IO_EXCEPTION
:發生IO異常時執行重試OCCUR_EXCEPTION
:發生任意異常時執行重試如果只有一部分請求需要重試,可以在對應的介面或方法上使用@Retry
註解。
如果需要修改請求重試行為,可以繼承RetryInterceptor
,並將其配置成Spring bean
。
熔斷降級預設關閉,目前支援sentinel
和resilience4j
兩種實作。
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : sentinel
配置degrade-type=sentinel
開啟,然後在相關介面或方法上聲明@SentinelDegrade
註解即可。
記得手動引入Sentinel
依賴:
< dependency >
< groupId >com.alibaba.csp</ groupId >
< artifactId >sentinel-core</ artifactId >
< version >1.6.3</ version >
</ dependency >
此外,也支援全域Sentinel
熔斷降級:
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : sentinel
# 全局sentinel降级配置
global-sentinel-degrade :
# 是否开启
enable : true
# ...其他sentinel全局配置
配置degrade-type=resilience4j
開啟。然後在相關介面或方法上聲明@Resilience4jDegrade
即可。
記得手動引入Resilience4j
依賴:
< dependency >
< groupId >io.github.resilience4j</ groupId >
< artifactId >resilience4j-circuitbreaker</ artifactId >
< version >1.7.1</ version >
</ dependency >
透過以下配置可開啟全域resilience4j熔斷降級:
retrofit :
# 熔断降级配置
degrade :
# 熔断降级类型。默认none,表示不启用熔断降级
degrade-type : resilience4j
# 全局resilience4j降级配置
global-resilience4j-degrade :
# 是否开启
enable : true
# 根据该名称从#{@link CircuitBreakerConfigRegistry}获取CircuitBreakerConfig,作为全局熔断配置
circuit-breaker-config-name : defaultCircuitBreakerConfig
熔斷配置管理:
實作CircuitBreakerConfigRegistrar
接口,註冊CircuitBreakerConfig
。
@ Component
public class CustomCircuitBreakerConfigRegistrar implements CircuitBreakerConfigRegistrar {
@ Override
public void register ( CircuitBreakerConfigRegistry registry ) {
// 替换默认的CircuitBreakerConfig
registry . register ( Constants . DEFAULT_CIRCUIT_BREAKER_CONFIG , CircuitBreakerConfig . ofDefaults ());
// 注册其它的CircuitBreakerConfig
registry . register ( "testCircuitBreakerConfig" , CircuitBreakerConfig . custom ()
. slidingWindowType ( CircuitBreakerConfig . SlidingWindowType . TIME_BASED )
. failureRateThreshold ( 20 )
. minimumNumberOfCalls ( 5 )
. permittedNumberOfCallsInHalfOpenState ( 5 )
. build ());
}
}
透過circuitBreakerConfigName
指定CircuitBreakerConfig
。包括retrofit.degrade.global-resilience4j-degrade.circuit-breaker-config-name
或@Resilience4jDegrade.circuitBreakerConfigName
如果使用者需要使用其他的熔斷降級實現,繼承BaseRetrofitDegrade
,並將其配置Spring Bean
。
如果@RetrofitClient
不設定fallback
或fallbackFactory
,當觸發熔斷時,會直接拋出RetrofitBlockException
異常。 使用者可以透過設定fallback
或fallbackFactory
來自訂熔斷時的方法傳回值。
注意:
fallback
類別必須是目前介面的實作類,fallbackFactory
必須是FallbackFactory<T>
實作類,泛型參數類型為目前介面類型。另外,fallback
和fallbackFactory
實例必須配置成Spring Bean
。
fallbackFactory
相對於fallback
,主要差異在於能夠感知每次熔斷的異常原因(cause),參考範例如下:
@ Slf4j
@ Service
public class HttpDegradeFallback implements HttpDegradeApi {
@ Override
public Result < Integer > test () {
Result < Integer > fallback = new Result <>();
fallback . setCode ( 100 )
. setMsg ( "fallback" )
. setBody ( 1000000 );
return fallback ;
}
}
@ Slf4j
@ Service
public class HttpDegradeFallbackFactory implements FallbackFactory < HttpDegradeApi > {
@ Override
public HttpDegradeApi create ( Throwable cause ) {
log . error ( "触发熔断了! " , cause . getMessage (), cause );
return new HttpDegradeApi () {
@ Override
public Result < Integer > test () {
Result < Integer > fallback = new Result <>();
fallback . setCode ( 100 )
. setMsg ( "fallback" )
. setBody ( 1000000 );
return fallback ;
}
};
}
}
在HTTP
發生請求錯誤(包括發生異常或回應資料不符合預期)的時候,錯誤解碼器可將HTTP
相關資訊解碼到自訂異常。你可以在@RetrofitClient
註解的errorDecoder()
指定目前介面的錯誤解碼器,自訂錯誤解碼器需要實作ErrorDecoder
介面:
ServiceInstanceChooser
使用者可以自行實作ServiceInstanceChooser
接口,完成服務實例的選取邏輯,並將其配置成Spring Bean
。對於Spring Cloud
應用,可以使用以下實作。
@ Service
public class SpringCloudServiceInstanceChooser implements ServiceInstanceChooser {
private LoadBalancerClient loadBalancerClient ;
@ Autowired
public SpringCloudServiceInstanceChooser ( LoadBalancerClient loadBalancerClient ) {
this . loadBalancerClient = loadBalancerClient ;
}
/**
* Chooses a ServiceInstance URI from the LoadBalancer for the specified service.
*
* @param serviceId The service ID to look up the LoadBalancer.
* @return Return the uri of ServiceInstance
*/
@ Override
public URI choose ( String serviceId ) {
ServiceInstance serviceInstance = loadBalancerClient . choose ( serviceId );
Assert . notNull ( serviceInstance , "can not found service instance! serviceId=" + serviceId );
return serviceInstance . getUri ();
}
}
serviceId
和path
@ RetrofitClient ( serviceId = "user" , path = "/api/user" )
public interface ChooserOkHttpUserService {
/**
* 根据id查询用户信息
*/
@ GET ( "getUser" )
User getUser ( @ Query ( "id" ) Long id );
}
如果我們需要對整個系統的的HTTP
請求執行統一的攔截處理,可以實現全域攔截器GlobalInterceptor
, 並配置成spring Bean
。
@ Component
public class MyGlobalInterceptor implements GlobalInterceptor {
@ Override
public Response intercept ( Chain chain ) throws IOException {
Response response = chain . proceed ( chain . request ());
// response的Header加上global
return response . newBuilder (). header ( "global" , "true" ). build ();
}
}
實作NetworkInterceptor
接口,並配置成spring Bean
。
Retrofit
可以透過CallAdapterFactory
將Call<T>
物件適配成介面方法的回傳值類型。元件擴展了一些CallAdapterFactory
實作:
BodyCallAdapterFactory
HTTP
請求,將回應體內容適配成方法的回傳值類型。BodyCallAdapterFactory
,優先順序最低。ResponseCallAdapterFactory
HTTP
請求,將回應體內容適配成Retrofit.Response<T>
傳回。Retrofit.Response<T>
,才可以使用ResponseCallAdapterFactory
。CallAdapterFactory
Retrofit
會根據方法傳回值類型選擇對應的CallAdapterFactory
執行適配處理,目前支援的回傳值類型如下:
String
:將Response Body
適配成String
回傳。Long
/ Integer
/ Boolean
/ Float
/ Double
):將Response Body
適應成上述基礎類型Java
類型: 將Response Body
適配成對應的Java
物件傳回CompletableFuture<T>
: 將Response Body
適配成CompletableFuture<T>
物件傳回Void
: 不關注回傳類型可以使用Void
Response<T>
: 將Response
適配成Response<T>
物件傳回Call<T>
: 不執行適配處理,直接回傳Call<T>
對象Mono<T>
: Project Reactor
響應式回傳類型Single<T>
: Rxjava
響應式傳回類型(支援Rxjava2/Rxjava3
)Completable
: Rxjava
響應式回傳類型, HTTP
請求沒有回應體(支援Rxjava2/Rxjava3
)可以透過繼承CallAdapter.Factory
來擴充CallAdapter
。
元件支援透過retrofit.global-call-adapter-factories
配置全域呼叫適配器工廠:
retrofit :
# 全局转换器工厂(组件扩展的`CallAdaptorFactory`工厂已经内置,这里请勿重复配置)
global-call-adapter-factories :
# ...
針對每個Java接口,也可以透過@RetrofitClient.callAdapterFactories
指定目前介面採用的CallAdapter.Factory
。
建議:將
CallAdapter.Factory
配置成Spring Bean
Retrofit
使用Converter
將@Body
註解的物件轉換成Request Body
,將Response Body
轉換成一個Java
對象,可以選用以下幾種Converter
:
元件支援透過retrofit.global-converter-factories
配置全域Converter.Factory
,預設的是retrofit2.converter.jackson.JacksonConverterFactory
。
如果需要修改Jackson
配置,自行覆蓋JacksonConverterFactory
的bean
配置即可。
retrofit :
# 全局转换器工厂
global-converter-factories :
- com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory
- retrofit2.converter.jackson.JacksonConverterFactory
針對每個Java
接口,也可以透過@RetrofitClient.converterFactories
指定目前介面採用的Converter.Factory
。
建議:將
Converter.Factory
配置成Spring Bean
。
@RetrofitClient
、 @Retry
、 @Logging
、 @Resilience4jDegrade
等註解支持元註解、繼承以及@AliasFor
。
@ Retention ( RetentionPolicy . RUNTIME )
@ Target ( ElementType . TYPE )
@ Documented
@ Inherited
@ RetrofitClient ( baseUrl = "${test.baseUrl}" )
@ Logging ( logLevel = LogLevel . WARN )
@ Retry ( intervalMs = 200 )
public @interface MyRetrofitClient {
@ AliasFor ( annotation = RetrofitClient . class , attribute = "converterFactories" )
Class <? extends Converter . Factory >[] converterFactories () default { GsonConverterFactory . class };
@ AliasFor ( annotation = Logging . class , attribute = "logStrategy" )
LogStrategy logStrategy () default LogStrategy . BODY ;
}
@ FormUrlEncoded
@ POST ( "token/verify" )
Object tokenVerify ( @ Field ( "source" ) String source , @ Field ( "signature" ) String signature , @ Field ( "token" ) String token );
@ FormUrlEncoded
@ POST ( "message" )
CompletableFuture < Object > sendMessage ( @ FieldMap Map < String , Object > param );
// 对文件名使用URLEncoder进行编码
public ResponseEntity importTerminology ( MultipartFile file ){
String fileName = URLEncoder . encode ( Objects . requireNonNull ( file . getOriginalFilename ()), "utf-8" );
okhttp3 . RequestBody requestBody = okhttp3 . RequestBody . create ( MediaType . parse ( "multipart/form-data" ), file . getBytes ());
MultipartBody . Part part = MultipartBody . Part . createFormData ( "file" , fileName , requestBody );
apiService . upload ( part );
return ok (). build ();
}
HTTP
上傳介面 @ POST ( "upload" )
@ Multipart
Void upload ( @ Part MultipartBody . Part file );
HTTP
下載介面 @ RetrofitClient ( baseUrl = "https://img.ljcdn.com/hc-picture/" )
public interface DownloadApi {
@ GET ( "{fileKey}" )
Response < ResponseBody > download ( @ Path ( "fileKey" ) String fileKey );
}
HTTP
下載使用 @ SpringBootTest ( classes = { RetrofitBootApplication . class })
@ RunWith ( SpringRunner . class )
public class DownloadTest {
@ Autowired
DownloadApi downLoadApi ;
@ Test
public void download () throws Exception {
String fileKey = "6302d742-ebc8-4649-95cf-62ccf57a1add" ;
Response < ResponseBody > response = downLoadApi . download ( fileKey );
ResponseBody responseBody = response . body ();
// 二进制流
InputStream is = responseBody . byteStream ();
// 具体如何处理二进制流,由业务自行控制。这里以写入文件为例
File tempDirectory = new File ( "temp" );
if (! tempDirectory . exists ()) {
tempDirectory . mkdir ();
}
File file = new File ( tempDirectory , UUID . randomUUID (). toString ());
if (! file . exists ()) {
file . createNewFile ();
}
FileOutputStream fos = new FileOutputStream ( file );
byte [] b = new byte [ 1024 ];
int length ;
while (( length = is . read ( b )) > 0 ) {
fos . write ( b , 0 , length );
}
is . close ();
fos . close ();
}
}
使用@url
註解可實現動態URL。此時, baseUrl
配置任意合法url即可。例如: http://github.com/
。運行時只會根據@Url
位址發起請求。
注意:
@url
必須放在方法參數的第一個位置,另外,@GET
、@POST
等註解上,不需要定義端點路徑。
@ GET
Map < String , Object > test3 ( @ Url String url , @ Query ( "name" ) String name );
DELETE
請求新增請求體 @ HTTP ( method = "DELETE" , path = "/user/delete" , hasBody = true )
GET
請求新增請求體okhttp3
本身不支援GET
請求新增請求體,源碼如下:
作者給了具體原因,可以參考: issue
但是,如果實在需要這麼做,可以使用: @HTTP(method = "get", path = "/user/get", hasBody = true)
,使用小寫get
繞過上述限制。
如有任何問題,歡迎提issue或加QQ群回饋。
群組號碼:806714302