提交1 bmad搭建与项目启动 - ok

This commit is contained in:
2025-10-17 17:24:56 +08:00
commit ec58562661
686 changed files with 149750 additions and 0 deletions

285
crop-x/apis/interceptor.js Normal file
View File

@@ -0,0 +1,285 @@
/**
* API 请求拦截器
* 功能:
* 1. 为除登录接口外的所有请求添加身份信息
* 2. 统一错误处理
* 3. 请求/响应日志记录
* 4. 自动刷新token
*/
import axios from 'axios';
import { toast } from 'sonner';
/**
* 创建API实例并配置拦截器
*/
export const createAPI = ({ baseURL, timeout = 10000, enableLogging = false }) => {
// 创建axios实例
const api = axios.create({
baseURL,
timeout,
headers: {
'Content-Type': 'application/json',
},
});
// 获取存储的token和用户信息
const getAuthData = () => {
try {
const authData = localStorage.getItem('authData');
if (authData) {
const { token, user } = JSON.parse(authData);
return { token, user_id: user?.id };
}
} catch (error) {
console.warn('Failed to parse auth data from localStorage:', error);
}
return { token: null, user_id: null };
};
// 请求拦截器
api.interceptors.request.use(
(config) => {
// 添加请求日志
if (enableLogging) {
console.log(`🚀 API Request: ${config.method?.toUpperCase()} ${config.url}`, {
data: config.data,
params: config.params,
});
}
// 为除登录接口外的所有请求添加身份信息
const isLoginRequest = config.url?.includes('/auth/login');
const isRegisterRequest = config.url?.includes('/auth/register');
if (!isLoginRequest && !isRegisterRequest) {
const { token, user_id } = getAuthData();
if (token) {
config.headers['auth'] = token; // JWT token
}
if (user_id) {
config.headers['user_id'] = user_id; // 用户ID
}
}
// 添加请求时间戳
config.metadata = { startTime: new Date() };
return config;
},
(error) => {
console.error('❌ Request Error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
api.interceptors.response.use(
(response) => {
// 添加响应日志
if (enableLogging) {
const duration = new Date() - response.config.metadata.startTime;
console.log(`✅ API Response: ${response.config.method?.toUpperCase()} ${response.config.url}`, {
status: response.status,
duration: `${duration}ms`,
data: response.data,
});
}
// 统一处理成功响应格式
if (response.data && typeof response.data === 'object') {
// 如果后端返回统一格式 { code, message, data }
if ('code' in response.data) {
const { code, message, data } = response.data;
if (code === 200 || code === 0) {
return { ...response, data }; // 返回实际数据
} else {
// 业务错误处理
toast.error(message || '请求失败');
return Promise.reject(new Error(message || '请求失败'));
}
}
}
return response;
},
async (error) => {
const originalRequest = error.config;
// 添加错误日志
if (enableLogging) {
const duration = originalRequest?.metadata?.startTime
? `${new Date() - originalRequest.metadata.startTime}ms`
: 'N/A';
console.error(`❌ API Error: ${originalRequest?.method?.toUpperCase()} ${originalRequest?.url}`, {
status: error.response?.status,
duration,
message: error.message,
data: error.response?.data,
});
}
// 处理不同类型的错误
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
// 未授权 - token过期或无效
return handleUnauthorizedError(originalRequest);
case 403:
// 禁止访问 - 权限不足
toast.error('您没有权限访问此资源');
break;
case 404:
// 资源不存在
toast.error('请求的资源不存在');
break;
case 422:
// 表单验证错误
if (data?.errors) {
Object.values(data.errors).flat().forEach(errorMessage => {
toast.error(errorMessage);
});
} else {
toast.error(data?.message || '请求参数错误');
}
break;
case 429:
// 请求过于频繁
toast.error('请求过于频繁,请稍后再试');
break;
case 500:
// 服务器内部错误
toast.error('服务器内部错误,请稍后再试');
break;
default:
// 其他错误
toast.error(data?.message || `请求失败 (${status})`);
}
} else if (error.request) {
// 网络错误
if (error.code === 'ECONNABORTED') {
toast.error('请求超时,请检查网络连接');
} else {
toast.error('网络连接失败,请检查网络');
}
} else {
// 其他错误
toast.error('请求配置错误');
}
return Promise.reject(error);
}
);
return api;
};
/**
* 处理401未授权错误
* 尝试刷新token或跳转到登录页
*/
const handleUnauthorizedError = async (originalRequest) => {
// 避免重复刷新token
if (originalRequest._retry) {
// 刷新失败,清除本地存储并跳转到登录页
localStorage.removeItem('authData');
window.location.href = '/login';
return Promise.reject(new Error('登录已过期,请重新登录'));
}
originalRequest._retry = true;
try {
// 尝试刷新token
const authData = JSON.parse(localStorage.getItem('authData') || '{}');
const refreshToken = authData.refreshToken;
if (refreshToken) {
const response = await axios.post(`${originalRequest.baseURL}/auth/refresh-token`, {
refreshToken
});
const { token, user } = response.data.data;
// 更新本地存储
localStorage.setItem('authData', JSON.stringify({
token,
refreshToken,
user
}));
// 重新发送原始请求
originalRequest.headers['auth'] = token;
originalRequest.headers['user_id'] = user.id;
return axios(originalRequest);
} else {
// 没有刷新token跳转到登录页
localStorage.removeItem('authData');
window.location.href = '/login';
return Promise.reject(new Error('登录已过期,请重新登录'));
}
} catch (refreshError) {
// 刷新token失败
localStorage.removeItem('authData');
window.location.href = '/login';
return Promise.reject(new Error('登录已过期,请重新登录'));
}
};
/**
* 设置认证信息
* @param {string} token JWT token
* @param {object} user 用户信息
* @param {string} refreshToken 刷新token
*/
export const setAuthData = (token, user, refreshToken) => {
const authData = {
token,
refreshToken: refreshToken || '',
user
};
localStorage.setItem('authData', JSON.stringify(authData));
};
/**
* 清除认证信息
*/
export const clearAuthData = () => {
localStorage.removeItem('authData');
};
/**
* 获取当前认证信息
*/
export const getAuthData = () => {
try {
const authData = localStorage.getItem('authData');
return authData ? JSON.parse(authData) : null;
} catch (error) {
console.warn('Failed to get auth data:', error);
return null;
}
};
/**
* 检查是否已登录
*/
export const isAuthenticated = () => {
const authData = getAuthData();
return !!(authData?.token && authData?.user);
};
export default createAPI;