Spaces:
Paused
Paused
| import requests | |
| from app.domain.image_models import ImageMetadata, ImageUploader, UploadResponse | |
| from enum import Enum | |
| from typing import Optional, Any | |
| class UploadErrorType(Enum): | |
| """上传错误类型枚举""" | |
| NETWORK_ERROR = "network_error" # 网络请求错误 | |
| AUTH_ERROR = "auth_error" # 认证错误 | |
| INVALID_FILE = "invalid_file" # 无效文件 | |
| SERVER_ERROR = "server_error" # 服务器错误 | |
| PARSE_ERROR = "parse_error" # 响应解析错误 | |
| UNKNOWN = "unknown" # 未知错误 | |
| class UploadError(Exception): | |
| """图片上传错误异常类""" | |
| def __init__( | |
| self, | |
| message: str, | |
| error_type: UploadErrorType = UploadErrorType.UNKNOWN, | |
| status_code: Optional[int] = None, | |
| details: Optional[dict] = None, | |
| original_error: Optional[Exception] = None | |
| ): | |
| """ | |
| 初始化上传错误异常 | |
| Args: | |
| message: 错误消息 | |
| error_type: 错误类型 | |
| status_code: HTTP状态码 | |
| details: 详细错误信息 | |
| original_error: 原始异常 | |
| """ | |
| self.message = message | |
| self.error_type = error_type | |
| self.status_code = status_code | |
| self.details = details or {} | |
| self.original_error = original_error | |
| # 构建完整错误信息 | |
| full_message = f"[{error_type.value}] {message}" | |
| if status_code: | |
| full_message = f"{full_message} (Status: {status_code})" | |
| if details: | |
| full_message = f"{full_message} - Details: {details}" | |
| super().__init__(full_message) | |
| def from_response(cls, response: Any, message: Optional[str] = None) -> "UploadError": | |
| """ | |
| 从HTTP响应创建错误实例 | |
| Args: | |
| response: HTTP响应对象 | |
| message: 自定义错误消息 | |
| """ | |
| try: | |
| error_data = response.json() | |
| details = error_data.get("data", {}) | |
| return cls( | |
| message=message or error_data.get("message", "Unknown error"), | |
| error_type=UploadErrorType.SERVER_ERROR, | |
| status_code=response.status_code, | |
| details=details | |
| ) | |
| except Exception: | |
| return cls( | |
| message=message or "Failed to parse error response", | |
| error_type=UploadErrorType.PARSE_ERROR, | |
| status_code=response.status_code | |
| ) | |
| class SmMsUploader(ImageUploader): | |
| API_URL = "https://sm.ms/api/v2/upload" | |
| def __init__(self, api_key: str): | |
| self.api_key = api_key | |
| def upload(self, file: bytes, filename: str) -> UploadResponse: | |
| try: | |
| # 准备请求头 | |
| headers = { | |
| "Authorization": f"Basic {self.api_key}" | |
| } | |
| # 准备文件数据 | |
| files = { | |
| "smfile": (filename, file, "image/png") | |
| } | |
| # 发送请求 | |
| response = requests.post( | |
| self.API_URL, | |
| headers=headers, | |
| files=files | |
| ) | |
| # 检查响应状态 | |
| response.raise_for_status() | |
| # 解析响应 | |
| result = response.json() | |
| # 验证上传是否成功 | |
| if not result.get("success"): | |
| raise UploadError(result.get("message", "Upload failed")) | |
| # 转换为统一格式 | |
| data = result["data"] | |
| image_metadata = ImageMetadata( | |
| width=data["width"], | |
| height=data["height"], | |
| filename=data["filename"], | |
| size=data["size"], | |
| url=data["url"], | |
| delete_url=data["delete"] | |
| ) | |
| return UploadResponse( | |
| success=True, | |
| code="success", | |
| message="Upload success", | |
| data=image_metadata | |
| ) | |
| except requests.RequestException as e: | |
| # 处理网络请求相关错误 | |
| raise UploadError(f"Upload request failed: {str(e)}") | |
| except (KeyError, ValueError) as e: | |
| # 处理响应解析错误 | |
| raise UploadError(f"Invalid response format: {str(e)}") | |
| except Exception as e: | |
| # 处理其他未预期的错误 | |
| raise UploadError(f"Upload failed: {str(e)}") | |
| class QiniuUploader(ImageUploader): | |
| def __init__(self, access_key: str, secret_key: str): | |
| self.access_key = access_key | |
| self.secret_key = secret_key | |
| def upload(self, file: bytes, filename: str) -> UploadResponse: | |
| # 实现七牛云的具体上传逻辑 | |
| pass | |
| class PicGoUploader(ImageUploader): | |
| """Chevereto API 图片上传器""" | |
| def __init__(self, api_key: str, api_url: str = "https://www.picgo.net/api/1/upload"): | |
| """ | |
| 初始化 Chevereto 上传器 | |
| Args: | |
| api_key: Chevereto API 密钥 | |
| api_url: Chevereto API 上传地址 | |
| """ | |
| self.api_key = api_key | |
| self.api_url = api_url | |
| def upload(self, file: bytes, filename: str) -> UploadResponse: | |
| """ | |
| 上传图片到 Chevereto 服务 | |
| Args: | |
| file: 图片文件二进制数据 | |
| filename: 文件名 | |
| Returns: | |
| UploadResponse: 上传响应对象 | |
| Raises: | |
| UploadError: 上传失败时抛出异常 | |
| """ | |
| try: | |
| # 准备请求头 | |
| headers = { | |
| "X-API-Key": self.api_key | |
| } | |
| # 准备文件数据 | |
| files = { | |
| "source": (filename, file) | |
| } | |
| # 发送请求 | |
| response = requests.post( | |
| self.api_url, | |
| headers=headers, | |
| files=files | |
| ) | |
| # 检查响应状态 | |
| response.raise_for_status() | |
| # 解析响应 | |
| result = response.json() | |
| # 验证上传是否成功 | |
| if result.get("status_code") != 200: | |
| error_message = "Upload failed" | |
| if "error" in result: | |
| error_message = result["error"].get("message", error_message) | |
| raise UploadError( | |
| message=error_message, | |
| error_type=UploadErrorType.SERVER_ERROR, | |
| status_code=result.get("status_code"), | |
| details=result.get("error") | |
| ) | |
| # 从响应中提取图片信息 | |
| image_data = result.get("image", {}) | |
| # 构建图片元数据 | |
| image_metadata = ImageMetadata( | |
| width=image_data.get("width", 0), | |
| height=image_data.get("height", 0), | |
| filename=image_data.get("filename", filename), | |
| size=image_data.get("size", 0), | |
| url=image_data.get("url", ""), | |
| delete_url=image_data.get("delete_url", None) | |
| ) | |
| return UploadResponse( | |
| success=True, | |
| code="success", | |
| message=result.get("success", {}).get("message", "Upload success"), | |
| data=image_metadata | |
| ) | |
| except requests.RequestException as e: | |
| # 处理网络请求相关错误 | |
| raise UploadError( | |
| message=f"Upload request failed: {str(e)}", | |
| error_type=UploadErrorType.NETWORK_ERROR, | |
| original_error=e | |
| ) | |
| except (KeyError, ValueError, TypeError) as e: | |
| # 处理响应解析错误 | |
| raise UploadError( | |
| message=f"Invalid response format: {str(e)}", | |
| error_type=UploadErrorType.PARSE_ERROR, | |
| original_error=e | |
| ) | |
| except UploadError: | |
| # 重新抛出已经是 UploadError 类型的异常 | |
| raise | |
| except Exception as e: | |
| # 处理其他未预期的错误 | |
| raise UploadError( | |
| message=f"Upload failed: {str(e)}", | |
| error_type=UploadErrorType.UNKNOWN, | |
| original_error=e | |
| ) | |
| class CloudFlareImgBedUploader(ImageUploader): | |
| """CloudFlare图床上传器""" | |
| def __init__(self, auth_code: str, api_url: str): | |
| """ | |
| 初始化CloudFlare图床上传器 | |
| Args: | |
| auth_code: 认证码 | |
| api_url: 上传API地址 | |
| """ | |
| self.auth_code = auth_code | |
| self.api_url = api_url | |
| def upload(self, file: bytes, filename: str) -> UploadResponse: | |
| """ | |
| 上传图片到CloudFlare图床 | |
| Args: | |
| file: 图片文件二进制数据 | |
| filename: 文件名 | |
| Returns: | |
| UploadResponse: 上传响应对象 | |
| Raises: | |
| UploadError: 上传失败时抛出异常 | |
| """ | |
| try: | |
| # 准备请求URL(添加认证码参数,如果存在) | |
| if self.auth_code: | |
| request_url = f"{self.api_url}?authCode={self.auth_code}&uploadNameType=origin" | |
| else: | |
| request_url = f"{self.api_url}?uploadNameType=origin" | |
| # 准备文件数据 | |
| files = { | |
| "file": (filename, file) | |
| } | |
| # 发送请求 | |
| response = requests.post( | |
| request_url, | |
| files=files | |
| ) | |
| # 检查响应状态 | |
| response.raise_for_status() | |
| # 解析响应 | |
| result = response.json() | |
| # 验证响应格式 | |
| if not result or not isinstance(result, list) or len(result) == 0: | |
| raise UploadError( | |
| message="Invalid response format", | |
| error_type=UploadErrorType.PARSE_ERROR | |
| ) | |
| # 获取文件URL | |
| file_path = result[0].get("src") | |
| if not file_path: | |
| raise UploadError( | |
| message="Missing file URL in response", | |
| error_type=UploadErrorType.PARSE_ERROR | |
| ) | |
| # 构建完整URL(如果返回的是相对路径) | |
| base_url = self.api_url.split("/upload")[0] | |
| full_url = file_path if file_path.startswith(("http://", "https://")) else f"{base_url}{file_path}" | |
| # 构建图片元数据(注意:CloudFlare-ImgBed不返回所有元数据,所以部分字段为默认值) | |
| image_metadata = ImageMetadata( | |
| width=0, # CloudFlare-ImgBed不返回宽度 | |
| height=0, # CloudFlare-ImgBed不返回高度 | |
| filename=filename, | |
| size=0, # CloudFlare-ImgBed不返回大小 | |
| url=full_url, | |
| delete_url=None # CloudFlare-ImgBed不返回删除URL | |
| ) | |
| return UploadResponse( | |
| success=True, | |
| code="success", | |
| message="Upload success", | |
| data=image_metadata | |
| ) | |
| except requests.RequestException as e: | |
| # 处理网络请求相关错误 | |
| raise UploadError( | |
| message=f"Upload request failed: {str(e)}", | |
| error_type=UploadErrorType.NETWORK_ERROR, | |
| original_error=e | |
| ) | |
| except (KeyError, ValueError, TypeError, IndexError) as e: | |
| # 处理响应解析错误 | |
| raise UploadError( | |
| message=f"Invalid response format: {str(e)}", | |
| error_type=UploadErrorType.PARSE_ERROR, | |
| original_error=e | |
| ) | |
| except UploadError: | |
| # 重新抛出已经是 UploadError 类型的异常 | |
| raise | |
| except Exception as e: | |
| # 处理其他未预期的错误 | |
| raise UploadError( | |
| message=f"Upload failed: {str(e)}", | |
| error_type=UploadErrorType.UNKNOWN, | |
| original_error=e | |
| ) | |
| class ImageUploaderFactory: | |
| def create(provider: str, **credentials) -> ImageUploader: | |
| if provider == "smms": | |
| return SmMsUploader(credentials["api_key"]) | |
| elif provider == "qiniu": | |
| return QiniuUploader( | |
| credentials["access_key"], | |
| credentials["secret_key"] | |
| ) | |
| elif provider == "picgo": | |
| api_url = credentials.get("api_url", "https://www.picgo.net/api/1/upload") | |
| return PicGoUploader(credentials["api_key"], api_url) | |
| elif provider == "cloudflare_imgbed": | |
| return CloudFlareImgBedUploader( | |
| credentials["auth_code"], | |
| credentials["base_url"] | |
| ) | |
| raise ValueError(f"Unknown provider: {provider}") | |