私域操盘手 - 修复 http-interceptors.ts 文件中未准确定位401错误时,可能导致意外删除 localStorage 中 token 的问题

This commit is contained in:
柳清爽
2025-05-08 14:51:22 +08:00
parent 1ec9863a8a
commit 5d83938c24
4 changed files with 313 additions and 50 deletions

View File

@@ -51,13 +51,79 @@ export const request = async <T>(
try {
const response = await fetch(`${API_BASE_URL}${url}`, options);
const result = await response.json();
// 检查网络响应状态
if (!response.ok) {
// 只有当响应状态码为401时才特殊处理为认证错误
if (response.status === 401) {
if (typeof window !== 'undefined') {
// 直接调用handleTokenExpired而不是handleApiError以处理401错误
// 因为有响应体的情况下我们应该让handleApiResponse处理401
// 在这里只处理没有响应体的401网络错误
const errorMessage = `Unauthorized: ${response.statusText}`;
console.error('授权错误:', errorMessage);
// 尝试解析响应如果无法解析才直接处理token过期
try {
await response.json();
// 如果能够解析,让后续代码处理
} catch (e) {
// 如果无法解析说明是纯网络层401错误直接处理token过期
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = '/login';
throw new Error(errorMessage);
}
}
}
}
// 其他HTTP错误正常返回让上层组件自行处理
}
let result;
try {
result = await response.json();
} catch (parseError) {
// 处理JSON解析错误
console.error('无法解析响应JSON:', parseError);
throw new Error('服务器响应格式错误');
}
// 使用响应拦截器处理响应
return handleApiResponse<T>(response, result);
if (result && result.code === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
localStorage.removeItem('user');
// 使用客户端导航而不是直接修改window.location
setTimeout(() => {
window.location.href = '/login';
}, 0);
}
throw new Error(result.msg || '登录已过期,请重新登录');
}
// 返回结果而不调用可能会清除token的handleApiResponse
return result;
} catch (error) {
// 使用错误拦截器处理错误
return handleApiError(error);
// 只有在确认是401错误时才清除token
if (error instanceof Error &&
(error.message.includes('401') ||
(error.message.toLowerCase().includes('unauthorized') &&
error.message.toLowerCase().includes('token')))) {
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
localStorage.removeItem('user');
setTimeout(() => {
window.location.href = '/login';
}, 0);
}
}
// 其他错误直接抛出但不调用handleApiError
console.error('API请求错误:', error);
throw error;
}
};
@@ -103,10 +169,58 @@ export const validateToken = async (): Promise<boolean> => {
}
try {
const response = await loginApi.getUserInfo();
return response.code === 200;
// 直接使用fetch而不是通过api调用以便捕获具体错误
const token = getToken();
if (!token) return false;
const response = await fetch(`${API_BASE_URL}/v1/auth/info`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
// 如果状态码是401明确是认证问题
if (response.status === 401) {
return false;
}
// 如果是其他HTTP错误我们不确定是否是认证问题
if (!response.ok) {
// 对于非401错误不要立即判定token无效
console.warn(`验证token时收到HTTP错误: ${response.status} ${response.statusText}`);
// 尝试读取响应内容
try {
const result = await response.json();
// 只有明确返回code为401才判断为token无效
if (result && result.code === 401) {
return false;
}
// 其他错误代码视为服务端问题不影响token有效性
return true;
} catch (parseError) {
// 无法解析响应视为网络或服务器问题不影响token
console.error('无法解析验证token的响应:', parseError);
return true;
}
}
// 正常情况下尝试解析响应并检查code
try {
const result = await response.json();
return result.code === 200;
} catch (parseError) {
// 无法解析响应视为网络或服务器问题不影响token
console.error('无法解析验证token的响应:', parseError);
return true;
}
} catch (error) {
return false;
// 网络错误或其他异常不应该导致token被视为无效
console.error('验证token时发生异常:', error);
// 对于网络连接问题不直接判定为token无效
return true;
}
};

View File

@@ -1,4 +1,5 @@
import { useRouter } from "next/navigation";
import { toast } from "@/components/ui/use-toast";
// Token过期处理
export const handleTokenExpired = () => {
@@ -15,34 +16,49 @@ export const handleTokenExpired = () => {
}
};
// 响应拦截器
export const handleApiResponse = <T>(response: Response, result: any): T => {
// 处理token过期情况
if (result && (result.code === 401 || result.msg?.includes('token'))) {
// 仅在客户端处理token过期
if (typeof window !== 'undefined') {
handleTokenExpired();
// 显示API错误但不会重定向
export const showApiError = (error: any, defaultMessage: string = '请求失败') => {
if (typeof window === 'undefined') return; // 服务端不处理
let errorMessage = defaultMessage;
// 尝试从各种可能的错误格式中获取消息
if (error) {
if (typeof error === 'string') {
errorMessage = error;
} else if (error instanceof Error) {
errorMessage = error.message || defaultMessage;
} else if (typeof error === 'object') {
// 尝试从API响应中获取错误消息
errorMessage = error.msg || error.message || error.error || defaultMessage;
}
throw new Error(result.msg || '登录已过期,请重新登录');
}
// 显示错误消息
if (typeof toast !== 'undefined') {
toast({
title: "请求错误",
description: errorMessage,
variant: "destructive"
});
} else {
console.error('API错误:', errorMessage);
}
};
// 响应拦截器 - 此函数已不再使用,保留仅用于兼容性
export const handleApiResponse = <T>(response: Response, result: any): T => {
// 不再处理401响应而是直接返回结果
// 401的处理已移至api.ts中直接处理
console.warn('handleApiResponse已弃用请直接在api.ts中处理响应');
return result;
};
// 处理API错误
// 处理API错误 - 此函数已不再使用,保留仅用于兼容性
export const handleApiError = (error: unknown): never => {
console.error('API请求错误:', error);
if (error instanceof Error) {
// 如果是未授权错误可能是token过期
if (error.message.includes('401') || error.message.includes('token') || error.message.includes('授权')) {
// 仅在客户端处理token过期
if (typeof window !== 'undefined') {
handleTokenExpired();
}
}
throw error;
}
console.warn('handleApiError已弃用请直接在api.ts中处理错误');
throw new Error('未知错误,请稍后重试');
};