You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:05:00