Files
Neo-ZQYY/apps/miniprogram - 副本/miniprogram/utils/request.ts

205 lines
5.1 KiB
TypeScript
Raw 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.
/**
* 统一请求封装
*
* - 自动附加 Authorization: Bearer <token>
* - 401 时自动尝试 refresh_token 刷新,刷新成功后重试原请求
* - 刷新失败时清除 token 并跳转 login 页面
* - BASE_URL 从 config.ts 读取(按运行环境自动切换)
*/
import { API_BASE } from "./config"
export interface RequestOptions {
url: string
method?: "GET" | "POST" | "PUT" | "DELETE"
data?: any
header?: Record<string, string>
/** 是否需要认证,默认 true */
needAuth?: boolean
}
/** 是否正在刷新 token防止并发刷新 */
let isRefreshing = false
/** 刷新期间排队的请求(等刷新完成后统一重试) */
let pendingQueue: Array<{
resolve: (value: any) => void
reject: (reason: any) => void
options: RequestOptions
}> = []
/**
* 从 globalData 或 Storage 获取 access_token
*/
function getAccessToken(): string | undefined {
const app = getApp<IAppOption>()
return app.globalData.token || wx.getStorageSync("token") || undefined
}
/**
* 从 globalData 或 Storage 获取 refresh_token
*/
function getRefreshToken(): string | undefined {
const app = getApp<IAppOption>()
return app.globalData.refreshToken || wx.getStorageSync("refreshToken") || undefined
}
/**
* 清除本地存储的所有 token
*/
function clearTokens(): void {
const app = getApp<IAppOption>()
app.globalData.token = undefined
app.globalData.refreshToken = undefined
wx.removeStorageSync("token")
wx.removeStorageSync("refreshToken")
}
/**
* 保存 token 到 globalData 和 Storage
*/
function saveTokens(accessToken: string, refreshToken: string): void {
const app = getApp<IAppOption>()
app.globalData.token = accessToken
app.globalData.refreshToken = refreshToken
wx.setStorageSync("token", accessToken)
wx.setStorageSync("refreshToken", refreshToken)
}
/**
* 跳转到登录页(使用 reLaunch 清空页面栈)
*/
function redirectToLogin(): void {
wx.reLaunch({ url: "/pages/login/login" })
}
/**
* 执行底层 wx.request返回 Promise
*/
function wxRequest(options: RequestOptions): Promise<any> {
return new Promise((resolve, reject) => {
wx.request({
url: `${API_BASE}${options.url}`,
method: options.method || "GET",
data: options.data,
header: options.header || {},
success(res) {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject({ statusCode: res.statusCode, data: res.data })
}
},
fail(err) {
reject({ statusCode: 0, data: err })
},
})
})
}
/**
* 尝试用 refresh_token 刷新令牌
*
* 成功 → 保存新 token 并返回 true
* 失败 → 清除 token、跳转登录页、返回 false
*/
async function tryRefreshToken(): Promise<boolean> {
const rt = getRefreshToken()
if (!rt) {
clearTokens()
redirectToLogin()
return false
}
try {
const data = await wxRequest({
url: "/api/xcx/refresh",
method: "POST",
data: { refresh_token: rt },
header: { "Content-Type": "application/json" },
needAuth: false,
})
if (data.access_token && data.refresh_token) {
saveTokens(data.access_token, data.refresh_token)
return true
}
// 响应格式异常,视为刷新失败
clearTokens()
redirectToLogin()
return false
} catch {
clearTokens()
redirectToLogin()
return false
}
}
/**
* 处理排队中的请求:刷新成功后全部重试,失败则全部拒绝
*/
function flushPendingQueue(success: boolean): void {
const queue = [...pendingQueue]
pendingQueue = []
for (const item of queue) {
if (success) {
// 重试时会自动附加新 token
request(item.options).then(item.resolve, item.reject)
} else {
item.reject({ statusCode: 401, data: { detail: "刷新令牌失败" } })
}
}
}
/**
* 统一请求入口
*
* @param options 请求配置
* @returns 响应数据(已解析的 JSON
*/
export function request(options: RequestOptions): Promise<any> {
const needAuth = options.needAuth !== false
const headers: Record<string, string> = {
"Content-Type": "application/json",
...options.header,
}
// 自动附加 Authorization header
if (needAuth) {
const token = getAccessToken()
if (token) {
headers["Authorization"] = `Bearer ${token}`
}
}
const finalOptions: RequestOptions = { ...options, header: headers }
return wxRequest(finalOptions).catch(async (err) => {
// 非 401 或不需要认证的请求,直接抛出
if (err.statusCode !== 401 || !needAuth) {
throw err
}
// 401 → 尝试刷新 token
if (isRefreshing) {
// 已有刷新请求在进行中,排队等待
return new Promise((resolve, reject) => {
pendingQueue.push({ resolve, reject, options })
})
}
isRefreshing = true
try {
const ok = await tryRefreshToken()
flushPendingQueue(ok)
if (!ok) {
throw { statusCode: 401, data: { detail: "刷新令牌失败" } }
}
// 刷新成功,用新 token 重试原请求
return request(options)
} finally {
isRefreshing = false
}
})
}
export default request