import { eventBus, EVENT_TYPES, notifyApiError } from "./event-bus" // API 基础路径 const API_BASE_URL = "/api" // Token 过期时间 (毫秒) const TOKEN_EXPIRY_TIME = 24 * 60 * 60 * 1000 // 24小时 /** * 检查 token 是否过期 * @returns boolean */ export function isTokenExpired(): boolean { const tokenTimestamp = localStorage.getItem("tokenTimestamp") if (!tokenTimestamp) return true const timestamp = Number.parseInt(tokenTimestamp, 10) const now = Date.now() return now - timestamp > TOKEN_EXPIRY_TIME } /** * 保存 token 和时间戳 * @param token 授权令牌 */ export function saveToken(token: string): void { localStorage.setItem("token", token) localStorage.setItem("tokenTimestamp", Date.now().toString()) } /** * 清除 token 和相关数据 */ export function clearToken(): void { localStorage.removeItem("token") localStorage.removeItem("tokenTimestamp") localStorage.removeItem("user") } /** * 发送带有授权令牌的 API 请求 * @param url 请求 URL (不需要包含 API_BASE_URL) * @param options 请求选项 * @returns Promise */ export async function fetchWithAuth(url: string, options: RequestInit = {}): Promise { // 从本地存储获取 token const token = localStorage.getItem("token") // 准备请求头 const headers = new Headers(options.headers || {}) // 如果有 token,添加到请求头 if (token) { headers.set("Authorization", "Bearer " + token) } // 确保 Content-Type 为 application/json if (options.body && !headers.has("Content-Type")) { headers.set("Content-Type", "application/json") } // 合并选项 const mergedOptions = { ...options, headers, } // 构建完整 URL (确保不重复添加 /api) const fullUrl = url.startsWith("/") ? `${API_BASE_URL}${url}` : `${API_BASE_URL}/${url}` try { // 发送请求 const response = await fetch(fullUrl, mergedOptions) // 检查是否是 401 错误 (未授权),可能是 token 过期 if (response.status === 401) { // 清除无效的 token clearToken() // 触发认证过期事件 eventBus.emit(EVENT_TYPES.AUTH_EXPIRED) // 如果不是登录或注册请求,可以重定向到登录页面 if (!url.includes("login") && !url.includes("register")) { // 使用 window.location 而不是 router,因为这是一个工具函数 window.location.href = "/login" return new Response(JSON.stringify({ code: 401, msg: "登录已过期,请重新登录" }), { headers: { "Content-Type": "application/json" }, }) } } // 克隆响应以便可以读取两次 const clonedResponse = response.clone() // 尝试解析响应为JSON try { const data = await clonedResponse.json() // 如果响应码不是200,触发API错误事件 if (data.code !== 0) { eventBus.emit(EVENT_TYPES.API_ERROR, { url, status: response.status, code: data.code, message: data.msg || "请求失败", }) // 使用事件总线发送通知 notifyApiError("请求失败", data.msg || "服务器返回错误") } } catch (e) { // 如果响应不是JSON格式,忽略错误 } return response } catch (error) { // 网络错误或其他异常 const errorMessage = error instanceof Error ? error.message : "网络请求失败" // 触发API错误事件 eventBus.emit(EVENT_TYPES.API_ERROR, { url, error: errorMessage, }) // 使用事件总线发送通知 notifyApiError("网络错误", errorMessage) // 重新抛出错误,让调用者处理 throw error } }