From b3f574103bbd79c569fee95779fc1d835875800d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 19 Feb 2026 12:20:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=B8=8A=E4=BC=A0=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E7=9C=9F=E5=AE=9E=E5=8E=9F=E5=9B=A0=E5=B9=B6?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E7=BD=91=E7=BB=9C=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/grok/services/image_edit.py | 32 ++++++++++++++++++++++++ app/services/reverse/assets_upload.py | 7 ++++++ 2 files changed, 39 insertions(+) diff --git a/app/services/grok/services/image_edit.py b/app/services/grok/services/image_edit.py index 5d94abd..8d1df16 100644 --- a/app/services/grok/services/image_edit.py +++ b/app/services/grok/services/image_edit.py @@ -64,6 +64,31 @@ def _is_upload_rejected_error(exc: Exception) -> bool: return False +def _is_upload_network_error(exc: Exception) -> bool: + """判断是否为网络连通/网关挑战类上传失败。""" + msg = str(exc or "").lower() + if ( + "tls connect error" in msg + or "timed out" in msg + or "timeout" in msg + or "connection" in msg + or "proxy" in msg + or "curl: (35)" in msg + ): + return True + + details = getattr(exc, "details", None) + if isinstance(details, dict): + status = details.get("status") + body = str(details.get("body") or "").lower() + if status == 403 and ("just a moment" in body or "cloudflare" in body): + return True + if "tls connect error" in body or "timed out" in body: + return True + + return False + + class ImageEditService: """Image edit orchestration service.""" @@ -470,6 +495,13 @@ class ImageEditService: code="upload_rejected", status_code=400, ) + if _is_upload_network_error(e): + raise AppException( + message="图片上传失败:网络连接异常,请稍后重试", + error_type=ErrorType.SERVER.value, + code="upload_network_error", + status_code=502, + ) raise AppException( message="图片上传失败,请稍后重试", error_type=ErrorType.SERVER.value, diff --git a/app/services/reverse/assets_upload.py b/app/services/reverse/assets_upload.py index 6c1f24b..cb8f3c9 100644 --- a/app/services/reverse/assets_upload.py +++ b/app/services/reverse/assets_upload.py @@ -203,6 +203,7 @@ class AssetsUploadReverse: "AssetsUpload transient exception, mark as retryable: " f"{err_msg}" ) + last_fallback_error: UpstreamException | None = None # 部分 '"code"' 异常不会走到上面的降级分支,这里再强制兜底一次。 try: response = await session.post( @@ -228,6 +229,8 @@ class AssetsUploadReverse: details={"status": response.status_code, "body": body_preview}, ) except Exception as forced_direct_err: + if isinstance(forced_direct_err, UpstreamException): + last_fallback_error = forced_direct_err logger.warning( "AssetsUpload forced direct fallback failed, " f"error={forced_direct_err}" @@ -254,11 +257,15 @@ class AssetsUploadReverse: details={"status": response.status_code, "body": body_preview}, ) except Exception as forced_urllib_err: + if isinstance(forced_urllib_err, UpstreamException): + last_fallback_error = forced_urllib_err logger.warning( "AssetsUpload forced urllib fallback failed, " f"error={forced_urllib_err}" ) + if last_fallback_error is not None: + raise last_fallback_error raise UpstreamException( message=f"AssetsUpload transient failure: {err_msg}", details={"status": 403, "error": err_msg},