Files
cunkebao_v3/Cunkebao/lib/api.ts

253 lines
7.5 KiB
TypeScript
Raw Normal View History

2025-03-29 17:02:40 +08:00
import { handleApiResponse, handleApiError } from "./http-interceptors"
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://yishi.com';
// 安全地获取token
const getToken = (): string | null => {
if (typeof window !== 'undefined') {
return localStorage.getItem('token');
}
return null;
};
// 安全地设置token
const setToken = (token: string): void => {
if (typeof window !== 'undefined') {
localStorage.setItem('token', token);
}
};
// 创建请求头
const createHeaders = (withAuth: boolean = true): HeadersInit => {
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
if (withAuth) {
const token = getToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}
return headers;
};
// 基础请求函数
export const request = async <T>(
url: string,
method: string = 'GET',
data?: any,
withAuth: boolean = true
): Promise<T> => {
const options: RequestInit = {
method,
headers: createHeaders(withAuth),
};
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(`${API_BASE_URL}${url}`, options);
// 检查网络响应状态
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('服务器响应格式错误');
}
2025-03-29 17:02:40 +08:00
// 使用响应拦截器处理响应
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;
2025-03-29 17:02:40 +08:00
} catch (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;
2025-03-29 17:02:40 +08:00
}
};
// 导出便捷的请求方法
export const api = {
get: <T>(url: string, withAuth: boolean = true) =>
request<T>(url, 'GET', undefined, withAuth),
post: <T>(url: string, data: any, withAuth: boolean = true) =>
request<T>(url, 'POST', data, withAuth),
put: <T>(url: string, data: any, withAuth: boolean = true) =>
request<T>(url, 'PUT', data, withAuth),
delete: <T>(url: string, withAuth: boolean = true) =>
request<T>(url, 'DELETE', undefined, withAuth),
};
// 登录API
export const loginApi = {
// 账号密码登录
login: (account: string, password: string) =>
api.post<ApiResponse>('/v1/auth/login', {
account,
password,
typeId: 1 // 默认使用用户类型1
}, false),
// 获取用户信息
getUserInfo: () =>
api.get<ApiResponse>('/v1/auth/info'),
// 刷新Token
refreshToken: () =>
api.post<ApiResponse>('/v1/auth/refresh', {}),
};
// 验证 Token 是否有效
export const validateToken = async (): Promise<boolean> => {
// 如果在服务端直接返回false避免在服务端发起不必要的请求
if (typeof window === 'undefined') {
return false;
}
2025-03-29 17:02:40 +08:00
try {
// 直接使用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;
}
2025-03-29 17:02:40 +08:00
} catch (error) {
// 网络错误或其他异常不应该导致token被视为无效
console.error('验证token时发生异常:', error);
// 对于网络连接问题不直接判定为token无效
return true;
2025-03-29 17:02:40 +08:00
}
};
// 刷新令牌
export const refreshAuthToken = async (): Promise<boolean> => {
// 如果在服务端直接返回false
if (typeof window === 'undefined') {
return false;
}
2025-03-29 17:02:40 +08:00
try {
const response = await loginApi.refreshToken();
if (response.code === 200 && response.data?.token) {
// 更新本地存储的token
setToken(response.data.token);
return true;
}
return false;
} catch (error) {
console.error('刷新Token失败:', error);
return false;
}
};
// 定义响应类型接口
export interface ApiResponse {
code: number;
msg: string;
data?: any;
}