import axios, { AxiosError, AxiosHeaders, InternalAxiosRequestConfig } from 'axios';
import { toast } from 'react-hot-toast';
import { getAuthToken, removeAuthToken, BusinessCode } from './';
type ResponseBody<T = unknown> = { code: number; message: string } & (
| { data: T; success: true }
| { data: null; success: false }
);
// 防重复toast机制
const toastMessageCache = new Map<string, number>();
const TOAST_DEBOUNCE_TIME = 3000; // 3秒内不重复显示相同消息
const showToastWithDebounce = (message: string, type: 'success' | 'error' = 'error') => {
const now = Date.now();
const lastShownTime = toastMessageCache.get(message);
// 如果消息在3秒内已经显示过,则跳过
if (lastShownTime && now - lastShownTime < TOAST_DEBOUNCE_TIME) {
return;
}
// 更新缓存并显示toast
toastMessageCache.set(message, now);
// 清理过期的缓存条目(可选,避免内存泄漏)
for (const [cachedMessage, timestamp] of toastMessageCache.entries()) {
if (now - timestamp > TOAST_DEBOUNCE_TIME) {
toastMessageCache.delete(cachedMessage);
}
}
if (type === 'error') {
toast.error(message);
} else {
toast.success(message);
}
};
export const getRequestClient = (config: { baseUrl: string }) => {
const client = axios.create({
baseURL: config.baseUrl,
headers: { 'Content-Type': 'application/json' },
});
client.interceptors.request.use(authRequestInterceptor);
client.interceptors.request.use(sentryRequestInterceptor);
client.interceptors.response.use(
(response) => {
if (response?.data?.code === BusinessCode.UNAUTHORIZED) {
showToastWithDebounce(response?.data?.message || '请登录', 'error');
removeAuthToken();
window.location.href = '/login';
return response;
}
return response;
},
(error: AxiosError<ResponseBody>) => {
showToastWithDebounce(error.response?.data?.message || '请求失败', 'error');
return Promise.reject(error);
},
);
return client;
};
const authRequestInterceptor = async (c: InternalAxiosRequestConfig) => {
const token = getAuthToken();
if (token) {
c.headers = c.headers || new AxiosHeaders();
c.headers.set(`Authentication`, `Bearer ${token.token}`);
}
return c;
};
const sentryRequestInterceptor = async (c: InternalAxiosRequestConfig) => {
return c;
};
思路
使用一个 map 接受请求返回的 message,如果在 map 中不存在,就直接塞进 map 里,value 就是时间戳。如果存在,就拿出来对比一下现在的时间,是否在 3 秒以内,如果在 3 秒以内,说明已经提示过了,就直接丢弃这次 toast。最后清理一下 3 秒以外的过期缓存。