+19
-4
@@ -32,7 +32,7 @@ function buildUrl(path: string, query?: RequestOptions['query']): string {
|
||||
return s ? `${BASE}${path}?${s}` : BASE + path
|
||||
}
|
||||
|
||||
async function parseError(resp: Response): Promise<never> {
|
||||
async function parseError(resp: Response, sentToken: string): Promise<never> {
|
||||
let message = `请求失败(${resp.status})`
|
||||
let ociCode: string | undefined
|
||||
try {
|
||||
@@ -54,7 +54,7 @@ async function parseError(resp: Response): Promise<never> {
|
||||
} catch {
|
||||
/* 非 JSON 响应体,保留默认消息 */
|
||||
}
|
||||
if (resp.status === 401) useAuthStore().logout()
|
||||
if (resp.status === 401) handleUnauthorized(sentToken)
|
||||
// 全局限速(429)整页拦截;登录接口的 429 是账号锁定,留在表单内提示
|
||||
if (resp.status === 429 && !resp.url.includes('/auth/login') && location.pathname !== '/blocked') {
|
||||
location.assign('/blocked')
|
||||
@@ -62,17 +62,31 @@ async function parseError(resp: Response): Promise<never> {
|
||||
throw new ApiError(resp.status, message, ociCode)
|
||||
}
|
||||
|
||||
/** 401 处理:仅当失败请求发送时的 token 仍是当前会话 token 才登出——换发新
|
||||
* token 后(OAuth 绑定回跳等),旧 token 请求的迟到 401 不得清掉新会话;
|
||||
* 登出后带 redirect 统一跳登录页,不把用户留在满屏报错的页面上。 */
|
||||
function handleUnauthorized(sentToken: string) {
|
||||
const auth = useAuthStore()
|
||||
if (sentToken !== auth.token) return
|
||||
auth.logout()
|
||||
if (location.pathname !== '/login') {
|
||||
const redirect = encodeURIComponent(location.pathname + location.search)
|
||||
location.assign(`/login?redirect=${redirect}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 统一请求封装:注入 JWT、401 登出、错误转 ApiError */
|
||||
export async function request<T>(path: string, opts: RequestOptions = {}): Promise<T> {
|
||||
const auth = useAuthStore()
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json', ...opts.headers }
|
||||
if (auth.token) headers.Authorization = `Bearer ${auth.token}`
|
||||
const sentToken = auth.token
|
||||
const resp = await fetch(buildUrl(path, opts.query), {
|
||||
method: opts.method ?? 'GET',
|
||||
headers,
|
||||
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
||||
})
|
||||
if (!resp.ok) await parseError(resp)
|
||||
if (!resp.ok) await parseError(resp, sentToken)
|
||||
// 202/204 等成功响应可能无 body,直接 resp.json() 会抛 Unexpected end of JSON input
|
||||
const text = await resp.text()
|
||||
return (text ? JSON.parse(text) : undefined) as T
|
||||
@@ -91,12 +105,13 @@ export async function rawFetch(
|
||||
const auth = useAuthStore()
|
||||
const headers: Record<string, string> = { ...opts.headers }
|
||||
if (auth.token) headers.Authorization = `Bearer ${auth.token}`
|
||||
const sentToken = auth.token
|
||||
const resp = await fetch(buildUrl(path, opts.query), {
|
||||
method: opts.method ?? 'GET',
|
||||
headers,
|
||||
body: opts.body,
|
||||
})
|
||||
if (!resp.ok) await parseError(resp)
|
||||
if (!resp.ok) await parseError(resp, sentToken)
|
||||
return resp
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user