項目結構
├── assets/ # 靜態資源(CSS/圖片)
├── components/ # Vue 組件
├── layouts/ # 布局模板
├── pages/ # 自動生成路由
├── plugins/ # 插件(如 axios 攔截器)
├── store/ # Vuex 狀態管理
├── nuxt.config.js # 項目配置
└── middleware/ # 路由中間件
Token過期或者退出登錄重定向問題
功能實現
添加一個響應攔截器每次請求前需要先驗證用戶Token有沒有過期,如果過期了,那么就直接重定向到登陸頁面讓用戶重新進行登錄操作。
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNDkxNDAyMjM0Iiwic3ViIjoiMTQ5MTQwMjIzNCIsImlhdCI6MTc0NTE0ODg3OX0.hWqI3bww_oQCcQlqyqsI56TJrfIE0qK6uTT5gxqbCko
根據local storage
里的信息發現該token
中載荷由以下三部分組成:
jti
(JWT ID):令牌唯一標識符sub
(Subject):主題/用戶IDiat
(Issued At):簽發時間(Unix時間戳)
上面三部分并沒有關于過期時間exp
的字段說明,但從local storage·
還發現了auth._token_expiration.local
字段,于是將該字段最為判斷token
是否過期的憑證。
// auth.js
export default {// 獲取tokengetToken() {return localStorage.getItem('auth._token.local') || '';},// 檢查token是否過期isTokenExpired() {const storedExpiration = localStorage.getItem('auth._token_expiration.local');if (storedExpiration) {const isExpired = Date.now() >= parseInt(storedExpiration, 10);if (isExpired) {console.warn('[Auth] Token expired (based on localStorage expiration)');return true;}}let token = this.getToken()console.log(token)if (token == "false") return true;},// 清除tokenclearToken() {localStorage.removeItem('auth._token.local');localStorage.removeItem('refreshToken');},// 保存token (用于登錄成功后)saveToken(token, refreshToken) {localStorage.setItem('auth._token.local', token);if (refreshToken) {localStorage.setItem('refreshToken', refreshToken);}}};
之后利用auth.js
里的函數完成攔截器:
import axios from 'axios';
import auth from './auth'; // 創建axios實例
const instance = axios.create({baseURL: process.env.VUE_APP_API_BASE_URL, // 從環境變量獲取基礎URLtimeout: 10000 // 請求超時時間
});instance.interceptors.request.use(config => {// 檢查token是否過期if (auth.isTokenExpired()) {auth.clearToken();if (process.client) {// 使用 Nuxt 的 redirect 方法window.location.href = '/login'; }// 終止請求return Promise.reject(new Error('Token expired'));}const token = auth.getToken();if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;},error => {// 請求錯誤處理return Promise.reject(error);}
);export default instance;
上述函數通過以下幾步組成:
- Token檢查:在每次請求前,通過
auth.isTokenExpired()
檢查Token是否過期 - 過期處理:如果Token過期,則清除Token并重定向到登錄頁面
- Token添加:如果Token有效,則將其添加到請求頭的Authorization字段中
- 錯誤處理:處理請求配置階段的錯誤
Bug修改
完成上述實現之后,發現出現了下面這樣的問題—列表類型的組件出現了非常多的重復的空的元素!!!但實際上正確的應該是只有一個面試官。
下面開始排查問題所在:
請求頭攜帶的token格式錯誤
可能是多個并發請求同時檢測到 Token 過期,導致多次重定向嘗試。于是在下面添加一個類似于鎖的機制:
// src/utils/axiosInstance.jslet isRedirecting = false; // 新添加
instance.interceptors.request.use(config => {if (auth.isTokenExpired() && !isRedirecting) { // 新添加isRedirecting = true; // 新添加auth.clearToken();if (process.client) {window.location.href = '/login';}return Promise.reject(new Error('Token expired'));}return config;},error => {return Promise.reject(error);}
);
現在列表項的數目降到了3個。
但之后發現其實并不是isRedirecting
起了作用,而是將config.headers.Authorization = Bearer ${token};
刪了才起的作用。
這里是我完全解決了之后才回來重新寫的
其實到了這一步后端就已經能返回正確的響應了,但當時并沒有像之后那樣看Network去分析,之前的問題就是我在攔截器里重新添加了Authorization,而偏偏我添加的是錯的,這導致了之前會出現很多的空的項。
我通過分析Network里的沒經過攔截器的請求頭里的Authorization發現:Authorization里是沒有Bearer的(見下圖),之前加了Bearer反而錯了
響應解封裝錯誤
之后調試了很久,在Network里發現,后端明明相應了正確的請求,為什么會取不出來呢?
打印出來看一看,發現也沒什么問題。
這是第二個問題,就是我使用了請求攔截器之后后端返回的響應不知道為什么會多封裝一層,原本能夠
res.data
取出的數據需要res.data.data
才行,所以我就直接又加了一個響應攔截器。 (見下面代碼)
instance.interceptors.response.use(response => {return response.data;},error => {// 處理HTTP錯誤狀態碼if (error.response) {switch (error.response.status) {case 401:window.location.href = '/login';break;}}// 返回錯誤信息return Promise.reject(error);}
);