2025-09-19 18:08:03 +08:00

303 lines
7.4 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.

/**
* 视频编辑API错误处理器
*/
import { debugLog, errorHandlingConfig } from './config';
export interface ApiError {
code: number;
message: string;
details?: any;
timestamp: string;
endpoint?: string;
}
export interface RetryOptions {
maxRetries?: number;
retryDelay?: number;
shouldRetry?: (error: ApiError) => boolean;
onRetry?: (attempt: number, error: ApiError) => void;
}
/**
* 创建API错误对象
*/
export function createApiError(
code: number,
message: string,
details?: any,
endpoint?: string
): ApiError {
return {
code,
message,
details,
timestamp: new Date().toISOString(),
endpoint
};
}
/**
* 判断错误是否可重试
*/
export function isRetryableError(error: ApiError): boolean {
// 网络错误、超时、5xx服务器错误可重试
return (
error.code === 0 || // 网络错误
error.code === 408 || // 请求超时
error.code === 429 || // 请求过多
(error.code >= 500 && error.code < 600) // 服务器错误
);
}
/**
* 计算重试延迟(指数退避)
*/
export function calculateRetryDelay(attempt: number, baseDelay: number = 1000): number {
return Math.min(baseDelay * Math.pow(2, attempt - 1), 10000); // 最大10秒
}
/**
* 带重试的API调用包装器
*/
export async function withRetry<T>(
apiCall: () => Promise<T>,
options: RetryOptions = {}
): Promise<T> {
const {
maxRetries = errorHandlingConfig.maxRetries,
retryDelay = errorHandlingConfig.retryDelay,
shouldRetry = isRetryableError,
onRetry
} = options;
let lastError: ApiError;
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
try {
const result = await apiCall();
// 如果之前有重试,记录成功
if (attempt > 1) {
debugLog(`API调用在第${attempt}次尝试后成功`);
}
return result;
} catch (error: any) {
// 转换为标准错误格式
lastError = error instanceof Error
? createApiError(0, error.message, error)
: error;
debugLog(`API调用失败 (尝试 ${attempt}/${maxRetries + 1})`, lastError);
// 如果是最后一次尝试,或错误不可重试,直接抛出
if (attempt > maxRetries || !shouldRetry(lastError)) {
throw lastError;
}
// 计算延迟时间
const delay = calculateRetryDelay(attempt, retryDelay);
// 调用重试回调
onRetry?.(attempt, lastError);
debugLog(`${delay}ms后进行第${attempt + 1}次重试`);
// 等待后重试
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
/**
* API错误分类
*/
export enum ErrorCategory {
NETWORK = 'network',
VALIDATION = 'validation',
AUTHENTICATION = 'authentication',
AUTHORIZATION = 'authorization',
NOT_FOUND = 'not_found',
SERVER = 'server',
UNKNOWN = 'unknown'
}
/**
* 根据错误码分类错误
*/
export function categorizeError(error: ApiError): ErrorCategory {
const { code } = error;
if (code === 0) return ErrorCategory.NETWORK;
if (code === 400) return ErrorCategory.VALIDATION;
if (code === 401) return ErrorCategory.AUTHENTICATION;
if (code === 403) return ErrorCategory.AUTHORIZATION;
if (code === 404) return ErrorCategory.NOT_FOUND;
if (code >= 500) return ErrorCategory.SERVER;
return ErrorCategory.UNKNOWN;
}
/**
* 获取用户友好的错误消息
*/
export function getUserFriendlyMessage(error: ApiError): string {
const category = categorizeError(error);
switch (category) {
case ErrorCategory.NETWORK:
return '网络连接失败,请检查网络连接后重试';
case ErrorCategory.VALIDATION:
return '请求参数有误,请检查输入内容';
case ErrorCategory.AUTHENTICATION:
return '登录已过期,请重新登录';
case ErrorCategory.AUTHORIZATION:
return '权限不足,无法执行此操作';
case ErrorCategory.NOT_FOUND:
return '请求的资源不存在';
case ErrorCategory.SERVER:
return '服务器暂时不可用,请稍后重试';
default:
return error.message || '发生未知错误';
}
}
/**
* 错误通知处理
*/
export function handleErrorNotification(error: ApiError): void {
const category = categorizeError(error);
const message = getUserFriendlyMessage(error);
// 根据配置决定是否显示通知
const { notifications } = errorHandlingConfig;
let shouldNotify = false;
switch (category) {
case ErrorCategory.NETWORK:
shouldNotify = notifications.showNetworkErrors;
break;
case ErrorCategory.VALIDATION:
shouldNotify = notifications.showValidationErrors;
break;
case ErrorCategory.SERVER:
shouldNotify = notifications.showServerErrors;
break;
default:
shouldNotify = true;
}
if (shouldNotify) {
// 这里可以集成实际的通知系统
console.error('API错误通知:', message, error);
// 如果有全局通知系统,在这里调用
if (typeof window !== 'undefined' && (window as any).msg) {
(window as any).msg.error(message);
}
}
}
/**
* 错误恢复策略
*/
export interface RecoveryStrategy {
name: string;
canRecover: (error: ApiError) => boolean;
recover: (error: ApiError) => Promise<any>;
}
/**
* Mock API回退策略
*/
export const mockFallbackStrategy: RecoveryStrategy = {
name: 'mock-fallback',
canRecover: (error: ApiError) => {
return errorHandlingConfig.fallbackToMock &&
(error.code === 404 || error.code >= 500);
},
recover: async (error: ApiError) => {
debugLog('API失败回退到Mock模式', error);
// 这里可以动态切换到Mock API
return null;
}
};
/**
* 缓存回退策略
*/
export const cacheStrategy: RecoveryStrategy = {
name: 'cache-fallback',
canRecover: (error: ApiError) => {
return error.code === 0 || error.code >= 500;
},
recover: async (error: ApiError) => {
debugLog('尝试从缓存恢复数据', error);
// 这里可以从本地缓存获取数据
return null;
}
};
/**
* 默认恢复策略列表
*/
export const defaultRecoveryStrategies: RecoveryStrategy[] = [
cacheStrategy,
mockFallbackStrategy
];
/**
* 尝试错误恢复
*/
export async function attemptRecovery(
error: ApiError,
strategies: RecoveryStrategy[] = defaultRecoveryStrategies
): Promise<any> {
for (const strategy of strategies) {
if (strategy.canRecover(error)) {
try {
debugLog(`尝试恢复策略: ${strategy.name}`);
const result = await strategy.recover(error);
if (result !== null) {
debugLog(`恢复策略 ${strategy.name} 成功`);
return result;
}
} catch (recoveryError) {
debugLog(`恢复策略 ${strategy.name} 失败`, recoveryError);
}
}
}
debugLog('所有恢复策略都失败了');
return null;
}
/**
* 完整的错误处理流程
*/
export async function handleApiError(error: any, endpoint?: string): Promise<any> {
// 标准化错误格式
const apiError: ApiError = error instanceof Error
? createApiError(0, error.message, error, endpoint)
: { ...error, endpoint };
// 记录错误
debugLog('API错误', apiError);
// 显示通知
handleErrorNotification(apiError);
// 尝试恢复
const recoveryResult = await attemptRecovery(apiError);
if (recoveryResult !== null) {
return recoveryResult;
}
// 抛出错误
throw apiError;
}