mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
feat: 增加故障转移和重试,并整理代码
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @fileoverview 错误归一化模块
|
||||
* @description 统一处理页面级和 HTTP 级错误,提供可重试判定
|
||||
*/
|
||||
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { ADAPTER_ERRORS } from '../../utils/constants.js';
|
||||
|
||||
// ==========================================
|
||||
// 可重试判定
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 判断错误是否可重试
|
||||
* @param {string} errorMessage - 错误消息
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isRetryableError(errorMessage) {
|
||||
if (!errorMessage) return false;
|
||||
|
||||
const retryablePatterns = [
|
||||
// 网络错误
|
||||
/network|net::|econnreset|econnrefused|etimedout/i,
|
||||
// 超时
|
||||
/timeout|timed out/i,
|
||||
// 页面崩溃
|
||||
/crashed|crash/i,
|
||||
// 5xx 服务端错误
|
||||
/5\d{2}|internal server error|bad gateway|service unavailable/i,
|
||||
// 限流(可能是临时的)
|
||||
/rate limit|too many requests|429/i,
|
||||
];
|
||||
|
||||
return retryablePatterns.some(pattern => pattern.test(errorMessage));
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 页面错误归一化
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 统一处理页面级错误
|
||||
* @param {Error} err - 原始错误
|
||||
* @param {object} [meta={}] - 日志元数据
|
||||
* @returns {{ error: string, code: string, retryable: boolean } | null}
|
||||
*/
|
||||
export function normalizePageError(err, meta = {}) {
|
||||
if (err.message === 'PAGE_CLOSED') {
|
||||
logger.error('适配器', '页面已关闭', meta);
|
||||
return { error: '页面已关闭,请勿在生图过程中刷新页面', code: ADAPTER_ERRORS.PAGE_CLOSED, retryable: true };
|
||||
}
|
||||
if (err.message === 'PAGE_CRASHED') {
|
||||
logger.error('适配器', '页面崩溃', meta);
|
||||
return { error: '页面崩溃,请重试', code: ADAPTER_ERRORS.PAGE_CRASHED, retryable: true };
|
||||
}
|
||||
if (err.message === 'PAGE_INVALID') {
|
||||
logger.error('适配器', '页面状态无效', meta);
|
||||
return { error: '页面状态无效,请重新初始化', code: ADAPTER_ERRORS.PAGE_INVALID, retryable: true };
|
||||
}
|
||||
if (err.name === 'TimeoutError' || err.message?.includes('Timeout')) {
|
||||
logger.error('适配器', '请求超时', meta);
|
||||
return { error: '请求超时 (120秒), 请检查网络或稍后重试', code: ADAPTER_ERRORS.TIMEOUT_ERROR, retryable: true };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// HTTP 错误归一化
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 统一处理 HTTP 响应错误
|
||||
* @param {import('playwright-core').Response} response - HTTP 响应对象
|
||||
* @param {string} [content=null] - 响应体内容(可选)
|
||||
* @returns {{ error: string, code: string, retryable: boolean } | null}
|
||||
*/
|
||||
export function normalizeHttpError(response, content = null) {
|
||||
const status = response.status();
|
||||
|
||||
// 429 限流检查
|
||||
if (status === 429 || content?.includes('Too Many Requests')) {
|
||||
return { error: '触发限流/上游繁忙', code: ADAPTER_ERRORS.RATE_LIMITED, retryable: true };
|
||||
}
|
||||
|
||||
// reCAPTCHA 验证失败
|
||||
if (content?.includes('recaptcha validation failed')) {
|
||||
return { error: '触发人机验证', code: ADAPTER_ERRORS.CAPTCHA_REQUIRED, retryable: false };
|
||||
}
|
||||
|
||||
// 5xx 服务端错误(可重试)
|
||||
if (status >= 500) {
|
||||
return { error: `上游服务器错误,HTTP错误码: ${status}`, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: true };
|
||||
}
|
||||
|
||||
// 4xx 客户端错误(不可重试)
|
||||
if (status >= 400) {
|
||||
return { error: `请求错误,HTTP错误码: ${status}`, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: false };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 通用错误归一化
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 标准化错误对象(通用)
|
||||
* @param {string} error - 错误消息
|
||||
* @returns {{error: string, code: string, retryable: boolean}}
|
||||
*/
|
||||
export function normalizeError(error) {
|
||||
const retryable = isRetryableError(error);
|
||||
|
||||
let code = ADAPTER_ERRORS.NETWORK_ERROR;
|
||||
if (/timeout/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.TIMEOUT_ERROR;
|
||||
} else if (/crashed/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.PAGE_CRASHED;
|
||||
} else if (/closed/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.PAGE_CLOSED;
|
||||
} else if (/5\d{2}|internal server/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.HTTP_ERROR;
|
||||
} else if (/rate limit|429/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.RATE_LIMITED;
|
||||
} else if (/captcha|recaptcha/i.test(error)) {
|
||||
code = ADAPTER_ERRORS.CAPTCHA_REQUIRED;
|
||||
}
|
||||
|
||||
return { error, code, retryable };
|
||||
}
|
||||
Reference in New Issue
Block a user