/** * API 统一请求层(规范 §131) * * 职责:纯 HTTP 客户端,统一处理 Token / 超时 / 错误码 / Session 失效。 * 不含任何业务域方法(域方法在 services/ 层,规范 §130 页面不直接调用底层 request)。 */ const { BUSINESS_LIMITS, REQUEST_ERROR } = require('../constants'); const { API_BASE_URL } = require('../env'); class ApiClient { constructor() { this.baseURL = API_BASE_URL; this.token = null; this._refreshToken = null; /** @type {((reason: any) => void)|null} Session 失效回调,由 AuthStore 注入 */ this.onSessionExpired = null; /** @type {((config: object) => void)|null} 维护态拦截回调,由 AppStore 注入 */ this.onMaintenance = null; } /** 读取本地存储的 access token */ getToken() { if (this.token) return this.token; try { this.token = wx.getStorageSync('accessToken') || null; this._refreshToken = wx.getStorageSync('refreshToken') || null; } catch (e) { // ignore } return this.token; } /** 读取本地存储的 refresh token(供 auth service 刷新使用) */ getRefreshToken() { if (this._refreshToken) return this._refreshToken; try { this._refreshToken = wx.getStorageSync('refreshToken') || null; } catch (e) { // ignore } return this._refreshToken; } /** 持久化 token(登录/刷新成功后调用) */ saveToken(token, refreshToken) { this.token = token; this._refreshToken = refreshToken; wx.setStorageSync('accessToken', token); wx.setStorageSync('refreshToken', refreshToken); } /** 清除 token(登出/Session 失效后调用) */ clearToken() { this.token = null; this._refreshToken = null; wx.removeStorageSync('accessToken'); wx.removeStorageSync('refreshToken'); } /** 生成请求 ID(用于链路追踪) */ generateRequestId() { return Date.now().toString(36) + Math.random().toString(36).substring(2, 8); } /** * 通用请求方法 * @param {{url: string, method?: string, data?: any, headers?: object, idempotencyKey?: string, timeout?: number}} options */ request(options) { const { url, method = 'GET', data, headers = {}, idempotencyKey, timeout } = options; const token = this.getToken(); const requestId = this.generateRequestId(); return new Promise((resolve, reject) => { wx.request({ url: this.baseURL + url, method, data, timeout: timeout || BUSINESS_LIMITS.REQUEST_TIMEOUT_MS, header: { 'Content-Type': 'application/json', 'X-Request-Id': requestId, ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}), ...(token ? { Authorization: `Bearer ${token}` } : {}), ...headers, }, success: (res) => { // 维护态拦截(规范 §77) if (res.statusCode === 503) { if (typeof this.onMaintenance === 'function') this.onMaintenance(res.data || {}); reject({ code: REQUEST_ERROR.MAINTENANCE, message: '平台正在维护,请稍后再试', requestId, }); return; } if (res.statusCode === 200) { const body = res.data || {}; if (body.success) { resolve(body.data); } else { reject({ code: body.code || REQUEST_ERROR.BUSINESS_ERROR, message: body.message, requestId, }); } } else if (res.statusCode === 401) { this.clearToken(); if (typeof this.onSessionExpired === 'function') 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, data, headers) { return this.request({ url, method: 'GET', data, headers }); } post(url, data, headers, idempotencyKey) { return this.request({ url, method: 'POST', data, headers, idempotencyKey }); } put(url, data, headers, idempotencyKey) { return this.request({ url, method: 'PUT', data, headers, idempotencyKey }); } patch(url, data, headers, idempotencyKey) { return this.request({ url, method: 'PATCH', data, headers, idempotencyKey }); } delete(url, data, headers, idempotencyKey) { return this.request({ url, method: 'DELETE', data, headers, idempotencyKey }); } } const apiClient = new ApiClient(); module.exports = apiClient;