diff --git a/src/utils/http.ts b/src/utils/http.ts index 64b92ab..e28ec24 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -14,6 +14,10 @@ export const clearAccessTokens = () => { Session.remove(refreshAccessTokenKey); }; +// Token刷新状态和请求队列 +let isRefreshing = false; +let requestQueue: Array<(token: string) => void> = []; + /** * 检查并存储授权信息 * @param res 响应对象 @@ -97,6 +101,15 @@ class Axios { async (res: AxiosResponse) => { // 检查并存储授权信息 checkAndStoreAuthentication(res); + + // 如果正在刷新Token,且响应中包含新Token,说明刷新成功 + if (isRefreshing && res.headers[accessTokenKey]) { + const newToken = res.headers[accessTokenKey]; + isRefreshing = false; + requestQueue.forEach((callback) => callback(newToken)); + requestQueue = []; + } + const data = res.data; if (res.config.url?.toLocaleLowerCase() === '/file/upload'.toLowerCase()) { return data; @@ -111,10 +124,6 @@ class Axios { case 401: clearAccessTokens(); message = '您已登出,请重新登录'; - // ElMessageBox.alert('您已登出,请重新登录', '提示', {}) - // .then(() => {}) - // .catch(() => {}); - // window.location.href = '/'; // 去登录页 break; case 403: message = '您没有权限,无法执行此项操作!'; @@ -130,11 +139,54 @@ class Axios { }); if (data.statusCode === 401) { clearAccessTokens(); + isRefreshing = false; + requestQueue = []; window.location.href = '/'; // 去登录页 } return Promise.reject(new Error(data.errors || 'Error')); }, - (err: any) => { + async (err: any) => { + const originalRequest = err.config; + + // 如果是401错误且不是重试请求 + if (err.response && err.response.status === 401 && !originalRequest._retry) { + // 如果已经在刷新Token,将请求加入队列 + if (isRefreshing) { + return new Promise((resolve) => { + requestQueue.push((newToken: string) => { + originalRequest.headers['Authorization'] = `Bearer ${newToken}`; + resolve(this.instance(originalRequest)); + }); + }); + } + + // 开始刷新Token + originalRequest._retry = true; + isRefreshing = true; + + // 检查是否有刷新Token + const refreshAccessToken = Session.get(refreshAccessTokenKey); + if (!refreshAccessToken) { + clearAccessTokens(); + isRefreshing = false; + window.location.href = '/'; + return Promise.reject(err); + } + + try { + // 重新发送原请求,Furion会自动处理Token刷新 + originalRequest.headers['X-Authorization'] = `Bearer ${refreshAccessToken}`; + return this.instance(originalRequest); + } catch (refreshErr) { + // 刷新失败,清除所有Token并跳转登录页 + clearAccessTokens(); + isRefreshing = false; + requestQueue = []; + window.location.href = '/'; + return Promise.reject(refreshErr); + } + } + // 这里用来处理http常见错误,进行全局提示 let message = err.message; if (err.response && err.response.status) { @@ -144,7 +196,8 @@ class Axios { break; case 401: message = '未授权,请重新登录(401)'; - // 这里可以做清空storage并跳转到登录页的操作 + clearAccessTokens(); + window.location.href = '/'; break; case 403: message = '拒绝访问(403)'; @@ -179,7 +232,6 @@ class Axios { } // 这里错误消息可以使用全局弹框展示出来 if (message) { - // 比如element plus 可以使用 ElMessage ElMessage({ showClose: true, message: `${message},请检查网络或联系管理员!`,