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

Kotlin Multiplatform中非阻塞刷新Access Token的正确方式

KMP+Ktor 非阻塞Token刷新实现方案

你担心的情况确实会发生——如果用阻塞方式占满所有可用线程等待Token刷新,百分百会触发死锁:没有空闲线程去执行刷新任务,整个流程直接卡住。下面是用协程实现的非阻塞方案,完全替代runBlocking、Mutex、Lock:

核心逻辑

利用协程的挂起特性+原子变量跟踪刷新状态,做到:

  • 多个并发请求遇Token过期时,只会触发一次刷新
  • 等待的请求仅挂起而非阻塞线程,线程可继续处理其他任务(包括刷新任务)

1. 全局Token管理类(KMP共享模块)

用AtomicReference原子变量管理刷新任务,避免重复触发:

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.util.concurrent.atomic.AtomicReference

class TokenManager {
    // 原子引用:跟踪当前是否有正在进行的刷新任务
    private val refreshJobRef = AtomicReference<Deferred<String?>?>(null)
    private var currentToken: String? = "初始过期Token"

    suspend fun getValidToken(): String {
        // 先检查当前Token是否有效,有效直接返回
        currentToken?.takeIf { !isTokenExpired(it) }?.let { return it }

        // 尝试获取或启动唯一的刷新任务
        val refreshJob = refreshJobRef.get() ?: run {
            val newRefreshJob = coroutineScope {
                async {
                    try {
                        // 替换成你的实际刷新Token接口请求(Ktor实现)
                        val newToken = fetchNewTokenFromApi()
                        currentToken = newToken
                        newToken
                    } finally {
                        // 刷新完成后清空原子引用,允许下次触发刷新
                        refreshJobRef.compareAndSet(newRefreshJob, null)
                    }
                }
            }
            // 原子更新引用,避免多线程同时启动刷新
            if (refreshJobRef.compareAndSet(null, newRefreshJob)) newRefreshJob else refreshJobRef.get()
        }

        // 挂起等待刷新完成,返回新Token
        return refreshJob.await() ?: error("Token刷新失败")
    }

    // 实际调用刷新Token的API(Ktor请求)
    private suspend fun fetchNewTokenFromApi(): String {
        // 示例:return ktorClient.post("https://your-api.com/auth/refresh") { ... }.body()
        return "新的有效Token"
    }

    // 实现Token过期判断逻辑(比如解析JWT过期时间)
    private fun isTokenExpired(token: String): Boolean {
        return true // 替换成实际判断逻辑
    }
}

2. Ktor请求拦截器(KMP共享模块)

在Ktor客户端配置中添加拦截器,自动处理Token过期重试:

import io.ktor.client.HttpClient
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerAuthProvider
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.http.HttpHeaders

fun createAuthHttpClient(tokenManager: TokenManager): HttpClient {
    return HttpClient {
        install(Auth) {
            bearer {
                // 每次请求前自动获取有效Token
                loadTokens {
                    val validToken = tokenManager.getValidToken()
                    BearerTokens(accessToken = validToken, refreshToken = "你的RefreshToken")
                }
                // 收到401时自动触发刷新,并重试请求
                refreshTokens {
                    val newToken = tokenManager.getValidToken()
                    BearerTokens(accessToken = newToken, refreshToken = "更新后的RefreshToken")
                }
            }
        }
        // 可选:添加重试逻辑,确保刷新后重试请求
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 1)
            exponentialDelay()
        }
    }
}

关键优势

  • 彻底避免死锁:协程挂起不会占用线程,线程可自由处理刷新任务
  • 无重复请求:原子变量确保同一时间只有一个刷新任务在运行
  • 全平台兼容:纯Kotlin代码,直接在Android和iOS上运行

内容的提问来源于stack exchange,提问作者DarkSatyr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 01:30:31