Retrofit运行时动态修改HTTP请求路径及BaseURL方案咨询
最优解决方案
一、运行时动态修改BaseURL
不用逐个修改请求参数,直接通过OkHttp拦截器实现全局动态替换:
class BaseUrlInterceptor(private val dynamicBaseUrl: () -> String) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val newBaseUrl = dynamicBaseUrl().takeIf { it.isNotEmpty() } ?: run { originalRequest.url.toString() } val baseUri = Uri.parse(newBaseUrl) val newUrl = originalRequest.url.newBuilder() .scheme(baseUri.scheme ?: originalRequest.url.scheme) .host(baseUri.host ?: originalRequest.url.host) .port(if (baseUri.port == -1) originalRequest.url.port else baseUri.port) .build() val newRequest = originalRequest.newBuilder().url(newUrl).build() return chain.proceed(newRequest) } }
创建OkHttpClient时注入该拦截器,后续只要更新dynamicBaseUrl的返回值,所有请求会自动切换到新的BaseURL。
二、批量处理API版本(替代逐个加@Path)
方案1:拦截器统一替换版本标识
如果所有接口路径都是/xxx-vX的格式,用拦截器全局替换版本号:
class ApiVersionInterceptor(private val currentVersion: () -> String) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val oldPath = originalRequest.url.encodedPath // 替换路径中的版本后缀,比如把/foo-v1改成/foo-v2 val newPath = oldPath.replace(Regex("-v\\d+"), "-v${currentVersion()}") val newUrl = originalRequest.url.newBuilder().encodedPath(newPath).build() val newRequest = originalRequest.newBuilder().url(newUrl).build() return chain.proceed(newRequest) } }
无需修改任何接口方法,只要更新currentVersion的返回值,所有接口版本自动切换。
方案2:创建版本化的Service接口
如果不同版本的接口参数/返回结构差异较大,直接拆分出不同版本的Service:
interface FooV1Service { @POST("/foo-v1") fun foo(@Body map: Map<String, String>): Call<Response> } interface FooV2Service { @POST("/foo-v2") fun foo(@Body map: Map<String, String>): Call<Response> }
运行时根据业务逻辑选择对应的Service实例,这种方式更清晰,适合版本间兼容性差的场景。
三、处理部分请求的路径前缀(如/auth)
方案1:自定义注解+拦截器
先定义标识注解:
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class AuthPrefix
给需要加前缀的接口方法标注:
interface ApiService { @POST("/foo-v1") fun foo(@Body map: Map<String, String>): Call<Response> @AuthPrefix @POST("/bar-v1") fun bar(@Body map: Map<String, String>): Call<Response> }
在拦截器中判断注解并添加前缀:
class AuthPrefixInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() val invocation = originalRequest.tag(Invocation::class.java) ?: return chain.proceed(originalRequest) val needPrefix = invocation.method().isAnnotationPresent(AuthPrefix::class.java) if (!needPrefix) return chain.proceed(originalRequest) val oldPath = originalRequest.url.encodedPath val newPath = "/auth$oldPath" val newUrl = originalRequest.url.newBuilder().encodedPath(newPath).build() val newRequest = originalRequest.newBuilder().url(newUrl).build() return chain.proceed(newRequest) } }
方案2:拆分不同BaseURL的Retrofit实例
给需要/auth前缀的请求单独创建Retrofit实例,BaseURL直接拼接前缀:
val authRetrofit = Retrofit.Builder() .baseUrl("${getCurrentBaseUrl()}/auth/") .client(okHttpClient) .build() val authApi = authRetrofit.create(AuthApi::class.java) interface AuthApi { @POST("/foo-v1") // 实际请求路径为 BaseURL/auth/foo-v1 fun foo(@Body map: Map<String, String>): Call<Response> }
这种方式适合有大量统一前缀的接口,管理更简洁。
内容的提问来源于stack exchange,提问作者Vadim Eksler
相关产品推荐
相关产品推荐

