微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
204
apps/miniprogram - 副本/miniprogram/utils/request.ts
Normal file
204
apps/miniprogram - 副本/miniprogram/utils/request.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* 统一请求封装
|
||||
*
|
||||
* - 自动附加 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
|
||||
Reference in New Issue
Block a user