import { API_BASE_URL, REQUEST_TIMEOUT_MS } from './config.js' import { forceRelogin } from './auth-session.js' /** 这些接口的业务 code=401 表示凭据错误等,不是会话过期 */ const AUTH_EXEMPT_PATHS = [ '/web/auth/login', '/web/auth/login-rsa-public-key', '/web/auth/refresh-token', '/mini/auth/wechat-login', '/mini/auth/web-qr-scanned', '/mini/auth/web-qr-confirm' ] function isAuthExemptPath(path) { const p = String(path || '') for (let i = 0; i < AUTH_EXEMPT_PATHS.length; i++) { if (p.indexOf(AUTH_EXEMPT_PATHS[i]) !== -1) return true } return false } function hasBearerToken(header) { const auth = (header && header.Authorization) || '' return /^Bearer\s+\S+/i.test(String(auth)) } /** * HTTP 401,或已带 token 的业务 code=401 → 强制重新登录。 * 登录接口豁免,避免「账号密码错误」被当成会话失效。 */ function maybeForceRelogin(options, httpStatus, body) { if (isAuthExemptPath(options && options.url)) return const bizCode = body && body.code const msg = (body && typeof body.message === 'string' && body.message.trim()) || (body && typeof body.detail === 'string' && body.detail.trim()) || '' if (httpStatus === 401 || (bizCode === 401 && hasBearerToken(options && options.header))) { forceRelogin(msg || '登录已失效,请重新登录') } } /** * 解析后端统一响应。成功返回 data;失败抛 Error(message 为业务文案)。 * @param {{ code?: number, message?: string, data?: any }} body * @param {number} [httpStatus] */ export function unwrapResponse(body, httpStatus) { if (body == null || typeof body !== 'object') { const err = new Error('网络异常,请稍后重试') err.code = 'BAD_RESPONSE' throw err } const code = body.code if (code === 200) { return body.data } const msg = (typeof body.message === 'string' && body.message.trim()) || '请求失败' const err = new Error(msg) err.code = code err.httpStatus = httpStatus throw err } function joinUrl(path) { const base = (API_BASE_URL || '').replace(/\/$/, '') const p = path.startsWith('/') ? path : `/${path}` return `${base}${p}` } /** * @param {{ url: string, method?: string, data?: any, header?: Record }} options * @returns {Promise} data 字段 */ export function request(options) { const method = (options.method || 'GET').toUpperCase() const url = joinUrl(options.url) const header = Object.assign( { 'Content-Type': 'application/json' }, options.header || {} ) const reqOpts = { url: options.url, header } return new Promise((resolve, reject) => { uni.request({ url, method, data: options.data, header, timeout: REQUEST_TIMEOUT_MS, success(res) { try { const body = res.data maybeForceRelogin(reqOpts, res.statusCode, body) if (res.statusCode >= 200 && res.statusCode < 300) { resolve(unwrapResponse(body, res.statusCode)) return } // HTTP 非 2xx:若 body 仍是业务包,优先展示 message try { resolve(unwrapResponse(body, res.statusCode)) } catch (bizErr) { reject(bizErr) } } catch (e) { reject(e) } }, fail() { const err = new Error('网络异常,请稍后重试') err.code = 'NETWORK' reject(err) } }) }) } export function get(url, header) { return request({ url, method: 'GET', header }) } export function post(url, data, header) { return request({ url, method: 'POST', data, header }) }