소스 검색

token刷新问题修复

wyn 4 달 전
부모
커밋
626e4c8b91
4개의 변경된 파일199개의 추가작업 그리고 309개의 파일을 삭제
  1. 5 5
      src/App.tsx
  2. 104 52
      src/utils/auth.ts
  3. 89 251
      src/utils/axios.ts
  4. 1 1
      src/views/Login.tsx

+ 5 - 5
src/App.tsx

@@ -11,9 +11,9 @@ export default function App() {
     console.log('[App] refreshToken存在:', !!refreshToken);
     
     if (refreshToken) {
-      // 每隔1秒检测一次token是否失效
+      // 每隔30秒检测一次token是否失效
       console.log('[App] 启动token检测');
-      startTokenCheck(1);
+      startTokenCheck(30);
     } else {
       console.log('[App] 没有refreshToken,不启动token检测');
     }
@@ -31,15 +31,15 @@ export default function App() {
       const refreshToken = getRefreshToken();
       if (refreshToken) {
         console.log('[App] 检测到refreshToken,启动token检测');
-        startTokenCheck(1);
+        startTokenCheck(30);
       }
     };
     
     // 立即检查一次
     checkAndStart();
     
-    // 每2秒检查一次refreshToken是否存在(用于登录后启动检测)
-    const checkInterval = setInterval(checkAndStart, 2000);
+    // 每5秒检查一次refreshToken是否存在(用于登录后启动检测)
+    const checkInterval = setInterval(checkAndStart, 5000);
     
     return () => {
       clearInterval(checkInterval);

+ 104 - 52
src/utils/auth.ts

@@ -286,53 +286,81 @@ const checkTokenAndRefresh = async () => {
       return;
     }
     
-    const url = `${baseURL}/system/notify-message/get-unread-count`;
-    console.log(`[TokenCheck] 调用检测接口: ${url}`);
+    // 使用一个轻量级的接口来检测token是否有效
+    // 优先使用get-unread-count,如果失败则尝试其他接口
+    const testUrls = [
+      `${baseURL}/system/notify-message/get-unread-count`,
+      `${baseURL}/system/auth/get-permission-info`,
+    ];
     
-    const response = await axios.get(url, {
-      headers: {
-        'Authorization': `Bearer ${token}`,
-        'Content-Type': 'application/json',
-      },
-      validateStatus: (status) => {
-        // 接受所有状态码,包括401,以便我们能够检测到登录状态失效
-        return status >= 200 && status < 500;
-      },
-    });
+    let tokenExpired = false;
+    let lastError: any = null;
     
-    console.log(`[TokenCheck] 接口响应: status=${response.status}, code=${response.data?.code}`);
-    
-    // 检查响应数据,如果code是401或HTTP状态码是401,说明token失效
-    const responseData = response.data;
-    const isTokenExpired = 
-      response.status === 401 || 
-      (responseData && (responseData.code === 401 || responseData.msg === '账号未登录'));
-    
-    if (isTokenExpired) {
-      console.log('[TokenCheck] 检测到token失效,开始刷新token...');
-      await refreshTokenIfNeeded(refreshToken);
-    } else {
-      console.log('[TokenCheck] Token有效,无需刷新');
+    // 尝试每个检测接口
+    for (const url of testUrls) {
+      try {
+        console.log(`[TokenCheck] 调用检测接口: ${url}`);
+        
+        const response = await axios.get(url, {
+          headers: {
+            'Authorization': `Bearer ${token}`,
+            'Content-Type': 'application/json',
+          },
+          validateStatus: (status) => {
+            // 接受所有状态码,包括401,以便我们能够检测到登录状态失效
+            return status >= 200 && status < 500;
+          },
+          timeout: 5000, // 5秒超时
+        });
+        
+        console.log(`[TokenCheck] 接口响应: status=${response.status}, code=${response.data?.code}`);
+        
+        // 检查响应数据,如果code是401或HTTP状态码是401,说明token失效
+        const responseData = response.data;
+        const isExpired = 
+          response.status === 401 || 
+          (responseData && (responseData.code === 401 || responseData.msg === '账号未登录'));
+        
+        if (isExpired) {
+          tokenExpired = true;
+          console.log('[TokenCheck] 检测到token失效');
+          break;
+        } else {
+          // token有效,不需要继续检测
+          console.log('[TokenCheck] Token有效,无需刷新');
+          return;
+        }
+      } catch (error: any) {
+        console.log(`[TokenCheck] 接口调用异常:`, error?.message || error);
+        lastError = error;
+        
+        // 检查错误响应,如果是401,说明token失效
+        const isExpired = 
+          error?.response?.status === 401 ||
+          (error?.response?.data && (
+            error.response.data.code === 401 || 
+            error.response.data.msg === '账号未登录'
+          ));
+        
+        if (isExpired) {
+          tokenExpired = true;
+          console.log('[TokenCheck] 检测到token失效(通过错误响应)');
+          break;
+        }
+        // 如果是网络错误等其他错误,继续尝试下一个接口
+      }
     }
-    // 如果返回正常,说明token有效,不需要处理
-  } catch (error: any) {
-    console.log(`[TokenCheck] 接口调用异常:`, error?.message || error);
     
-    // 检查错误响应,如果是401,说明token失效
-    const isTokenExpired = 
-      error?.response?.status === 401 ||
-      (error?.response?.data && (
-        error.response.data.code === 401 || 
-        error.response.data.msg === '账号未登录'
-      ));
-    
-    if (isTokenExpired) {
-      console.log('[TokenCheck] 检测到token失效(通过错误响应),开始刷新token...');
+    // 如果检测到token失效,立即刷新
+    if (tokenExpired) {
+      console.log('[TokenCheck] 开始刷新token...');
       await refreshTokenIfNeeded(refreshToken);
-    } else {
-      // 其他错误(网络问题等),不处理,不退出登录
-      console.log('[TokenCheck] Token检测接口调用失败:', error?.message || error, '但不退出登录');
+    } else if (lastError) {
+      // 所有接口都失败,但不是401错误,可能是网络问题
+      console.log('[TokenCheck] Token检测接口调用失败:', lastError?.message || lastError, '但不退出登录');
     }
+  } catch (error: any) {
+    console.log(`[TokenCheck] 检测过程异常:`, error?.message || error);
   } finally {
     isChecking = false;
     console.log('[TokenCheck] 检测完成');
@@ -342,20 +370,44 @@ const checkTokenAndRefresh = async () => {
 // 刷新token的辅助函数
 const refreshTokenIfNeeded = async (refreshToken: string) => {
   try {
+    console.log('[TokenCheck] 开始调用refreshToken接口...');
     const { loginApi } = await import('../api/Login');
-    const refreshResponse = await loginApi.refreshToken(refreshToken);
+    
+    // 使用原生axios调用,绕过拦截器,避免循环
+    const axios = (await import('axios')).default;
+    const { env } = await import('./env');
+    const baseURL = env.baseUrl ? `${env.baseUrl}/admin-api` : '/admin-api';
+    
+    const response = await axios.post(
+      `${baseURL}/system/auth/refresh-token?refreshToken=${encodeURIComponent(refreshToken)}`,
+      {},
+      {
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        validateStatus: (status) => status < 500, // 接受所有非5xx状态码
+      }
+    );
     
     // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
+    const responseData = response.data;
     let tokenData;
-    if (refreshResponse?.code === 0 && refreshResponse?.data) {
+    
+    if (responseData?.code === 0 && responseData?.data) {
       // 标准格式:{code: 0, data: {...}}
-      tokenData = refreshResponse.data;
-    } else if (refreshResponse?.data) {
+      tokenData = responseData.data;
+    } else if (responseData?.code === 200 && responseData?.data) {
+      // 兼容格式:{code: 200, data: {...}}
+      tokenData = responseData.data;
+    } else if (responseData?.data) {
       // 兼容格式:{data: {...}}
-      tokenData = refreshResponse.data;
+      tokenData = responseData.data;
+    } else if (responseData?.accessToken || responseData?.token) {
+      // 直接是token数据对象
+      tokenData = responseData;
     } else {
-      // 直接是数据对象
-      tokenData = refreshResponse;
+      // 可能是直接返回的数据
+      tokenData = responseData;
     }
     
     const newAccessToken = tokenData?.accessToken || tokenData?.token;
@@ -368,13 +420,13 @@ const refreshTokenIfNeeded = async (refreshToken: string) => {
       if (newRefreshToken) {
         setRefreshToken(newRefreshToken);
       }
-      console.log('Token刷新成功');
+      console.log('[TokenCheck] Token刷新成功,新token已保存');
     } else {
-      console.warn('刷新token失败:未返回新token,但不退出登录');
+      console.warn('[TokenCheck] 刷新token失败:未返回新token,但不退出登录');
       // 即使刷新失败,也不退出登录,继续保留当前页面
     }
-  } catch (refreshError) {
-    console.warn('刷新token失败:', refreshError, '但不退出登录,继续保留当前页面');
+  } catch (refreshError: any) {
+    console.warn('[TokenCheck] 刷新token失败:', refreshError?.message || refreshError, '但不退出登录,继续保留当前页面');
     // 即使刷新失败,也不退出登录,继续保留当前页面
   }
 };

+ 89 - 251
src/utils/axios.ts

@@ -33,6 +33,90 @@ const processQueue = (error: any, token: string | null = null) => {
   failedQueue = [];
 };
 
+// 统一的token刷新函数
+const refreshTokenAndRetry = async (originalRequest: InternalAxiosRequestConfig & { _retry?: boolean }) => {
+  // 如果是刷新token的请求失败,不退出登录,继续保留当前页面
+  if (originalRequest.url?.includes('/refresh-token') || originalRequest._retry) {
+    const errorMessage = 'Token刷新失败,请稍后重试';
+    return Promise.reject(new Error(errorMessage));
+  }
+
+  // 如果正在刷新token,将请求加入队列
+  if (isRefreshing) {
+    return new Promise((resolve, reject) => {
+      failedQueue.push({ resolve, reject });
+    })
+      .then((token) => {
+        if (originalRequest.headers) {
+          originalRequest.headers.Authorization = `Bearer ${token}`;
+        }
+        return axiosInstance(originalRequest);
+      })
+      .catch((err) => {
+        return Promise.reject(err);
+      });
+  }
+
+  // 尝试刷新token
+  const refreshToken = getRefreshToken();
+  if (!refreshToken) {
+    const errorMessage = 'RefreshToken不存在,无法刷新';
+    return Promise.reject(new Error(errorMessage));
+  }
+
+  isRefreshing = true;
+  originalRequest._retry = true;
+
+  try {
+    const res: any = await loginApi.refreshToken(refreshToken);
+    
+    // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
+    let tokenData;
+    if (res?.code === 0 && res?.data) {
+      // 标准格式:{code: 0, data: {...}}
+      tokenData = res.data;
+    } else if (res?.data) {
+      // 兼容格式:{data: {...}}
+      tokenData = res.data;
+    } else {
+      // 直接是数据对象
+      tokenData = res;
+    }
+    
+    const newAccessToken = tokenData?.accessToken || tokenData?.token;
+    const newRefreshToken = tokenData?.refreshToken;
+
+    if (newAccessToken) {
+      // 更新token
+      setToken({ accessToken: newAccessToken, token: newAccessToken });
+      setAccessToken(newAccessToken);
+      if (newRefreshToken) {
+        setRefreshToken(newRefreshToken);
+      }
+
+      // 更新请求头
+      if (originalRequest.headers) {
+        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
+      }
+
+      // 处理队列中的请求
+      processQueue(null, newAccessToken);
+
+      // 重试原始请求
+      return axiosInstance(originalRequest);
+    } else {
+      throw new Error('刷新token失败:未返回新token');
+    }
+  } catch (refreshError: any) {
+    // 刷新失败,不清除认证信息,不跳转登录,继续保留当前页面
+    const errorMessage = refreshError?.message || 'Token刷新失败,请稍后重试';
+    processQueue(refreshError, null);
+    return Promise.reject(new Error(errorMessage));
+  } finally {
+    isRefreshing = false;
+  }
+};
+
 // 请求拦截器
 axiosInstance.interceptors.request.use(
   (config) => {
@@ -76,91 +160,7 @@ axiosInstance.interceptors.response.use(
       } else if (data.code === 401) {
         // 业务错误码 401,表示未登录,需要处理 token 刷新或退出登录
         const originalRequest = config as InternalAxiosRequestConfig & { _retry?: boolean };
-        
-        // 如果是刷新token的请求失败,不退出登录,继续保留当前页面
-        if (originalRequest.url?.includes('/refresh-token') || originalRequest._retry) {
-          const errorMessage = 'Token刷新失败,请稍后重试';
-          // 不清除认证信息,不跳转登录,继续保留当前页面
-          return Promise.reject(new Error(errorMessage));
-        }
-
-        // 如果正在刷新token,将请求加入队列
-        if (isRefreshing) {
-          return new Promise((resolve, reject) => {
-            failedQueue.push({ resolve, reject });
-          })
-            .then((token) => {
-              if (originalRequest.headers) {
-                originalRequest.headers.Authorization = `Bearer ${token}`;
-              }
-              return axiosInstance(originalRequest);
-            })
-            .catch((err) => {
-              return Promise.reject(err);
-            });
-        }
-
-        // 尝试刷新token
-        const refreshToken = getRefreshToken();
-        if (!refreshToken) {
-          const errorMessage = 'RefreshToken不存在,无法刷新';
-          // 不清除认证信息,不跳转登录,继续保留当前页面
-          return Promise.reject(new Error(errorMessage));
-        }
-
-        isRefreshing = true;
-        originalRequest._retry = true;
-
-        return loginApi.refreshToken(refreshToken)
-          .then((res: any) => {
-            // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
-            let tokenData;
-            if (res?.code === 0 && res?.data) {
-              // 标准格式:{code: 0, data: {...}}
-              tokenData = res.data;
-            } else if (res?.data) {
-              // 兼容格式:{data: {...}}
-              tokenData = res.data;
-            } else {
-              // 直接是数据对象
-              tokenData = res;
-            }
-            
-            const newAccessToken = tokenData?.accessToken || tokenData?.token;
-            const newRefreshToken = tokenData?.refreshToken;
-
-            if (newAccessToken) {
-              // 更新token
-              setToken({ accessToken: newAccessToken, token: newAccessToken });
-              setAccessToken(newAccessToken);
-              if (newRefreshToken) {
-                setRefreshToken(newRefreshToken);
-              }
-
-              // 更新请求头
-              if (originalRequest.headers) {
-                originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
-              }
-
-              // 处理队列中的请求
-              processQueue(null, newAccessToken);
-
-              // 重试原始请求
-              return axiosInstance(originalRequest);
-            } else {
-              throw new Error('刷新token失败:未返回新token');
-            }
-          })
-          .catch((refreshError: any) => {
-            // 刷新失败,不清除认证信息,不跳转登录,继续保留当前页面
-            const errorMessage = refreshError?.message || 'Token刷新失败,请稍后重试';
-            processQueue(refreshError, null);
-            // 不清除认证信息,不跳转登录
-            return Promise.reject(new Error(errorMessage));
-          })
-          .finally(() => {
-            isRefreshing = false;
-          });
+        return refreshTokenAndRetry(originalRequest);
       } else {
         // 其他业务错误 - 创建一个包含更多信息的错误对象
         const errorMessage = data.message || data.msg || '请求失败';
@@ -174,88 +174,7 @@ axiosInstance.interceptors.response.use(
         // 如果 code 是 401,需要处理认证问题
         if (data.code === 401) {
           const originalRequest = config as InternalAxiosRequestConfig & { _retry?: boolean };
-          
-          // 如果是刷新token的请求失败,不退出登录,继续保留当前页面
-          if (originalRequest.url?.includes('/refresh-token') || originalRequest._retry) {
-            // 不清除认证信息,不跳转登录,继续保留当前页面
-            return Promise.reject(error);
-          }
-
-          // 如果正在刷新token,将请求加入队列
-          if (isRefreshing) {
-            return new Promise((resolve, reject) => {
-              failedQueue.push({ resolve, reject });
-            })
-              .then((token) => {
-                if (originalRequest.headers) {
-                  originalRequest.headers.Authorization = `Bearer ${token}`;
-                }
-                return axiosInstance(originalRequest);
-              })
-              .catch((err) => {
-                return Promise.reject(err);
-              });
-          }
-
-          // 尝试刷新token
-          const refreshToken = getRefreshToken();
-          if (!refreshToken) {
-            // 不清除认证信息,不跳转登录,继续保留当前页面
-            return Promise.reject(error);
-          }
-
-          isRefreshing = true;
-          originalRequest._retry = true;
-
-          return loginApi.refreshToken(refreshToken)
-            .then((res: any) => {
-              // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
-              let tokenData;
-              if (res?.code === 0 && res?.data) {
-                // 标准格式:{code: 0, data: {...}}
-                tokenData = res.data;
-              } else if (res?.data) {
-                // 兼容格式:{data: {...}}
-                tokenData = res.data;
-              } else {
-                // 直接是数据对象
-                tokenData = res;
-              }
-              
-              const newAccessToken = tokenData?.accessToken || tokenData?.token;
-              const newRefreshToken = tokenData?.refreshToken;
-
-              if (newAccessToken) {
-                // 更新token
-                setToken({ accessToken: newAccessToken, token: newAccessToken });
-                setAccessToken(newAccessToken);
-                if (newRefreshToken) {
-                  setRefreshToken(newRefreshToken);
-                }
-
-                // 更新请求头
-                if (originalRequest.headers) {
-                  originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
-                }
-
-                // 处理队列中的请求
-                processQueue(null, newAccessToken);
-
-                // 重试原始请求
-                return axiosInstance(originalRequest);
-              } else {
-                throw new Error('刷新token失败:未返回新token');
-              }
-            })
-            .catch((refreshError: any) => {
-              // 刷新失败,不清除认证信息,不跳转登录,继续保留当前页面
-              processQueue(refreshError, null);
-              // 不清除认证信息,不跳转登录
-              return Promise.reject(error);
-            })
-            .finally(() => {
-              isRefreshing = false;
-            });
+          return refreshTokenAndRetry(originalRequest);
         }
         
         // 不在这里显示 toast,让调用方决定是否显示
@@ -277,91 +196,10 @@ axiosInstance.interceptors.response.use(
         case 401:
           // 获取原始请求配置
           const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
-          
-          // 如果是刷新token的请求失败,不退出登录,继续保留当前页面
-          if (originalRequest.url?.includes('/refresh-token') || originalRequest._retry) {
-            errorMessage = 'Token刷新失败,请稍后重试';
-            // 不清除认证信息,不跳转登录,继续保留当前页面
-            return Promise.reject(new Error(errorMessage));
-          }
-
-          // 如果正在刷新token,将请求加入队列
-          if (isRefreshing) {
-            return new Promise((resolve, reject) => {
-              failedQueue.push({ resolve, reject });
-            })
-              .then((token) => {
-                if (originalRequest.headers) {
-                  originalRequest.headers.Authorization = `Bearer ${token}`;
-                }
-                return axiosInstance(originalRequest);
-              })
-              .catch((err) => {
-                return Promise.reject(err);
-              });
-          }
-
-          // 尝试刷新token
-          const refreshToken = getRefreshToken();
-          if (!refreshToken) {
-            errorMessage = 'RefreshToken不存在,无法刷新';
-            // 不清除认证信息,不跳转登录,继续保留当前页面
-            return Promise.reject(new Error(errorMessage));
+          if (originalRequest) {
+            return refreshTokenAndRetry(originalRequest);
           }
-
-          isRefreshing = true;
-          originalRequest._retry = true;
-
-          return loginApi.refreshToken(refreshToken)
-            .then((res: any) => {
-              // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
-              let tokenData;
-              if (res?.code === 0 && res?.data) {
-                // 标准格式:{code: 0, data: {...}}
-                tokenData = res.data;
-              } else if (res?.data) {
-                // 兼容格式:{data: {...}}
-                tokenData = res.data;
-              } else {
-                // 直接是数据对象
-                tokenData = res;
-              }
-              
-              const newAccessToken = tokenData?.accessToken || tokenData?.token;
-              const newRefreshToken = tokenData?.refreshToken;
-
-              if (newAccessToken) {
-                // 更新token
-                setToken({ accessToken: newAccessToken, token: newAccessToken });
-                setAccessToken(newAccessToken);
-                if (newRefreshToken) {
-                  setRefreshToken(newRefreshToken);
-                }
-
-                // 更新请求头
-                if (originalRequest.headers) {
-                  originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
-                }
-
-                // 处理队列中的请求
-                processQueue(null, newAccessToken);
-
-                // 重试原始请求
-                return axiosInstance(originalRequest);
-              } else {
-                throw new Error('刷新token失败:未返回新token');
-              }
-            })
-            .catch((refreshError: any) => {
-              // 刷新失败,不清除认证信息,不跳转登录,继续保留当前页面
-              errorMessage = refreshError?.message || 'Token刷新失败,请稍后重试';
-              processQueue(refreshError, null);
-              // 不清除认证信息,不跳转登录
-              return Promise.reject(new Error(errorMessage));
-            })
-            .finally(() => {
-              isRefreshing = false;
-            });
+          errorMessage = '未授权,请重新登录';
           break;
         case 403:
           errorMessage = '拒绝访问';

+ 1 - 1
src/views/Login.tsx

@@ -343,7 +343,7 @@ export default function Login() {
         
         // 登录成功后启动token检测
         console.log('[Login] 登录成功,启动token检测');
-        startTokenCheck(1);
+        startTokenCheck(30);
         
         navigate(redirectPath);
       }, 500);