|
@@ -1972,49 +1972,58 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 尝试刷新token
|
|
|
|
|
- let isRefreshing = false; // 防止并发刷新
|
|
|
|
|
|
|
+ // 尝试刷新token(单飞模式,避免并发刷新导致状态错乱)
|
|
|
|
|
+ let isRefreshing = false;
|
|
|
|
|
+ let refreshPromise = null;
|
|
|
async function tryRefreshToken () {
|
|
async function tryRefreshToken () {
|
|
|
- // 如果正在刷新,直接返回false
|
|
|
|
|
- if (isRefreshing) {
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ // 如果已有刷新任务在进行,直接复用该任务结果
|
|
|
|
|
+ if (refreshPromise) {
|
|
|
|
|
+ return refreshPromise;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- isRefreshing = true;
|
|
|
|
|
|
|
+ refreshPromise = (async () => {
|
|
|
|
|
+ if (isRefreshing) return false;
|
|
|
|
|
|
|
|
- const token = localStorage.getItem('auth_token');
|
|
|
|
|
- if (!token) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ isRefreshing = true;
|
|
|
|
|
|
|
|
- const response = await fetch('/api/auth/refresh', {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
|
|
|
|
+ const token = localStorage.getItem('auth_token');
|
|
|
|
|
+ if (!token) {
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
|
|
+ // 使用原生fetch,避免被addAuthHeader包装后递归进入刷新逻辑
|
|
|
|
|
+ const nativeFetch = window.__nativeFetch || window.fetch;
|
|
|
|
|
+ const response = await nativeFetch('/api/auth/refresh', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ 'Authorization': `Bearer ${token}`
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok && data.success && data.token) {
|
|
|
|
|
+ localStorage.setItem('auth_token', data.token);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (response.ok && data.success && data.token) {
|
|
|
|
|
- localStorage.setItem('auth_token', data.token);
|
|
|
|
|
- return true;
|
|
|
|
|
- } else {
|
|
|
|
|
- // 如果是401错误或token过期相关错误,直接返回false
|
|
|
|
|
if (response.status === 401 ||
|
|
if (response.status === 401 ||
|
|
|
data.code === 'TOKEN_EXPIRED_NO_SESSION' ||
|
|
data.code === 'TOKEN_EXPIRED_NO_SESSION' ||
|
|
|
data.code === 'MISSING_TOKEN') {
|
|
data.code === 'MISSING_TOKEN') {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
return false;
|
|
return false;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ isRefreshing = false;
|
|
|
|
|
+ refreshPromise = null;
|
|
|
}
|
|
}
|
|
|
- } catch (error) {
|
|
|
|
|
- return false;
|
|
|
|
|
- } finally {
|
|
|
|
|
- isRefreshing = false;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ return refreshPromise;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function clearAuthAndRedirect () {
|
|
function clearAuthAndRedirect () {
|
|
@@ -2044,14 +2053,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
if (userInfo) userInfo.style.display = 'flex';
|
|
if (userInfo) userInfo.style.display = 'flex';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function addAuthHeader (token) {
|
|
|
|
|
- // 为所有fetch请求添加认证头和错误处理
|
|
|
|
|
- const originalFetch = window.fetch;
|
|
|
|
|
|
|
+ function addAuthHeader () {
|
|
|
|
|
+ // 避免重复包装fetch
|
|
|
|
|
+ if (!window.__nativeFetch) {
|
|
|
|
|
+ window.__nativeFetch = window.fetch;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (window.__authFetchWrapped) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ window.__authFetchWrapped = true;
|
|
|
|
|
+
|
|
|
|
|
+ const originalFetch = window.__nativeFetch;
|
|
|
window.fetch = async function (url, options = {}) {
|
|
window.fetch = async function (url, options = {}) {
|
|
|
if (options.headers === undefined) {
|
|
if (options.headers === undefined) {
|
|
|
options.headers = {};
|
|
options.headers = {};
|
|
|
}
|
|
}
|
|
|
- options.headers['Authorization'] = `Bearer ${token}`;
|
|
|
|
|
|
|
+ // 每次请求都读取最新token,避免闭包持有旧token导致长期运行后401
|
|
|
|
|
+ const latestToken = localStorage.getItem('auth_token');
|
|
|
|
|
+ if (latestToken) {
|
|
|
|
|
+ options.headers['Authorization'] = `Bearer ${latestToken}`;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const response = await originalFetch(url, options);
|
|
const response = await originalFetch(url, options);
|
|
@@ -2068,7 +2089,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
const refreshed = await tryRefreshToken();
|
|
const refreshed = await tryRefreshToken();
|
|
|
if (refreshed) {
|
|
if (refreshed) {
|
|
|
// 更新token后重试请求
|
|
// 更新token后重试请求
|
|
|
- options.headers['Authorization'] = `Bearer ${localStorage.getItem('auth_token')}`;
|
|
|
|
|
|
|
+ const retryToken = localStorage.getItem('auth_token');
|
|
|
|
|
+ if (retryToken) {
|
|
|
|
|
+ options.headers['Authorization'] = `Bearer ${retryToken}`;
|
|
|
|
|
+ }
|
|
|
return originalFetch(url, options);
|
|
return originalFetch(url, options);
|
|
|
} else {
|
|
} else {
|
|
|
// 刷新失败,清除认证信息并跳转登录
|
|
// 刷新失败,清除认证信息并跳转登录
|