const { getBaseUrl } = require('./constants'); // Token刷新状态和请求队列 let isRefreshing = false; let requestQueue = []; function withAuthHeader(needAuth, header, useRefreshTokenOnly = false) { const app = getApp(); if (!needAuth) return header; const accessToken = app && app.globalData ? app.globalData.accessToken : ''; const refreshToken = app && app.globalData ? app.globalData.refreshToken : ''; header = header || {}; if (useRefreshTokenOnly && refreshToken) { // 只使用 refresh token 模式(用于刷新) header['X-Authorization'] = `Bearer ${refreshToken}`; } else if (accessToken) { // 正常模式:使用 access token header.Authorization = `Bearer ${accessToken}`; // 同时带上 refresh token,供后端在需要时自动刷新 if (refreshToken) { header['X-Authorization'] = `Bearer ${refreshToken}`; } } return header; } function normalizeError(payload) { if (!payload) return new Error('Empty response'); // RESTfulResult usually has `errors` and/or `message` if (typeof payload === 'string') return new Error(payload); if (payload.errors) return new Error(payload.errors.toString()); if (payload.message) return new Error(payload.message); return new Error(JSON.stringify(payload)); } function getHeaderValue(headers, headerName) { if (!headers || typeof headers !== 'object') return ''; const target = String(headerName || '').toLowerCase(); for (const k in headers) { if (Object.prototype.hasOwnProperty.call(headers, k) && String(k).toLowerCase() === target) { const v = headers[k]; return typeof v === 'string' ? v : ''; } } return ''; } function tryUpdateAccessTokenFromResponseHeaders(res) { // wx.request 的返回头在 res.header(部分环境也可能是 res.headers) const headers = (res && res.header) || (res && res.headers) || null; // 从响应头获取新的 token const newAccessToken = getHeaderValue(headers, 'access-token') || getHeaderValue(headers, 'x-access-token'); const newRefreshToken = getHeaderValue(headers, 'x-access-token') || getHeaderValue(headers, 'x-refresh-token'); if (!newAccessToken && !newRefreshToken) return false; const app = getApp(); const authData = {}; let hasUpdate = false; if (newAccessToken && newAccessToken !== 'invalid_token') { authData.accessToken = newAccessToken; hasUpdate = true; } if (newRefreshToken && newRefreshToken !== 'invalid_token') { authData.refreshToken = newRefreshToken; hasUpdate = true; } if (hasUpdate) { if (app && typeof app.setAuth === 'function') { app.setAuth(authData); } else { if (authData.accessToken) { wx.setStorageSync('accessToken', authData.accessToken); if (app && app.globalData) app.globalData.accessToken = authData.accessToken; } if (authData.refreshToken) { wx.setStorageSync('refreshToken', authData.refreshToken); if (app && app.globalData) app.globalData.refreshToken = authData.refreshToken; } } } // 如果正在刷新Token且获取到了新Token,执行队列中的请求 if (isRefreshing && newAccessToken && newAccessToken !== 'invalid_token') { isRefreshing = false; requestQueue.forEach((callback) => callback(newAccessToken)); requestQueue = []; return true; } return hasUpdate; } function clearAllTokensAndRedirect() { wx.clearStorageSync(); const app = getApp(); if (app && app.globalData) { app.globalData.accessToken = ''; app.globalData.refreshToken = ''; } wx.showModal({ title: '登录失效', content: '您的登录已失效,请重新登录。', showCancel: false, complete() { wx.navigateTo({ url: '/subpackages/mine/login/login' }); } }); } function request({ path, method = 'GET', data = {}, needAuth = false, contentType = 'application/json', // Internal flag: prevent infinite retry when refresh token也失败 __retry = false, // Internal flag: 标记这是刷新token的重试请求 __refreshRetry = false }) { const baseUrl = getBaseUrl(); const url = `${baseUrl}${path}`; // 正常请求:同时带上 access token 和 refresh token // 刷新重试请求:只使用 refresh token const header = withAuthHeader(needAuth, { 'content-type': contentType }, __refreshRetry); return new Promise((resolve, reject) => { wx.request({ url, method: method.toUpperCase(), data: method.toUpperCase() === 'GET' ? data : data, header: header, success: async (res) => { // 如果后端在 Response Headers 下发新的 token,就替换本地 token const tokenUpdated = tryUpdateAccessTokenFromResponseHeaders(res); const statusCode = res && res.data && res.data.statusCode ? res.data.statusCode : 0; const payload = res && res.data !== undefined ? res.data : null; // ── 401 未授权:尝试刷新Token ── if (statusCode === 401 && needAuth && !__retry) { // 如果已经在刷新Token,将请求加入队列 if (isRefreshing) { requestQueue.push((newToken) => { resolve(request({ path, method, data, needAuth, contentType, __retry: true })); }); return; } // 开始刷新Token // 重发原请求 isRefreshing = true; try { // 检查是否有 refresh token const app = getApp(); const refreshToken = app && app.globalData ? app.globalData.refreshToken : ''; if (!refreshToken) { throw new Error('No refresh token available'); } // 重发请求 await request({ path, method, data, needAuth, contentType, __retry: false, __refreshRetry: true }); // 刷新成功,现在重试原请求 isRefreshing = false; resolve(request({ path, method, data, needAuth, contentType, __retry: true })); return; } catch (refreshErr) { // 刷新失败,清除Token并跳转登录 isRefreshing = false; requestQueue = []; clearAllTokensAndRedirect(); reject(new Error('Unauthorized')); return; } } // ── 401 未授权(重试后仍然失败) ── if (statusCode === 401) { clearAllTokensAndRedirect(); reject(new Error('Unauthorized')); return; } // ── 403 禁止访问 ── if (statusCode === 403) { wx.showToast({ title: '无访问权限', icon: 'none' }); reject(new Error('Forbidden')); return; } // ── 404 资源不存在 ─────────────────────────────────────────── if (statusCode === 404) { wx.showToast({ title: '请求资源不存在', icon: 'none' }); reject(new Error('Not Found')); return; } // ── 5xx 服务端错误 ─────────────────────────────────────────── if (statusCode >= 500) { wx.showToast({ title: '服务器异常,请稍后重试', icon: 'none' }); reject(new Error(`Server Error: ${statusCode}`)); return; } // ── 其他非 2xx ─────────────────────────────────────────────── if (statusCode !== 0 && (statusCode < 200 || statusCode >= 300)) { wx.showToast({ title: `请求失败(${statusCode})`, icon: 'none' }); reject(new Error(`HTTP Error: ${statusCode}`)); return; } // ── 2xx 成功:解析业务层 RESTfulResult ──────────────────────── if (payload && typeof payload === 'object' && typeof payload.succeeded === 'boolean') { if (payload.succeeded) { resolve(payload.data); } else { reject(normalizeError(payload)); } return; } // ── Fallback:直接返回原始 data ─────────────────────────────── resolve(payload); }, fail(err) { // 网络请求失败处理 reject(err); } }); }); } module.exports = { request };