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;