| import APIException from "@/lib/exceptions/APIException.ts"; |
| import EX from "@/api/consts/exceptions.ts"; |
| import logger from "@/lib/logger.ts"; |
|
|
| |
| |
| |
| export interface JimengErrorResponse { |
| ret: string; |
| errmsg: string; |
| data?: any; |
| historyId?: string; |
| } |
|
|
| |
| |
| |
| export interface ErrorHandlerOptions { |
| context?: string; |
| historyId?: string; |
| retryCount?: number; |
| maxRetries?: number; |
| operation?: string; |
| } |
|
|
| |
| |
| |
| export class JimengErrorHandler { |
| |
| |
| |
| |
| 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}`); |
| } |
|
|
| |
| |
| |
| |
| 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} 张图片,将返回现有结果`); |
| } |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| |
| 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; |
|
|