chore: 初始化仓库基线(AGENTS.md、git 规范、敏感文件排除)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
||||
import { message } from 'antd';
|
||||
|
||||
export const TOKEN_KEY = 'silkworm_access_token';
|
||||
export const REFRESH_TOKEN_KEY = 'silkworm_refresh_token';
|
||||
|
||||
export interface RequestConfig extends AxiosRequestConfig {
|
||||
silent?: boolean;
|
||||
/** 内部标记:本次请求是 refresh 重试,避免无限循环 */
|
||||
_retried?: boolean;
|
||||
}
|
||||
|
||||
export const http: AxiosInstance = axios.create({
|
||||
baseURL: '/api/v1',
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
http.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token) {
|
||||
config.headers = config.headers ?? {};
|
||||
(config.headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
let refreshing: Promise<string | null> | null = null;
|
||||
|
||||
const refreshToken = async (): Promise<string | null> => {
|
||||
const refresh = localStorage.getItem(REFRESH_TOKEN_KEY);
|
||||
if (!refresh) return null;
|
||||
try {
|
||||
const res = await axios.post('/api/v1/auth/refresh', { refreshToken: refresh });
|
||||
const { accessToken, refreshToken: newRefresh } = res.data || {};
|
||||
if (!accessToken) return null;
|
||||
localStorage.setItem(TOKEN_KEY, accessToken);
|
||||
if (newRefresh) localStorage.setItem(REFRESH_TOKEN_KEY, newRefresh);
|
||||
return accessToken;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const clearAuthAndRedirect = () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
if (location.pathname !== '/login') {
|
||||
message.warning('登录已过期,请重新登录');
|
||||
location.replace('/login');
|
||||
}
|
||||
};
|
||||
|
||||
http.interceptors.response.use(
|
||||
(res) => res,
|
||||
async (err: AxiosError) => {
|
||||
const status = err.response?.status;
|
||||
const cfg = err.config as RequestConfig | undefined;
|
||||
|
||||
// 访问令牌过期:尝试用 refresh 令牌换新后重试一次
|
||||
if (status === 401 && cfg && !cfg._retried && !cfg.url?.includes('/auth/')) {
|
||||
cfg._retried = true;
|
||||
refreshing = refreshing ?? refreshToken();
|
||||
const newToken = await refreshing;
|
||||
refreshing = null;
|
||||
if (newToken) {
|
||||
cfg.headers = cfg.headers ?? ({} as InternalAxiosRequestConfig['headers']);
|
||||
(cfg.headers as Record<string, string>)['Authorization'] = `Bearer ${newToken}`;
|
||||
return http.request(cfg);
|
||||
}
|
||||
clearAuthAndRedirect();
|
||||
} else if (status === 401) {
|
||||
clearAuthAndRedirect();
|
||||
} else if (!cfg?.silent) {
|
||||
const msg =
|
||||
(err.response?.data as any)?.error ||
|
||||
(err.response?.data as any)?.message ||
|
||||
(err.response?.data as any)?.msg ||
|
||||
err.message ||
|
||||
'请求失败';
|
||||
message.error(typeof msg === 'string' ? msg : '请求失败');
|
||||
}
|
||||
return Promise.reject(err);
|
||||
},
|
||||
);
|
||||
|
||||
export const request = <T = unknown>(config: RequestConfig): Promise<T> =>
|
||||
http.request<T>(config).then((r) => r.data);
|
||||
|
||||
export const get = <T = unknown>(url: string, config?: RequestConfig) =>
|
||||
request<T>({ ...config, method: 'GET', url });
|
||||
|
||||
export const post = <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
|
||||
request<T>({ ...config, method: 'POST', url, data });
|
||||
|
||||
export const put = <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
|
||||
request<T>({ ...config, method: 'PUT', url, data });
|
||||
|
||||
export const patch = <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
|
||||
request<T>({ ...config, method: 'PATCH', url, data });
|
||||
|
||||
export const del = <T = unknown>(url: string, config?: RequestConfig) =>
|
||||
request<T>({ ...config, method: 'DELETE', url });
|
||||
Reference in New Issue
Block a user