/** * HTTP 客户端封装 * 统一处理请求/响应拦截、错误处理、Token 管理 */ import { API_BASE_URL } from '../config/env'; import type { ApiResponse } from '../types/api'; // Token 存储键名 const TOKEN_KEY = 'texpixel_token'; const TOKEN_EXPIRES_KEY = 'texpixel_token_expires'; const USER_EMAIL_KEY = 'texpixel_user_email'; /** * Token 管理工具 */ export const tokenManager = { getToken(): string | null { return localStorage.getItem(TOKEN_KEY); }, setToken(token: string, expiresAt: number, email?: string): void { localStorage.setItem(TOKEN_KEY, token); localStorage.setItem(TOKEN_EXPIRES_KEY, expiresAt.toString()); if (email) { localStorage.setItem(USER_EMAIL_KEY, email); } }, removeToken(): void { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(TOKEN_EXPIRES_KEY); localStorage.removeItem(USER_EMAIL_KEY); }, getEmail(): string | null { return localStorage.getItem(USER_EMAIL_KEY); }, isTokenValid(): boolean { const token = this.getToken(); const expiresAt = localStorage.getItem(TOKEN_EXPIRES_KEY); if (!token || !expiresAt) return false; // 提前 5 分钟判定为过期 const expiresTimestamp = parseInt(expiresAt, 10) * 1000; return Date.now() < expiresTimestamp - 5 * 60 * 1000; }, getExpiresAt(): number | null { const expiresAt = localStorage.getItem(TOKEN_EXPIRES_KEY); return expiresAt ? parseInt(expiresAt, 10) : null; }, }; /** * 自定义 API 错误类 */ export class ApiError extends Error { constructor( public code: number, message: string, public requestId?: string ) { super(message); this.name = 'ApiError'; } } /** * 请求配置类型 */ interface RequestConfig extends RequestInit { skipAuth?: boolean; successCodes?: number[]; } /** * 发起 HTTP 请求 */ async function request( endpoint: string, config: RequestConfig = {} ): Promise> { const { skipAuth = false, successCodes = [200], headers: customHeaders, ...restConfig } = config; const headers: HeadersInit = { 'Content-Type': 'application/json', ...customHeaders, }; // 自动添加 Authorization header if (!skipAuth) { const token = tokenManager.getToken(); if (token) { (headers as Record)['Authorization'] = token; } } const url = `${API_BASE_URL}${endpoint}`; try { const response = await fetch(url, { ...restConfig, headers, }); const data: ApiResponse = await response.json(); // 统一处理业务错误 if (!successCodes.includes(data.code)) { throw new ApiError(data.code, data.message, data.request_id); } return data; } catch (error) { if (error instanceof ApiError) { throw error; } // 网络错误或其他错误 throw new ApiError(-1, '网络错误,请检查网络连接'); } } /** * HTTP 方法快捷函数 */ export const http = { get(endpoint: string, config?: RequestConfig): Promise> { return request(endpoint, { ...config, method: 'GET' }); }, post(endpoint: string, body?: unknown, config?: RequestConfig): Promise> { return request(endpoint, { ...config, method: 'POST', body: body ? JSON.stringify(body) : undefined, }); }, put(endpoint: string, body?: unknown, config?: RequestConfig): Promise> { return request(endpoint, { ...config, method: 'PUT', body: body ? JSON.stringify(body) : undefined, }); }, delete(endpoint: string, config?: RequestConfig): Promise> { return request(endpoint, { ...config, method: 'DELETE' }); }, }; export default http;