Files
leaudit-platform-frontend/app/api/axios-client.ts
T

402 lines
13 KiB
TypeScript

import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
import { mockData, type MockApiResponse } from './mock';
import { API_BASE_URL, DOCUMENT_URL } from '../config/api-config';
/**
* API响应类型
*/
export type ApiResponse<T> = {
data?: T;
error?: string;
status: number;
headers?: Record<string, string>;
};
export type QueryParams = Record<string, string | number | boolean | undefined>;
// 获取 API 基础 URL (从配置文件导入)
// const API_BASE_URL = 'http://172.16.0.58:8008';
// const API_BASE_URL = 'http://nas.7bm.co:3000';
// const API_BASE_URL = 'http://172.18.0.100:3000';
// const API_BASE_URL = 'http://172.16.0.119:9000/admin';
// 调试:打印当前API_BASE_URL的值
console.log('🔍 axios-client.ts - API_BASE_URL:', API_BASE_URL);
// 文档URL前缀 (从配置文件导入)
// export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/';
export { DOCUMENT_URL };
// 是否使用模拟数据(开发环境使用)
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
// 设置超时时间(毫秒)
const DEFAULT_TIMEOUT = 30000; // 增加到30秒
// 创建 axios 实例
const axiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: DEFAULT_TIMEOUT, // 增加超时时间
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// 最大重试次数
const MAX_RETRIES = 2;
/**
* 带重试功能的请求方法
* @param config Axios请求配置
* @param retries 当前重试次数
* @returns Axios响应
*/
async function axiosRetry(config: AxiosRequestConfig, retries = 0): Promise<AxiosResponse> {
try {
return await axiosInstance(config);
} catch (error) {
if (isAxiosError(error) && error.code === 'ECONNABORTED' && retries < MAX_RETRIES) {
console.log(`请求超时,第${retries + 1}次重试...`);
// 递增重试次数并重新发送请求
return axiosRetry(config, retries + 1);
}
throw error;
}
}
/**
* 将编码后的URL解码为可读格式
* 当前已注释掉日志,暂未使用此函数
* @param url 编码后的URL
* @returns 解码后的可读URL
* @unused 保留供将来使用
*/
// function decodeUrlForDisplay(url: string): string {
// try {
// // 首先解码整个URL
// const decodedUrl = decodeURIComponent(url);
//
// // 如果URL中包含@符号作为前缀,则移除它
// if (decodedUrl.startsWith('@')) {
// return decodedUrl.substring(1);
// }
//
// return decodedUrl;
// } catch (error) {
// // 如果解码失败,返回原始URL
// console.error('URL解码失败:', error);
// return url;
// }
// }
/**
* 构建完整的 API URL
*/
function buildUrl(endpoint: string, params?: QueryParams): string {
let fullUrl;
// 检查endpoint是否已经是完整URL
if (endpoint.startsWith('http')) {
fullUrl = endpoint;
} else {
// 处理相对路径的情况
if (API_BASE_URL === '/api') {
// 如果是相对路径,直接使用endpoint
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
fullUrl = path;
} else {
// 确保API_BASE_URL格式正确
const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
fullUrl = `${baseUrl}${path}`;
}
}
try {
// 创建URL对象
const url = new URL(fullUrl);
// 添加查询参数
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
});
}
return url.toString();
} catch (error) {
console.error('URL构建错误:', fullUrl, error);
throw new Error(`无法构建URL: ${fullUrl}, 错误: ${error}`);
}
}
/**
* 获取模拟响应数据
*/
function getMockResponse<T>(endpoint: string): ApiResponse<T> {
// console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
// 移除开头的斜杠以便于匹配
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 查找匹配的模拟数据
for (const mockPath in mockData) {
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/')) {
// 如果是详情查询 (如 /evaluation_points/1)
if (path.includes('/') && path !== normalizedMockPath) {
const id = parseInt(path.split('/')[1]);
const mockDataItem = mockData[mockPath as keyof typeof mockData] as MockApiResponse<unknown>;
if (Array.isArray(mockDataItem.data)) {
const item = mockDataItem.data.find((item: {id: number}) => item.id === id);
if (item) {
return {
data: {
code: 0,
msg: "成功",
data: item
} as unknown as T,
status: 200
};
}
}
return { error: '未找到数据', status: 404 };
}
// 返回列表数据
return {
data: mockData[mockPath as keyof typeof mockData] as unknown as T,
status: 200
};
}
}
return { error: '没有匹配的模拟数据', status: 404 };
}
/**
* 扩展AxiosRequestConfig类型,以支持body参数
*/
interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
body?: string;
}
/**
* 通用 API 请求函数
*/
export async function apiRequest<T>(
endpoint: string,
options: ExtendedAxiosRequestConfig = {},
params?: QueryParams
): Promise<ApiResponse<T>> {
// 如果使用模拟数据,直接返回模拟响应
if (USE_MOCK_DATA) {
return getMockResponse<T>(endpoint);
}
console.log('api-base-url-----------',API_BASE_URL)
try {
// 构建 URL
const url = buildUrl(endpoint, params);
// 设置默认请求头
const headers = options.headers || {};
if (!headers['Content-Type'] && options.method !== 'GET') {
headers['Content-Type'] = 'application/json';
}
if (!headers['Accept']) {
headers['Accept'] = 'application/json';
}
// 针对 PostgREST 的额外处理
if (endpoint.includes('evaluation_point_groups') && (options.method === 'POST' || options.method === 'PATCH')) {
// console.log('使用 PostgREST 特定配置处理请求');
// 确保请求体是有效的 JSON 对象
if (options.body && typeof options.body === 'string') {
try {
JSON.parse(options.body); // 验证 JSON 是否有效
} catch (e) {
console.error('请求体不是有效的 JSON:', options.body);
throw new Error('请求体必须是有效的 JSON');
}
}
}
// console.log(`📦 axios-client.ts->请求URL: ${url}`);
// console.log(`axios-client.ts->发送 ${options.method || 'GET'} 请求到: ${url}`);
// 处理body参数,转换为data
if (options.body) {
// console.log(`axios-client.ts->请求体: \n${options.body}`);
options.data = options.body;
} else if (options.data) {
// console.log(`axios-client.ts->请求体: \n${typeof options.data === 'string' ? options.data : JSON.stringify(options.data)}`);
}
// 发送请求
const config: AxiosRequestConfig = {
...options,
url,
headers,
// 确保使用默认超时时间
timeout: options.timeout || DEFAULT_TIMEOUT
};
// console.log(`📦 axios-client.ts->请求配置: \n${JSON.stringify(config)}`);
// 删除body属性,避免axios警告
if ('body' in config) {
delete (config as ExtendedAxiosRequestConfig).body;
}
// 使用带重试功能的请求方法
const response: AxiosResponse = await axiosRetry(config);
// 收集响应头信息
const responseHeaders: Record<string, string> = {};
Object.entries(response.headers).forEach(([key, value]) => {
if (typeof value === 'string') {
responseHeaders[key] = value;
}
});
// 打印响应信息
// console.log(`响应状态: ${response.status}`);
// console.log(`响应头:`, responseHeaders);
// console.log(`响应体:`, response.data);
// 检查 PostgREST 特定错误
if (response.status >= 400) {
if (response.status === 400) {
console.error('PostgREST 错误 - 无效请求:', response.data);
return {
error: response.data?.message || response.data?.msg || '无效的请求格式,请检查数据格式是否正确',
status: response.status,
headers: responseHeaders
};
} else if (response.status === 401) {
console.error('PostgREST 错误 - 未授权:', response.data);
return {
error: response.data?.message || response.data?.msg || '未授权,请检查认证信息',
status: response.status,
headers: responseHeaders
};
} else if (response.status === 403) {
console.error('PostgREST 错误 - 禁止访问:', response.data);
return {
error: response.data?.message || response.data?.msg || '没有权限执行此操作',
status: response.status,
headers: responseHeaders
};
} else if (response.status === 404) {
console.error('PostgREST 错误 - 资源不存在:', response.data);
return {
error: response.data?.message || response.data?.msg || '请求的资源不存在',
status: response.status,
headers: responseHeaders
};
} else {
console.error(`HTTP请求失败: ${response.status} - ${url}`, response.data);
return {
error: response.data?.message || response.data?.msg || `请求失败: ${response.status}`,
status: response.status,
headers: responseHeaders
};
}
}
// 检查API返回的状态码
const data = response.data;
if (data && typeof data === 'object' && 'code' in data && data.code !== 0) {
console.error(`API请求失败: ${data.message || data.msg || '未知错误'} - ${url}`);
return {
error: data.message || data.msg || '请求失败',
status: response.status,
headers: responseHeaders
};
}
return {
data,
status: response.status,
headers: responseHeaders
};
} catch (error: unknown) {
console.error('API请求失败:', error);
if (isAxiosError(error)) {
const status = error.response?.status || 500;
const errorData = error.response?.data;
// 如果超时或网络错误,使用模拟数据(仅开发环境)
if (error.code === 'ECONNABORTED' && process.env.NODE_ENV !== 'production') {
console.warn('自动使用模拟数据作为回退');
return getMockResponse<T>(endpoint);
}
return {
error: errorData?.message || errorData?.msg || error.message || '未知错误',
status
};
}
// 如果超时或网络错误,使用模拟数据(仅开发环境)
if (process.env.NODE_ENV !== 'production') {
console.warn('自动使用模拟数据作为回退');
return getMockResponse<T>(endpoint);
}
return {
error: error instanceof Error ? error.message : '未知错误',
status: 500
};
}
}
// GET请求简化方法
export async function get<T>(endpoint: string, params?: QueryParams): Promise<ApiResponse<T>> {
return apiRequest<T>(endpoint, { method: 'GET' }, params);
}
// POST请求简化方法
export async function post<T>(endpoint: string, data?: unknown, params?: QueryParams): Promise<ApiResponse<T>> {
return apiRequest<T>(endpoint, { method: 'POST', data }, params);
}
// PUT请求简化方法
export async function put<T>(endpoint: string, data?: unknown, params?: QueryParams): Promise<ApiResponse<T>> {
return apiRequest<T>(endpoint, { method: 'PUT', data }, params);
}
// PATCH请求简化方法
export async function patch<T>(endpoint: string, data?: unknown, params?: QueryParams): Promise<ApiResponse<T>> {
return apiRequest<T>(endpoint, { method: 'PATCH', data }, params);
}
// DELETE请求简化方法
export async function del<T>(endpoint: string, params?: QueryParams): Promise<ApiResponse<T>> {
return apiRequest<T>(endpoint, { method: 'DELETE' }, params);
}
// 下载文件的方法
export async function downloadFile(path: string): Promise<Blob> {
const downloadUrl = `${DOCUMENT_URL}${path}`;
try {
// console.log(`📦 axios-client.ts->下载文件: ${downloadUrl}`);
const response = await axios.get(downloadUrl, {
responseType: 'blob'
});
return response.data;
} catch (error) {
console.error('下载文件失败:', error);
throw error;
}
}