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},