马梓彭 b157e73eb2 新增检录进度页面
新增裁判认证页面
对接检录页面
对接成绩查询页面
2026-09-01 23:13:21 +08:00

292 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
/** 保留 statusCode,便于调用方按 401/403 等状态做差异化提示 */
function httpError(message, statusCode) {
const error = new Error(message || `请求失败(${statusCode})`);
error.statusCode = statusCode;
return error;
}
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' });
}
});
}
/**
* 清理请求参数,避免微信请求层把 undefined/null 转成 URL 中的字符串。
* GET 会移除无法被后端模型绑定的空参数;POST 仅移除 undefined,保留合法 null。
*/
function sanitizeRequestData(value, isGet) {
if (value === undefined) return undefined;
if (isGet && (value === null || value === 'undefined' || value === 'null')) return undefined;
if (typeof value === 'number' && !Number.isFinite(value)) return undefined;
if (Array.isArray(value)) {
return value
.map((item) => sanitizeRequestData(item, isGet))
.filter((item) => item !== undefined);
}
if (value && typeof value === 'object') {
const result = {};
Object.keys(value).forEach((key) => {
const cleaned = sanitizeRequestData(value[key], isGet);
if (cleaned !== undefined) result[key] = cleaned;
});
return result;
}
return value;
}
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}`;
const upperMethod = method.toUpperCase();
const requestData = sanitizeRequestData(data, upperMethod === 'GET');
// 正常请求:同时带上 access token 和 refresh token
// 刷新重试请求:只使用 refresh token
const header = withAuthHeader(needAuth, {
'content-type': contentType
}, __refreshRetry);
return new Promise((resolve, reject) => {
wx.request({
url,
method: upperMethod,
data: requestData,
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 errors = res && res.data && res.data.errors ? res.data.errors : null;
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(httpError(errors, statusCode));
return;
}
}
// ── 401 未授权(重试后仍然失败) ──
if (statusCode === 401) {
clearAllTokensAndRedirect();
reject(httpError(errors, statusCode));
return;
}
// ── 403 禁止访问 ──
if ( statusCode == 400 || statusCode == 403 ) {
wx.showToast({ title: errors || '请求失败', icon: 'none' });
reject(httpError(errors, statusCode));
return;
}
// ── 404 资源不存在 ───────────────────────────────────────────
if (statusCode == 404) {
wx.showToast({ title: errors || '请求资源不存在', icon: 'none' });
reject(httpError(errors || '请求资源不存在', statusCode));
return;
}
// ── 5xx 服务端错误 ───────────────────────────────────────────
if (statusCode >= 500) {
wx.showToast({ title: errors || '服务器异常,请稍后重试', icon: 'none' });
reject(httpError(errors || '服务器异常,请稍后重试', statusCode));
return;
}
// ── 其他非 2xx ───────────────────────────────────────────────
if (statusCode !== 0 && (statusCode < 200 || statusCode >= 300)) {
wx.showToast({ title: errors || `请求失败(${statusCode})`, icon: 'none' });
reject(httpError(errors, 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
};