動態

詳情 返回 返回

無感刷新token解決方案 - 動態 詳情

在你的 axios 封裝文件中,你需要兩個外部變量來管理狀態:

// 標記是否正在刷新 token 的“鎖”
let isRefreshing = false; 

// 存儲因 token 失效而掛起的請求的“隊列”
let requestQueue = []; 
  1. 請求攔截器 (Request Interceptor)
    它的任務很簡單:在每個請求發出去之前,都帶上當前的 token。
import axios from 'axios';

const service = axios.create({
  baseURL: '/api',
  timeout: 5000,
});

// 請求攔截器
service.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token'); // 從你的存儲中獲取 token
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);
  1. 響應攔截器 (Response Interceptor) - 核心邏輯所在地
    這裏是處理“保安攔人”和“排隊”邏輯的地方。
// 響應攔截器
service.interceptors.response.use(
  // 狀態碼為 2xx 的情況,直接返回響應
  response => {
    return response.data;
  },
  // 狀態碼非 2xx 的情況
  async error => {
    const { config, response } = error;
    const originalRequest = config; // 獲取原始請求配置

    // 1. 判斷是否是 401 (未授權) 錯誤
    if (response && response.status === 401) {
      
      // 2. 判斷是否正在刷新 Token (檢查“鎖”)
      if (!isRefreshing) {
        // --- “第一個被攔下的人”的邏輯 ---
        isRefreshing = true; // 上鎖

        try {
          // 3. 去“前台”辦理新卡 (調用刷新 token 的接口)
          const { newToken, newRefreshToken } = await refreshTokenApi(); // 這是一個你自己封裝的刷新 token 的 API 調用

          // 4. 辦理成功,更新本地存儲的 token
          localStorage.setItem('token', newToken);
          localStorage.setItem('refreshToken', newRefreshToken);
          
          // 5. 把新 token 更新到當前 axios 實例的默認配置中,後續請求就會使用新 token
          service.defaults.headers.common['Authorization'] = 'Bearer ' + newToken;

          // 6. 遍歷“等待隊列”,把之前失敗的請求用新 token 重新發送一遍
          requestQueue.forEach(callback => callback(newToken));
          
          // 清空隊列
          requestQueue = [];

          // 7. 把自己這個最初失敗的請求也重新發送一遍
          return service(originalRequest);

        } catch (refreshError) {
          // 如果刷新 token 本身也失敗了(比如 refresh token 也過期了)
          // 就跳轉到登錄頁,並清空所有狀態
          console.error('Unable to refresh token.', refreshError);
          // 清理本地存儲、跳轉登錄頁等操作
          // redirectToLogin(); 
          return Promise.reject(refreshError);
        } finally {
          // 解鎖
          isRefreshing = false;
        }
      } else {
        // --- “後面排隊的人”的邏輯 ---
        // 如果正在刷新 token,就把當前失敗的請求包裝成一個 Promise,存入隊列
        return new Promise((resolve) => {
          // 這個 callback 的參數就是未來獲取到的新 token
          requestQueue.push((token) => {
            // 當拿到新 token 後,更新這個請求的 header,然後重新發起請求
            originalRequest.headers['Authorization'] = 'Bearer ' + token;
            resolve(service(originalRequest));
          });
        });
      }
    }
    
    // 如果不是 401 錯誤,直接把錯誤拋出
    return Promise.reject(error);
  }
);

// 模擬一個刷新 token 的 API
async function refreshTokenApi() {
  const refreshToken = localStorage.getItem('refreshToken');
  // 實際項目中,這裏會用 axios 發送一個請求
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('Token refreshed!');
      resolve({
        newToken: 'a-brand-new-token-string',
        newRefreshToken: 'a-brand-new-refresh-token-string',
      });
    }, 1000);
  });
}

export default service;

流程圖
為了讓你更清晰地理解,這裏有一個流程圖:

graph TD

A[發起多個API請求] --> B{響應攔截器};
B --> C{狀態碼是 401?};
C -- 否 --> D[請求成功/其他錯誤];
C -- 是 --> E{isRefreshing === false? (我是第一個嗎?)};

E -- 是 --> F[isRefreshing = true (上鎖)];
F --> G[調用 refreshToken API];
G -- 成功 --> H[更新本地和axios的Token];
H --> I[執行隊列裏的所有請求];
I --> J[重試我自己的請求];
J --> K[isRefreshing = false (解鎖)];

G -- 失敗 --> L[跳轉到登錄頁];
L --> K;

E -- 否 --> M[當前請求進入等待隊列];
M --> N[等待...];
I --> N;

D --> Z[結束];
K --> Z;

ps: 文檔來源 google AI
核心點:
1、同一個頁面發送的所有ajax他們共享很多變量
比如isRefreshing 、requestQueue,並不是發送一個接口就實例化一次變量

2、一旦函數內部寫了return後,就不會執行任何函數後面的代碼了

所以需要把 請求自己那段代碼寫在最後。

// 7. 把自己這個最初失敗的請求也重新發送一遍
          return service(originalRequest);

從上面的請求順序來看,去發起刷新token的那個原始接口,在拿到最新token後,先執行了後面排隊的那些請求,執行完了後,自己最後才去請求的。

user avatar grewer 頭像 zaotalk 頭像 yinzhixiaxue 頭像 front_yue 頭像 jingdongkeji 頭像 kobe_fans_zxc 頭像 aqiongbei 頭像 linx 頭像 huajianketang 頭像 hard_heart_603dd717240e2 頭像 Dream-new 頭像 zero_dev 頭像
點贊 177 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.