255 lines
8.6 KiB
JavaScript
Raw Normal View History

2026-03-30 18:08:27 +08:00
const { getBaseUrl } = require('./constants');
2026-04-07 23:50:41 +08:00
// Token刷新状态和请求队列
let isRefreshing = false;
let requestQueue = [];
function withAuthHeader(needAuth, header, useRefreshTokenOnly = false) {
2026-03-30 18:08:27 +08:00
const app = getApp();
if (!needAuth) return header;
2026-04-07 23:50:41 +08:00
const accessToken = app && app.globalData ? app.globalData.accessToken : '';
2026-04-03 17:57:08 +08:00
const refreshToken = app && app.globalData ? app.globalData.refreshToken : '';
2026-04-07 23:50:41 +08:00
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}`;
}
2026-03-30 18:08:27 +08:00
}
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));
}
2026-04-03 17:57:08 +08:00
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;
2026-04-07 23:50:41 +08:00
// 从响应头获取新的 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;
2026-04-03 17:57:08 +08:00
const app = getApp();
2026-04-07 23:50:41 +08:00
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;
}
}
2026-04-03 17:57:08 +08:00
}
2026-04-07 23:50:41 +08:00
// 如果正在刷新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' });
}
});
2026-04-03 17:57:08 +08:00
}
2026-03-30 18:08:27 +08:00
function request({
path,
method = 'GET',
data = {},
needAuth = false,
2026-04-03 17:57:08 +08:00
contentType = 'application/json',
// Internal flag: prevent infinite retry when refresh token也失败
2026-04-07 23:50:41 +08:00
__retry = false,
// Internal flag: 标记这是刷新token的重试请求
__refreshRetry = false
2026-03-30 18:08:27 +08:00
}) {
const baseUrl = getBaseUrl();
const url = `${baseUrl}${path}`;
2026-04-07 23:50:41 +08:00
// 正常请求:同时带上 access token 和 refresh token
// 刷新重试请求:只使用 refresh token
2026-03-30 18:08:27 +08:00
const header = withAuthHeader(needAuth, {
'content-type': contentType
2026-04-07 23:50:41 +08:00
}, __refreshRetry);
2026-03-30 18:08:27 +08:00
return new Promise((resolve, reject) => {
wx.request({
url,
method: method.toUpperCase(),
data: method.toUpperCase() === 'GET' ? data : data,
header: header,
2026-04-04 18:57:50 +08:00
success: async (res) => {
2026-04-03 17:57:08 +08:00
// 如果后端在 Response Headers 下发新的 token,就替换本地 token
2026-04-07 23:50:41 +08:00
const tokenUpdated = tryUpdateAccessTokenFromResponseHeaders(res);
2026-04-04 18:57:50 +08:00
const statusCode = res && res.data && res.data.statusCode ? res.data.statusCode : 0;
2026-03-30 18:08:27 +08:00
const payload = res && res.data !== undefined ? res.data : null;
2026-04-07 23:50:41 +08:00
// ── 401 未授权:尝试刷新Token ──
if (statusCode === 401 && needAuth && !__retry) {
// 如果已经在刷新Token,将请求加入队列
if (isRefreshing) {
requestQueue.push((newToken) => {
resolve(request({ path, method, data, needAuth, contentType, __retry: true }));
});
return;
2026-04-04 18:57:50 +08:00
}
2026-04-07 23:50:41 +08:00
// 开始刷新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');
2026-04-04 18:57:50 +08:00
}
2026-04-07 23:50:41 +08:00
// 重发请求
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();
2026-04-04 18:57:50 +08:00
reject(new Error('Unauthorized'));
return;
}
2026-04-07 23:50:41 +08:00
// ── 403 禁止访问 ──
2026-04-04 18:57:50 +08:00
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 ────────────────────────
2026-03-30 18:08:27 +08:00
if (payload && typeof payload === 'object' && typeof payload.succeeded === 'boolean') {
2026-04-04 18:57:50 +08:00
if (payload.succeeded) {
resolve(payload.data);
} else {
reject(normalizeError(payload));
}
2026-03-30 18:08:27 +08:00
return;
}
2026-04-04 18:57:50 +08:00
// ── Fallback:直接返回原始 data ───────────────────────────────
2026-03-30 18:08:27 +08:00
resolve(payload);
},
fail(err) {
2026-04-07 23:50:41 +08:00
// 网络请求失败处理
2026-03-30 18:08:27 +08:00
reject(err);
}
});
});
}
module.exports = {
request
};