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

使用axios-auth-refresh处理refresh token时多请求重复触发刷新如何解决

问题核心原因

你的api_client被定义为每次调用都会创建全新axios实例的函数,retrieve/send方法每次执行都会生成独立的axios实例,实例之间的拦截器、刷新状态锁、请求队列完全隔离,自然无法实现「单次刷新、队列等待」的逻辑,并发请求时每个实例都会单独触发刷新。
同时你还存在两个隐性bug:

  1. 刷新token请求使用了加了401拦截的client实例发起,若刷新接口返回401会触发死循环
  2. cookie过期时间配置错误,new Date(Date.now() + 259200)仅比当前时间多259秒(约4分钟),实际要3天的话需要乘以1000转成毫秒

原生拦截器版本修复方案

将axios实例、刷新状态、队列都改为全局单例,避免重复创建实例:

import axios from 'axios';
import axiosRetry from 'axios-retry';
import config from 'config';
import {isNull, isUndefined} from 'underscore';
import Cookies from 'universal-cookie';

const cookies = new Cookies();
// 全局唯一axios实例
const client = axios.create({baseURL: config.server});
let isRefreshing = false;
let failedQueue = [];
const retryCodes = [401]

axiosRetry(client, {
    retries: 3,
    retryDelay: (retryCount) => retryCount * 1000,
    retryCondition: (error) => retryCodes.includes(error?.response?.status)
});

// 请求拦截器
client.interceptors.request.use(request => {
    const cookieToken = cookies.get('token');
    const cookieUser = cookies.get('user');
    if(!isNull(cookieToken) && !isNull(cookieUser) && !isUndefined(cookieToken.token)) {
        request.headers['Authorization'] = `Bearer ${cookieToken.token}`;
    }
    request.withCredentials = true;
    request.headers['Content-Type'] = 'application/json'
    request.headers['X-Fname'] = cookieUser?.username;
    return request;
}, error => Promise.reject(error));

// 队列处理
const processQueue = (error, token = null) => {
    failedQueue.forEach(prom => error ? prom.reject(error) : prom.resolve(token))
    failedQueue = [];
}

// 刷新token方法,用无拦截的axios发起请求
const getRefreshAuthToken = () => {
    const cookieToken = cookies.get('token');
    const cookieUser = cookies.get('user');
    if((isNull(cookieToken) || isNull(cookieUser)) || isUndefined(cookieToken.refreshToken)) {
        return Promise.reject('无可用刷新token');
    }
    return axios.post(`${config.server}/user/refresh?refreshToken=${cookieToken.refreshToken}`);
}

// 响应拦截器
client.interceptors.response.use(response => response, error => {
    const originalRequest = error.config;
    if (error?.response?.status !== 401 || originalRequest._retry) {
        return Promise.reject(error);
    }
    // 已在刷新中,加入队列等待
    if (isRefreshing) {
        return new Promise((resolve, reject) => {
            failedQueue.push({ resolve, reject })
        }).then(token => {
            originalRequest.headers['Authorization'] = `Bearer ${token}`;
            return client(originalRequest);
        }).catch(err => Promise.reject(err));
    }
    // 首次触发刷新
    originalRequest._retry = true;
    isRefreshing = true;
    return new Promise((resolve, reject) => {
        getRefreshAuthToken()
        .then(response => {
            const {token, refreshToken} = response.data;
            // 更新cookie
            cookies.remove("token", { domain: window.location.hostname, path: "/grt" });
            cookies.remove("token", { domain: window.location.hostname, path: "/" });
            cookies.set("token", { token, refreshToken }, { 
                domain: window.location.hostname, 
                path: "/grt", 
                expires: new Date(Date.now() + 259200 * 1000) // 修正毫秒单位
            });
            // 处理队列并重试当前请求
            processQueue(null, token);
            originalRequest.headers['Authorization'] = `Bearer ${token}`;
            resolve(client(originalRequest));
        }).catch(err => {
            processQueue(err, null);
            reject(err);
        }).finally(() => {
            isRefreshing = false
        });
    });
})

// 导出请求方法,复用全局唯一实例
export const retrieve = async (url, data = undefined) => {
    const response = await client.get(url, data);
    return response.data;
}

export const send = async (url, data = undefined) => {
    const response = await client.post(url, data, { skipAuthRefresh: true });
    return response.data;
}

export default {retrieve, send};

axios-auth-refresh版本修复方案

同样改为单例实例,同时共享刷新请求Promise避免重复发起:

import axios from 'axios';
import axiosRetry from 'axios-retry';
import config from 'config';
import {isNull, isUndefined} from 'underscore';
import Cookies from 'universal-cookie';
import createAuthRefreshInterceptor  from 'axios-auth-refresh';

const cookies = new Cookies();
const client = axios.create({baseURL: config.server});
// 缓存刷新请求Promise,避免重复发起
let refreshRequest = null;

client.interceptors.request.use(request => {
    const cookieToken = cookies.get('token');
    const cookieUser = cookies.get('user');
    if(!isNull(cookieToken) && !isNull(cookieUser) && !isUndefined(cookieToken.token)) {
        request.headers['Authorization'] = `Bearer ${cookieToken.token}`;
    }
    request.withCredentials = true;
    request.headers['Content-Type'] = 'application/json'
    request.headers['X-Fname'] = cookieUser?.username;
    return request;
}, error => Promise.reject(error));

const getRefreshAuthToken = () => {
    if (refreshRequest) return refreshRequest;
    const cookieToken = cookies.get('token');
    const cookieUser = cookies.get('user');
    if((isNull(cookieToken) || isNull(cookieUser)) || isUndefined(cookieToken.refreshToken)) {
        return Promise.reject('无可用刷新token');
    }
    refreshRequest = axios.post(`${config.server}/user/refresh?refreshToken=${cookieToken.refreshToken}`);
    refreshRequest.finally(() => refreshRequest = null);
    return refreshRequest;
}

const handleRefreshAuth = failedRequest => {
    return getRefreshAuthToken().then(response => {
        const {token, refreshToken} = response.data;
        cookies.remove("token", { domain: window.location.hostname, path: "/grt" });
        cookies.remove("token", { domain: window.location.hostname, path: "/" });
        cookies.set("token", { token, refreshToken }, { 
            domain: window.location.hostname, 
            path: "/grt", 
            expires: new Date(Date.now() + 259200 * 1000)
        });
        failedRequest.response.config.headers['Authorization'] = `Bearer ${token}`;
        return Promise.resolve();
    })
}

const options = {
    pauseInstanceWhileRefreshing: true,
    skipWhileRefreshing: false
}

createAuthRefreshInterceptor(client, handleRefreshAuth, options);

export const retrieve = async (url, data = undefined) => {
    const response = await client.get(url, data);
    return response.data;
}

export const send = async (url, data = undefined) => {
    const response = await client.post(url, data, { skipAuthRefresh: true });
    return response.data;
}

export default {retrieve, send};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 23:54:03