Commit 66c94542 by jianan

fix(types): 适配 axios 解包与 oidc-client-ts v3 Storage 接口

parent 3dfc8960
...@@ -22,38 +22,32 @@ instance.interceptors.request.use(async (config) => { ...@@ -22,38 +22,32 @@ instance.interceptors.request.use(async (config) => {
// 防止多个并发请求同时 401 触发多次跳转 // 防止多个并发请求同时 401 触发多次跳转
let redirecting = false let redirecting = false
function redirectToLogin(): void {
if (redirecting) return
// 已在回调页则不再发起,避免循环
if (window.location.pathname === '/auth/callback') return
redirecting = true
void signinRedirect(window.location.pathname + window.location.search)
}
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => { (response) => response,
const body = response.data as ApiResult<unknown>
if (body && body.code !== 200) {
throw new Error(body.msg || '请求失败')
}
// 拦截器直接解包 data,业务侧只拿到 T
return body.data
},
(error) => { (error) => {
if (error.response?.status === 401) { if (error.response?.status === 401) {
// token 缺失/失效:重新走授权,IdP 有 SSO 会话时无感跳回 if (!redirecting && window.location.pathname !== '/auth/callback') {
redirectToLogin() redirecting = true
void signinRedirect(window.location.pathname + window.location.search)
}
throw new Error('未登录或登录已过期') throw new Error('未登录或登录已过期')
} }
throw new Error(error.message || '网络请求失败') throw new Error(error.message || '网络请求失败')
} }
) )
// 拦截器已在运行时把响应解包为 data,这里用类型断言表达该事实 // 解包 { code, msg, data }:非 200 抛错,成功只返回 data
export function get<T>(path: string): Promise<T> { function unwrap<T>(body: ApiResult<T>): T {
return instance.get(path) as unknown as Promise<T> if (body.code !== 200) throw new Error(body.msg || '请求失败')
return body.data
}
export async function get<T>(path: string): Promise<T> {
const res = await instance.get<ApiResult<T>>(path)
return unwrap(res.data)
} }
export function post<T>(path: string, body?: unknown): Promise<T> { export async function post<T>(path: string, body?: unknown): Promise<T> {
return instance.post(path, body) as unknown as Promise<T> const res = await instance.post<ApiResult<T>>(path, body)
return unwrap(res.data)
} }
import { UserManager, WebStorageStateStore, type IStore } from 'oidc-client-ts' import { UserManager, WebStorageStateStore } from 'oidc-client-ts'
/** /**
* 内存存储:登录后的 User(含 access_token)只存于此,不写入 localStorage/sessionStorage。 * 内存存储:登录后的 User(含 access_token)只存于此,不写入 localStorage/sessionStorage。
* 刷新页面或关闭标签即丢失,符合 D8「access_token 仅存内存」。 * 刷新页面或关闭标签即丢失,符合 D8「access_token 仅存内存」。
* 实现标准 Web Storage 接口(含 length/key/clear),以满足 oidc-client-ts v3 的存储类型。
*/ */
class MemoryStore implements IStore { class MemoryStore implements Storage {
private readonly map = new Map<string, string>() private readonly map = new Map<string, string>()
get length(): number {
return this.map.size
}
getItem(key: string): string | null { getItem(key: string): string | null {
return this.map.has(key) ? this.map.get(key)! : null return this.map.has(key) ? this.map.get(key)! : null
} }
...@@ -15,6 +19,12 @@ class MemoryStore implements IStore { ...@@ -15,6 +19,12 @@ class MemoryStore implements IStore {
removeItem(key: string): void { removeItem(key: string): void {
this.map.delete(key) this.map.delete(key)
} }
clear(): void {
this.map.clear()
}
key(index: number): string | null {
return Array.from(this.map.keys())[index] ?? null
}
} }
const authority = import.meta.env.VITE_OIDC_AUTHORITY ?? 'https://auth.anjibao.cn' const authority = import.meta.env.VITE_OIDC_AUTHORITY ?? 'https://auth.anjibao.cn'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment