Files
pda-uniapp/api/http.js
T
2026-08-25 20:50:01 +08:00

150 lines
5.5 KiB
JavaScript

import { refresh as refreshRequest } from '@/api/auth.js'
import { isAccessTokenExpiring, session, signOut, updateTokenPair } from '@/stores/session.js'
import { requireOnline } from '@/utils/network.js'
let refreshPromise = null
let redirectingToLogin = false
function responseCode(response) {
return String(response?.data?.code ?? response?.statusCode ?? '')
}
function isUnauthorized(responseOrError) {
const statusCode = Number(responseOrError?.statusCode ?? responseOrError?.response?.statusCode ?? 0)
const code = String(responseOrError?.code ?? responseCode(responseOrError))
return statusCode === 401 || code === '401' || code === 'A0401'
}
function sessionExpiredError(source) {
const error = new Error('登录已过期,请重新登录')
error.code = source?.code || 'A0401'
error.statusCode = source?.statusCode || 401
return error
}
function redirectAfterSessionExpiry() {
if (redirectingToLogin) return
redirectingToLogin = true
if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none', duration: 2200 })
}
if (typeof uni !== 'undefined' && typeof uni.reLaunch === 'function') {
uni.reLaunch({
url: '/pages/login/login',
complete() { setTimeout(() => { redirectingToLogin = false }, 1000) }
})
}
}
function expireSession(source, observedRefreshToken = session.refreshToken) {
if (observedRefreshToken && session.refreshToken && observedRefreshToken !== session.refreshToken) return session.token
const hadSession = Boolean(session.currentUser || session.token || session.refreshToken)
signOut()
if (hadSession) redirectAfterSessionExpiry()
throw sessionExpiredError(source)
}
function networkError(error, fallback = '网络连接失败') {
const next = new Error(error?.errMsg || fallback)
next.network = true
return next
}
function rawRequest(options, accessToken) {
const baseUrl = session.apiBase || '/api/platform/v1'
const headers = { ...(options.header || {}) }
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
if (session.requestId) headers['X-Request-Id'] = session.requestId
return new Promise((resolve, reject) => {
uni.request({
...options,
url: `${baseUrl}${options.url}`,
header: headers,
success: resolve,
fail: (error) => reject(networkError(error))
})
})
}
async function refreshAccessToken() {
const observedRefreshToken = session.refreshToken
if (!observedRefreshToken) return expireSession(null, observedRefreshToken)
if (!refreshPromise) {
refreshPromise = (async () => {
if (session.refreshToken !== observedRefreshToken && session.token) return session.token
try {
const tokens = await refreshRequest(observedRefreshToken)
if (session.refreshToken !== observedRefreshToken) {
if (session.token) return session.token
throw sessionExpiredError()
}
updateTokenPair(tokens)
redirectingToLogin = false
return session.token
} catch (error) {
if (isUnauthorized(error)) return expireSession(error, observedRefreshToken)
throw error
}
})().finally(() => { refreshPromise = null })
}
return refreshPromise
}
async function validAccessToken() {
if (!session.token || isAccessTokenExpiring(session.token)) return refreshAccessToken()
return session.token
}
export function ensureAccessToken() {
return validAccessToken()
}
async function runAuthenticated(execute) {
requireOnline()
let accessToken = await validAccessToken()
let response = await execute(accessToken)
if (!isUnauthorized(response)) return response
accessToken = session.token && session.token !== accessToken ? session.token : await refreshAccessToken()
const activeRefreshToken = session.refreshToken
response = await execute(accessToken)
if (isUnauthorized(response)) expireSession(response, activeRefreshToken)
return response
}
export function apiRequest(options) {
return runAuthenticated((accessToken) => rawRequest(options, accessToken)).then((response) => {
const body = response.data || {}
const code = responseCode(response)
if (code !== '200') {
const error = new Error(body.message || '服务端请求失败')
error.code = body.code || response.statusCode
error.statusCode = response.statusCode
error.requestId = body.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id']
error.traceId = body.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id']
throw error
}
return body.data
})
}
export function apiDownload(options) {
return runAuthenticated((accessToken) => new Promise((resolve, reject) => {
const headers = { ...(options.header || {}) }
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
if (session.requestId) headers['X-Request-Id'] = session.requestId
uni.downloadFile({ ...options, header: headers, success: resolve, fail: (error) => reject(networkError(error, '文件下载失败')) })
}))
}
export function apiUpload(options, onProgress) {
return runAuthenticated((accessToken) => new Promise((resolve, reject) => {
const headers = { ...(options.header || {}) }
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
if (session.requestId) headers['X-Request-Id'] = session.requestId
const task = uni.uploadFile({ ...options, header: headers, success: resolve, fail: (error) => reject(networkError(error)) })
if (task && typeof task.onProgressUpdate === 'function' && typeof onProgress === 'function') task.onProgressUpdate(({ progress }) => onProgress(progress))
}))
}
export { isUnauthorized }