保姆级--实现前端token无感刷新

0 阅读1分钟

一、认识什么是无感token刷新

    1、首先认识一下什么是token,token是在当前前后端分类的架构中,重要的身份令牌 。
    2、为什么会有无感刷新,因为在用户体验中,如果token失效,导致api请求失败,会严重影响用户的体验,无感刷新油然而生。

二、token无感刷新的思路

    Access Token(访问token):用于api请求的身份认证,放在api请求头中用于身份验证
    Refresh Token(刷新token):当前Access Token过期时,    Refresh Token用来请求api,获取新的Access Token。
    1、前端请求api,token过期
    2、拿 Refresh Token去请求 刷新token的api,获取新的token,替换到旧的token。
    3、(重点);api重试机制,触发401的api 需要在重新请求来实现真正的无感,让用户感受不到token过期。

三、贴代码示例

示例使用我的一个uni-app项目做讲解

1、请求刷新token的api方法

async function refreshAccessToken(): Promise<string> {
const rt = uni.getStorageSync('refreshToken')
if (!rt) {
    throw new Error('refreshToken not found')
}


try {
    const data = await $refreshToken({ refreshToken: rt })
    uni.setStorageSync('token', data.accessToken)
    uni.setStorageSync('refreshToken', data.refreshToken)
    return data.accessToken

} catch { }

}

2、api触发401时 触发的关键代码。

//此方法在接口触发401调用
export const resetToken = (config: any) => {
return new Promise((resolve, reject) => {
    const queuedConfig = {
        ...config,
        header: { ...(config?.header || {}) }
    }
    //当token刷新时,将触发401的api加入队列中
    requestQueue.push({ config: queuedConfig, resolve, reject })

    // 已有刷新任务时,只入队等待,不重复触发刷新
    if (refreshingPromise) return
  

    refreshingPromise = refreshAccessToken()
    refreshingPromise
        .then((newToken) => {
            flushQueueWithToken(newToken)
        })
        .catch((err) => {
            console.log('刷新token失败', err)
            rejectQueue(err)
            redirectToLoginOnce()
        })
        .finally(() => {
            refreshingPromise = null
        })
})
}

3、其他

`//跳转到登录页面
function redirectToLoginOnce() {
if (hasRedirectedToLogin) return
hasRedirectedToLogin = true
uni.clearStorage()
uni.navigateTo({
    url: '/pages/login/index'
})
}

//tokne刷新成功后,将队列中的api重新请求,且清空队列
function flushQueueWithToken(token: string) {
const pending = requestQueue.splice(0, requestQueue.length)
for (const task of pending) {
    const retryConfig = {
        ...task.config,
        _retry: true,
        header: {
            ...(task.config?.header || {}),
            Authorization: `Bearer ${token}`
        }
    }
    uni.$uv.http.request(retryConfig).then(task.resolve).catch(task.reject)
}
}
//刷新token失败后,清空队列
function rejectQueue(error: any) {
const pending = requestQueue.splice(0, requestQueue.length)
for (const task of pending) {
    task.reject(error)
}
}`