Files
shorturl-analytics/lib/api.ts
2025-04-23 20:04:37 +08:00

139 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import supabase from './supabase';
// Define response type for API
export interface ApiResponse<T = unknown> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
/**
* 通用的LIMQ API请求函数包含重试机制和错误处理
*/
export async function limqRequest<T = unknown>(
endpoint: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: Record<string, unknown>,
options?: {
retryCount?: number;
retryDelay?: number;
timeout?: number;
}
): Promise<ApiResponse<T>> {
// 默认配置
const retryCount = options?.retryCount ?? 2; // 默认重试2次
const retryDelay = options?.retryDelay ?? 1000; // 默认延迟1秒
const timeout = options?.timeout ?? 10000; // 默认超时10秒
let lastError: Error | null = null;
let currentRetry = 0;
// 创建延迟函数
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// 重试循环
while (currentRetry <= retryCount) {
try {
console.log(`[API] ${method} ${endpoint} 尝试 ${currentRetry + 1}/${retryCount + 1}`);
// 获取会话
const { data: { session } } = await supabase.auth.getSession();
// 检查会话是否存在
if (!session) {
console.error(`[API] 未找到活跃会话,用户需要登录`);
if (currentRetry < retryCount) {
currentRetry++;
console.log(`[API] 等待 ${retryDelay}ms 后重试获取会话...`);
await delay(retryDelay);
continue;
}
return {
success: false,
error: '需要登录才能访问API'
};
}
// 获取API基础URL
const baseUrl = process.env.NEXT_PUBLIC_LIMQ_API;
if (!baseUrl) {
throw new Error('API URL未配置');
}
const url = `${baseUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
console.log(`[API] 请求URL: ${url}`);
// 构建请求选项
const fetchOptions: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
mode: 'cors'
};
if (data && (method === 'POST' || method === 'PUT')) {
fetchOptions.body = JSON.stringify(data);
console.log(`[API] 请求数据:`, data);
}
// 添加超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
fetchOptions.signal = controller.signal;
// 发送请求
const response = await fetch(url, fetchOptions);
clearTimeout(timeoutId); // 清除超时控制
// 处理响应
if (!response.ok) {
const errorData = await response.json().catch(() => null);
console.error(`[API] 请求失败: ${response.status} ${response.statusText}`, errorData);
// 对于认证错误,尝试重试
if ((response.status === 401 || response.status === 403) && currentRetry < retryCount) {
currentRetry++;
console.log(`[API] 认证错误,等待 ${retryDelay}ms 后重试...`);
await delay(retryDelay);
continue;
}
throw new Error(errorData?.error || `请求失败: ${response.status}`);
}
// 成功响应
const responseData = await response.json();
console.log(`[API] ${method} ${endpoint} 成功`);
return responseData;
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
console.error(`[API] 请求出错:`, lastError);
// 对于超时和网络错误,尝试重试
if (currentRetry < retryCount &&
(error instanceof DOMException && error.name === 'AbortError' ||
error instanceof TypeError && error.message.includes('network'))) {
currentRetry++;
console.log(`[API] 网络错误,等待 ${retryDelay}ms 后重试...`);
await delay(retryDelay);
continue;
}
// 已达到最大重试次数或不是网络错误
break;
}
}
// 所有重试均失败
console.error(`[API] ${method} ${endpoint} 失败,已重试 ${currentRetry}`);
return {
success: false,
error: lastError?.message || '请求失败,请稍后重试'
};
}