126 lines
3.5 KiB
JavaScript
126 lines
3.5 KiB
JavaScript
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'
|
|
]
|
|
|
|
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<string, string> }} options
|
|
* @returns {Promise<any>} 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 })
|
|
}
|