104 lines
2.3 KiB
TypeScript
104 lines
2.3 KiB
TypeScript
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||
import envConfig from '../../env';
|
||
import Cookies from 'js-cookie';
|
||
import utils from '../lib/utils';
|
||
import i18n from '../i18n';
|
||
|
||
const API_BASE_URL = envConfig.baseURL;
|
||
const message = utils.message; // 使用全局 message
|
||
|
||
|
||
const api = axios.create({
|
||
baseURL: API_BASE_URL,
|
||
timeout: 100000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
withCredentials: true,
|
||
});
|
||
|
||
// 请求拦截器
|
||
api.interceptors.request.use(
|
||
(config: InternalAxiosRequestConfig) => {
|
||
const token = localStorage.getItem('token');
|
||
if (token && config.headers) {
|
||
config.headers['Authorization'] = `Bearer ${token}`;
|
||
}
|
||
|
||
if (!(config.data instanceof FormData)) {
|
||
config.headers['Content-Type'] = 'application/json';
|
||
} else {
|
||
// FormData 删除json头,交给axios自动生成multipart
|
||
delete config.headers['Content-Type'];
|
||
}
|
||
return config;
|
||
},
|
||
(error: AxiosError) => {
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 🔥 全局唯一锁:保证并发多个 401 也只执行一次
|
||
let isLoggingOut = false;
|
||
|
||
const handleLogoutAndRedirect = () => {
|
||
// 已经在登出流程中,直接阻止
|
||
if (isLoggingOut) return;
|
||
|
||
// 立即上锁
|
||
isLoggingOut = true;
|
||
|
||
// 清除登录信息
|
||
try {
|
||
localStorage.clear();
|
||
Cookies.remove('userId');
|
||
Cookies.remove('Admin-Token');
|
||
Cookies.remove('token');
|
||
} catch { }
|
||
|
||
// redux 登出
|
||
import('../store').then(storeModule => {
|
||
import('../store/userSlice').then(({ logout }) => {
|
||
storeModule.default.dispatch(logout());
|
||
});
|
||
});
|
||
|
||
// 只提示一次
|
||
message?.error(i18n.t('api.loginExpired'));
|
||
|
||
// 只跳转一次
|
||
setTimeout(() => {
|
||
window.location.href = '/login';
|
||
}, 800);
|
||
};
|
||
|
||
// 响应拦截器
|
||
api.interceptors.response.use(
|
||
(response: AxiosResponse) => {
|
||
const data = response.data;
|
||
// 业务 401 → 统一走登出
|
||
if (data?.code === 401) {
|
||
handleLogoutAndRedirect();
|
||
return Promise.reject(new Error(i18n.t('api.loginExpired')));
|
||
}
|
||
return data;
|
||
},
|
||
(error: AxiosError) => {
|
||
// HTTP 401 → 统一走登出
|
||
if (error.response?.status == 401) {
|
||
handleLogoutAndRedirect();
|
||
return Promise.reject(error);
|
||
}
|
||
|
||
// 网络异常
|
||
if (!error.response) {
|
||
message?.error(i18n.t('api.networkError'));
|
||
return Promise.reject(error);
|
||
}
|
||
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
export default api;
|