From c3163729f49cdfbf87b12b26db00a9e8325fac2d Mon Sep 17 00:00:00 2001 From: daidai Date: Fri, 27 Mar 2026 19:45:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - normalizeHttpError 从响应体提取具体错误信息 - 支持 JSON 格式错误解析 - 新增 isRetryableError 判断错误是否可重试 - 新增 normalizeError 通用错误归一化 --- src/backend/utils/error.js | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/error.js b/src/backend/utils/error.js index 24d6992..7a3fbfa 100644 --- a/src/backend/utils/error.js +++ b/src/backend/utils/error.js @@ -96,6 +96,36 @@ export function normalizePageError(err, meta = {}) { export function normalizeHttpError(response, content = null) { const status = response.status(); + // 尝试从响应体中提取具体错误信息 + let detailError = null; + if (content) { + try { + const json = JSON.parse(content); + // 格式: {"error": "Request rejected: ..."} + if (json.error && typeof json.error === 'string') { + detailError = json.error; + } + // 格式: {"error": {"message": "..."}} + else if (json.error?.message) { + detailError = json.error.message; + } + } catch { + // 非 JSON 格式,尝试直接使用内容 + if (content.length < 200) { + detailError = content; + } + } + } + + // 检查是否是内容审核拒绝 (通常返回 422 或 429 但含有拒绝信息) + const isContentRejection = detailError && ( + /reject|violat|terms|blocked|forbidden|unsafe|moderat/i.test(detailError) || + detailError === 'prompt failed' + ); + if (isContentRejection) { + return { error: `内容被拒绝: ${detailError}`, code: ADAPTER_ERRORS.CONTENT_BLOCKED, retryable: false }; + } + // 429 限流检查 if (status === 429 || content?.includes('Too Many Requests')) { return { error: '触发限流/上游繁忙', code: ADAPTER_ERRORS.RATE_LIMITED, retryable: true }; @@ -108,12 +138,18 @@ export function normalizeHttpError(response, content = null) { // 5xx 服务端错误(可重试) if (status >= 500) { - return { error: `上游服务器错误,HTTP错误码: ${status}`, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: true }; + const msg = detailError + ? `上游服务器错误 (${status}): ${detailError}` + : `上游服务器错误,HTTP错误码: ${status}`; + return { error: msg, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: true }; } // 4xx 客户端错误(不可重试) if (status >= 400) { - return { error: `请求错误,HTTP错误码: ${status}`, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: false }; + const msg = detailError + ? `请求被拒绝 (${status}): ${detailError}` + : `请求错误,HTTP错误码: ${status}`; + return { error: msg, code: ADAPTER_ERRORS.HTTP_ERROR, retryable: false }; } return null;