/** * API 统一请求层(TypeScript) * * 职责:纯 HTTP 客户端,统一处理 Token / 超时 / 错误码 / Session 失效。 */ import { API_BASE_URL } from '../env'; import type { ApiResponse } from '../types'; export const REQUEST_ERROR = { NETWORK_ERROR: 'NETWORK_ERROR', UNAUTHORIZED: 'UNAUTHORIZED', FORBIDDEN: 'FORBIDDEN', RATE_LIMITED: 'RATE_LIMITED', BUSINESS_ERROR: 'BUSINESS_ERROR', SERVER_ERROR: 'SERVER_ERROR', MAINTENANCE: 'MAINTENANCE', } as const; export interface ApiError { code: string; message: string; requestId: string; error?: unknown; } export class ApiClient { baseURL: string; token: string | null = null; refreshToken: string | null = null; onSessionExpired: ((reason: any) => void) | null = null; onMaintenance: ((data: any) => void) | null = null; constructor() { this.baseURL = API_BASE_URL; } getToken(): string | null { if (this.token) return this.token; try { this.token = wx.getStorageSync('accessToken') as string || null; this.refreshToken = wx.getStorageSync('refreshToken') as string || null; } catch { /* ignore */ } return this.token; } getRefreshToken(): string | null { if (this.refreshToken) return this.refreshToken; try { this.refreshToken = wx.getStorageSync('refreshToken') as string || null; } catch { /* ignore */ } return this.refreshToken; } saveToken(token: string, refreshToken?: string): void { this.token = token; this.refreshToken = refreshToken || null; wx.setStorageSync('accessToken', token); if (refreshToken) wx.setStorageSync('refreshToken', refreshToken); } clearToken(): void { this.token = null; this.refreshToken = null; wx.removeStorageSync('accessToken'); wx.removeStorageSync('refreshToken'); } generateRequestId(): string { return Date.now().toString(36) + Math.random().toString(36).substring(2, 8); } request(options: { url: string; method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; data?: unknown; headers?: Record; idempotencyKey?: string; timeout?: number; }): Promise { const { url, method = 'GET', data, headers = {}, idempotencyKey, timeout = 10000 } = options; const token = this.getToken(); const requestId = this.generateRequestId(); return new Promise((resolve, reject) => { wx.request({ url: this.baseURL + url, method, data, timeout, header: { 'Content-Type': 'application/json', 'X-Request-Id': requestId, ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}), ...(token ? { Authorization: `Bearer ${token}` } : {}), ...headers, }, success: (res: any) => { if (res.statusCode === 503) { if (this.onMaintenance) this.onMaintenance(res.data || {}); reject({ code: REQUEST_ERROR.MAINTENANCE, message: '平台正在维护,请稍后再试', requestId }); return; } if (res.statusCode === 200) { const body = (res.data as any) || {}; if (body.success) { resolve(body.data as T); } else { reject({ code: body.code || REQUEST_ERROR.BUSINESS_ERROR, message: body.message, requestId }); } } else if (res.statusCode === 401) { this.clearToken(); if (this.onSessionExpired) this.onSessionExpired({}); reject({ code: REQUEST_ERROR.UNAUTHORIZED, message: '请重新登录', requestId }); } else if (res.statusCode === 403) { reject({ code: REQUEST_ERROR.FORBIDDEN, message: '无权限访问', requestId }); } else if (res.statusCode === 429) { reject({ code: REQUEST_ERROR.RATE_LIMITED, message: '请求过于频繁,请稍后重试', requestId }); } else { reject({ code: REQUEST_ERROR.SERVER_ERROR, message: '服务暂时不可用', requestId }); } }, fail: (err) => { reject({ code: REQUEST_ERROR.NETWORK_ERROR, message: '网络连接失败', requestId, error: err }); }, }); }); } get(url: string, data?: unknown, headers?: Record): Promise { return this.request({ url, method: 'GET', data, headers }); } post(url: string, data?: unknown, headers?: Record, idempotencyKey?: string): Promise { return this.request({ url, method: 'POST', data, headers, idempotencyKey }); } put(url: string, data?: unknown, headers?: Record): Promise { return this.request({ url, method: 'PUT', data, headers }); } delete(url: string, data?: unknown, headers?: Record): Promise { return this.request({ url, method: 'DELETE', data, headers }); } } export const api = new ApiClient(); export default api;