项目结构
├── 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);}
);