Skip to content

双 Token 自动刷新封装

ts
// 这个刷新 Token 的请求不能使用当前封装的 axios 实例,因为会陷入死循环
import { RefreshToken } from "@/services/api/refresh-token";
import { useUserStore } from "@/stores/user";
import axios, {
  AxiosError,
  type AxiosInstance,
  type InternalAxiosRequestConfig,
} from "axios";

interface FailedRequest {
  resolve: (value: unknown) => void;
  reject: (reason?: any) => void;
}

const http: AxiosInstance = axios.create({
  baseURL: "/api",
  timeout: 1000 * 10,
  headers: {
    "Content-Type": "application/json",
  },
});

let isRefreshing: boolean = false;
let failedQueue: FailedRequest[] = [];

/**
 * 处理错误请求队列
 */
const processQueue = (error: any, token: string | null = null) => {
  failedQueue.forEach(({ resolve, reject }) => {
    if (error) {
      reject(error);
    } else {
      resolve(token);
    }
  });

  failedQueue = [];
};

http.interceptors.request.use(
  (config) => {
    // 通过各种方式拿到 accessToken 都可以
    const accessToken = useUserStore.getState().jwtToken?.accessToken;

    if (accessToken) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

http.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & {
      _retry?: boolean;
    };

    if (
      error.response?.status === 401 &&
      !originalRequest._retry &&
      !originalRequest.url?.includes("/auth/refresh-token")
    ) {
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        })
          .then((accessToken) => {
            originalRequest.headers.Authorization = `Bearer ${accessToken}`;
            return http(originalRequest);
          })
          .catch((err) => {
            return Promise.reject(err);
          });
      }

      isRefreshing = true;
      originalRequest._retry = true;

      try {
        const { accessToken } = await RefreshToken();

        processQueue(null, accessToken);

        // 刷新成功,重新请求
        console.log("刷新 token 成功", accessToken);
        originalRequest.headers.Authorization = `Bearer ${accessToken}`;
        return http(originalRequest);
      } catch (err) {
        console.error("刷新 token 失败");

        // 刷新失败,清除用户信息, 或者其他退出登录的逻辑
        useUserStore.getState().logout();
        processQueue(err);

        return Promise.reject(err);
      } finally {
        isRefreshing = false;
      }
    }

    return Promise.reject(error);
  }
);

export default http;

基于 MIT 许可发布