token过期中断后续接口,并静默刷新token后重新调取

某个接口触发token过期,刷新token后重新调取接口,要求期间用户无感知

let tokenRefreshing = false // 是否在调取刷新token的接口
let retryQueue = [] // 重试队列,以备获取token后重新发起请求
let abortControllers = [] // 撤销队列,以备将请求撤销

function tokenHoldHandle(requestConfig) {
    if (tokenRefreshing) {
        // 正在刷新 token,将请求加入队列
        return new Promise((resolve) => {
            retryQueue.push(requestConfig)
        })
    } else {
        tokenRefreshing = true;
        return refreshToken().then((res) => { // 刷新 session
            setToken(res.sessionId)
            retryQueue.forEach((config) => instance(config)); // 重新发起队列中的请求
            retryQueue = []; // 清空队列
            return instance(requestConfig) // 重新调触发refresh的接口
        }).catch((error) => {
            const errorList = error.response?.data?.errorList || []
            const firstError = errorList[0] || {}

            passiveOut(firstError.errorMessage || 'Token 获取异常,请重新登录!')
        }).finally(() => {
            tokenRefreshing = false
        })
    }
}

以axios为例

import axios from 'axios'
import { refreshToken } from '@/apis/login'

/**
 * 不依赖token的接口判断,比如调取token的接口、登录接口等
 */
function withoutTokenJudge(requestURL){
    const withoutTokenAPIs = [
        '/auth',
        '/login',
        '/sendVerifyCode',
        '/refreshToken',
    ]
    return withoutTokenAPIs.some(url => {
        return new RegExp(`^${url}`).test(requestURL || '')
    })
}
function setConfig(config){
    const token = Cookies.get(TOKEN_NAME)
    const controller = new AbortController();
    // 不需要 auth token 的接口
    if (withoutTokenJudge(config)) {
        config.timeout = 30 * 1000
        config.headers["sub-sys-kbn"] = "aga"; // 其他头信息
        ......
    } else {
        config.headers.Authorization = `Bearer ${token}`; // 约定的 token 传参
        config.timeout = 60 * 1000

        config.signal = controller.signal
        config._controller = controller; // 可被控制的接口附上控制实例

    }
    return config
}

const instance= axios.create({
    withCredentials: true,
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json;charset=UTF-8',
    },
})

// axios实例请求发起拦截
instance.interceptors.request.use(config => {
    const updatedConfig = setConfig(config)
    if (updatedConfig._controller) {
        // 加入队列,以备撤销请求
        abortControllers.push({
            url: updatedConfig.url,
            method: updatedConfig.method,
            controller: updatedConfig._controller as AbortController
        })
    }
    return updatedConfig
}, (error) => {
    console.log('instance.interceptors.request', error)
    return Promise.reject(error)
})

// axios实例响应拦截
instance.interceptors.response.use(async (response) => {
    // TODO 错误码匹配反馈
    if (response.status === 200) {
        return response.data
    } else {
        console.error('接口返回异常,请重试!')
        return response.data
    }
}, async (error) => {
    const requestConfig = error.config;
    // 错误处理
    if (error.response.status === 401) && !withoutTokenJudge(requestConfig.url!)) {
        // 静默更新token逻辑
        return tokenHoldHandle(requestConfig)
    } else {
        abortControllers.forEach((item) => {
            item.controller.abort()  // 取消所有请求
        });
        abortControllers = []
        console.error('接口返回异常,请重试!')
    }

    return Promise.reject(error)
})

export default instance;
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容