Spaces:
Running
Running
File size: 6,877 Bytes
461222a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | import APIException from "@/lib/exceptions/APIException.ts";
import EX from "@/api/consts/exceptions.ts";
import logger from "@/lib/logger.ts";
/**
* 即梦API错误响应接口
*/
export interface JimengErrorResponse {
ret: string;
errmsg: string;
data?: any;
historyId?: string;
}
/**
* 错误处理选项
*/
export interface ErrorHandlerOptions {
context?: string;
historyId?: string;
retryCount?: number;
maxRetries?: number;
operation?: string;
}
/**
* 统一的即梦API错误处理器
*/
export class JimengErrorHandler {
/**
* 处理即梦API响应错误
*/
static handleApiResponse(
response: JimengErrorResponse,
options: ErrorHandlerOptions = {}
): never {
const { ret, errmsg, historyId } = response;
const { context = '即梦API请求', operation = '操作' } = options;
logger.error(`${context}失败: ret=${ret}, errmsg=${errmsg}${historyId ? `, historyId=${historyId}` : ''}`);
// 根据错误码分类处理
switch (ret) {
case '1015':
throw new APIException(EX.API_TOKEN_EXPIRES, `[登录失效]: ${errmsg}。请重新获取refresh_token并更新配置`);
case '5000':
throw new APIException(EX.API_IMAGE_GENERATION_INSUFFICIENT_POINTS,
`[积分不足]: ${errmsg}。建议:1)尝试使用1024x1024分辨率,2)检查是否需要购买积分,3)确认账户状态正常`);
case '4001':
throw new APIException(EX.API_CONTENT_FILTERED, `[内容违规]: ${errmsg}`);
case '4002':
throw new APIException(EX.API_REQUEST_PARAMS_INVALID, `[参数错误]: ${errmsg}`);
case '5001':
throw new APIException(EX.API_IMAGE_GENERATION_FAILED, `[生成失败]: ${errmsg}`);
case '5002':
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, `[视频生成失败]: ${errmsg}`);
default:
throw new APIException(EX.API_REQUEST_FAILED, `[${operation}失败]: ${errmsg} (错误码: ${ret})`);
}
}
/**
* 处理网络请求错误
*/
static handleNetworkError(
error: any,
options: ErrorHandlerOptions = {}
): never {
const { context = '网络请求', retryCount = 0, maxRetries = 3 } = options;
logger.error(`${context}网络错误 (尝试 ${retryCount + 1}/${maxRetries + 1}): ${error.message}`);
if (error.code === 'ECONNABORTED') {
throw new APIException(EX.API_REQUEST_FAILED, `[请求超时]: ${context}超时,请稍后重试`);
}
if (error.code === 'ENOTFOUND') {
throw new APIException(EX.API_REQUEST_FAILED, `[网络错误]: 无法连接到即梦服务器,请检查网络连接`);
}
if (error.response?.status >= 500) {
throw new APIException(EX.API_REQUEST_FAILED, `[服务器错误]: 即梦服务器暂时不可用 (${error.response.status})`);
}
if (error.response?.status === 429) {
throw new APIException(EX.API_REQUEST_FAILED, `[请求频率限制]: 请求过于频繁,请稍后重试`);
}
throw new APIException(EX.API_REQUEST_FAILED, `[${context}失败]: ${error.message}`);
}
/**
* 处理轮询超时错误
* @returns 如果有部分结果,返回 void 而不抛出异常
*/
static handlePollingTimeout(
pollCount: number,
maxPollCount: number,
elapsedTime: number,
status: number,
itemCount: number,
historyId?: string
): void {
const message = `轮询超时: 已轮询 ${pollCount} 次,耗时 ${elapsedTime} 秒,最终状态: ${status},图片数量: ${itemCount}`;
logger.warn(message + (historyId ? `,历史ID: ${historyId}` : ''));
if (itemCount === 0) {
throw new APIException(EX.API_IMAGE_GENERATION_FAILED,
`生成超时且无结果,状态码: ${status}${historyId ? `,历史ID: ${historyId}` : ''}`);
}
// 如果有部分结果,不抛出异常,让调用者处理
logger.info(`轮询超时但已获得 ${itemCount} 张图片,将返回现有结果`);
}
/**
* 处理生成失败错误
* @param itemCount 已生成的结果数量,如果 > 0 则不抛出异常
* @returns 如果有部分结果,返回 false 表示不应抛出异常
*/
static handleGenerationFailure(
status: number,
failCode: string | undefined,
historyId?: string,
type: 'image' | 'video' = 'image',
itemCount: number = 0
): boolean {
const typeText = type === 'image' ? '图像' : '视频';
const message = `${typeText}生成最终失败: status=${status}, failCode=${failCode}${historyId ? `, historyId=${historyId}` : ''}, 已生成数量=${itemCount}`;
// 如果有部分结果,只记录警告,不抛出异常
if (itemCount > 0) {
logger.warn(message);
logger.info(`${typeText}生成部分失败,但已获得 ${itemCount} 个结果,将返回现有结果`);
return false; // 不抛出异常
}
// 没有任何结果时,记录错误并抛出异常
logger.error(message);
const exception = type === 'image' ? EX.API_IMAGE_GENERATION_FAILED : EX.API_VIDEO_GENERATION_FAILED;
throw new APIException(exception, `${typeText}生成失败,状态码: ${status}${failCode ? `,错误码: ${failCode}` : ''}`);
}
/**
* 包装重试逻辑的错误处理
*/
static async withRetry<T>(
operation: () => Promise<T>,
options: ErrorHandlerOptions & { maxRetries?: number; retryDelay?: number } = {}
): Promise<T> {
const {
maxRetries = 3,
retryDelay = 5000,
context = '操作',
operation: operationName = '请求'
} = options;
let lastError: any;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// 如果是APIException,直接抛出,不重试
if (error instanceof APIException) {
throw error;
}
if (attempt < maxRetries) {
logger.warn(`${context}失败 (尝试 ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
logger.info(`${retryDelay / 1000}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
}
// 所有重试都失败了
this.handleNetworkError(lastError, {
context,
retryCount: maxRetries,
maxRetries,
operation: operationName
});
}
}
/**
* 便捷的错误处理函数
*/
export const handleJimengError = JimengErrorHandler.handleApiResponse;
export const handleNetworkError = JimengErrorHandler.handleNetworkError;
export const handlePollingTimeout = JimengErrorHandler.handlePollingTimeout;
export const handleGenerationFailure = JimengErrorHandler.handleGenerationFailure;
export const withRetry = JimengErrorHandler.withRetry;
|