/** * 统一请求封装 * * - 自动附加 Authorization: Bearer * - 401 时自动尝试 refresh_token 刷新,刷新成功后重试原请求 * - 刷新失败时清除 token 并跳转 login 页面 * - BASE_URL 从 config.ts 读取(按运行环境自动切换) */ import { API_BASE } from "./config" export interface RequestOptions { url: string method?: "GET" | "POST" | "PUT" | "DELETE" data?: any header?: Record /** 是否需要认证,默认 true */ needAuth?: boolean } /** 是否正在刷新 token,防止并发刷新 */ let isRefreshing = false /** 刷新期间排队的请求(等刷新完成后统一重试) */ let pendingQueue: Array<{ resolve: (value: any) => void reject: (reason: any) => void options: RequestOptions }> = [] /** * 从 globalData 或 Storage 获取 access_token */ function getAccessToken(): string | undefined { const app = getApp() return app.globalData.token || wx.getStorageSync("token") || undefined } /** * 从 globalData 或 Storage 获取 refresh_token */ function getRefreshToken(): string | undefined { const app = getApp() return app.globalData.refreshToken || wx.getStorageSync("refreshToken") || undefined } /** * 清除本地存储的所有 token */ function clearTokens(): void { const app = getApp() app.globalData.token = undefined app.globalData.refreshToken = undefined wx.removeStorageSync("token") wx.removeStorageSync("refreshToken") } /** * 保存 token 到 globalData 和 Storage */ function saveTokens(accessToken: string, refreshToken: string): void { const app = getApp() app.globalData.token = accessToken app.globalData.refreshToken = refreshToken wx.setStorageSync("token", accessToken) wx.setStorageSync("refreshToken", refreshToken) } /** * 跳转到登录页(使用 reLaunch 清空页面栈) */ function redirectToLogin(): void { wx.reLaunch({ url: "/pages/login/login" }) } /** * 执行底层 wx.request,返回 Promise */ function wxRequest(options: RequestOptions): Promise { return new Promise((resolve, reject) => { wx.request({ url: `${API_BASE}${options.url}`, method: options.method || "GET", data: options.data, header: options.header || {}, success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data) } else { reject({ statusCode: res.statusCode, data: res.data }) } }, fail(err) { reject({ statusCode: 0, data: err }) }, }) }) } /** * 尝试用 refresh_token 刷新令牌 * * 成功 → 保存新 token 并返回 true * 失败 → 清除 token、跳转登录页、返回 false */ async function tryRefreshToken(): Promise { const rt = getRefreshToken() if (!rt) { clearTokens() redirectToLogin() return false } try { const data = await wxRequest({ url: "/api/xcx/refresh", method: "POST", data: { refresh_token: rt }, header: { "Content-Type": "application/json" }, needAuth: false, }) if (data.access_token && data.refresh_token) { saveTokens(data.access_token, data.refresh_token) return true } // 响应格式异常,视为刷新失败 clearTokens() redirectToLogin() return false } catch { clearTokens() redirectToLogin() return false } } /** * 处理排队中的请求:刷新成功后全部重试,失败则全部拒绝 */ function flushPendingQueue(success: boolean): void { const queue = [...pendingQueue] pendingQueue = [] for (const item of queue) { if (success) { // 重试时会自动附加新 token request(item.options).then(item.resolve, item.reject) } else { item.reject({ statusCode: 401, data: { detail: "刷新令牌失败" } }) } } } /** * 统一请求入口 * * @param options 请求配置 * @returns 响应数据(已解析的 JSON) */ export function request(options: RequestOptions): Promise { const needAuth = options.needAuth !== false const headers: Record = { "Content-Type": "application/json", ...options.header, } // 自动附加 Authorization header if (needAuth) { const token = getAccessToken() if (token) { headers["Authorization"] = `Bearer ${token}` } } const finalOptions: RequestOptions = { ...options, header: headers } return wxRequest(finalOptions).catch(async (err) => { // 非 401 或不需要认证的请求,直接抛出 if (err.statusCode !== 401 || !needAuth) { throw err } // 401 → 尝试刷新 token if (isRefreshing) { // 已有刷新请求在进行中,排队等待 return new Promise((resolve, reject) => { pendingQueue.push({ resolve, reject, options }) }) } isRefreshing = true try { const ok = await tryRefreshToken() flushPendingQueue(ok) if (!ok) { throw { statusCode: 401, data: { detail: "刷新令牌失败" } } } // 刷新成功,用新 token 重试原请求 return request(options) } finally { isRefreshing = false } }) } export default request