""" 自定义异常类 ============ 分层异常体系: - AppException: 业务层错误(参数校验、权限、资源不存在等) - PlatformError: 第三方平台调用错误(网络、限流、认证、服务异常等) Router 层只处理这两类异常,其余全部兜底为 500。 """ from fastapi import HTTPException, status # ═══════════════════════════════════════════════════════════════ # 业务层异常(AppException 体系) # ═══════════════════════════════════════════════════════════════ class AppException(HTTPException): """应用基础异常""" def __init__( self, status_code: int, message: str = "操作失败", detail: dict | None = None, ): super().__init__(status_code=status_code, detail=detail or {}) self.message = message class NotFoundException(AppException): """资源不存在""" def __init__(self, message: str = "资源不存在"): super().__init__( status_code=status.HTTP_404_NOT_FOUND, message=message, ) class ValidationException(AppException): """参数验证失败""" def __init__(self, message: str = "参数验证失败"): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, message=message, ) class UnauthorizedException(AppException): """未授权""" def __init__(self, message: str = "未授权,请先登录"): super().__init__( status_code=status.HTTP_401_UNAUTHORIZED, message=message, ) class ForbiddenException(AppException): """禁止访问""" def __init__(self, message: str = "无权访问该资源"): super().__init__( status_code=status.HTTP_403_FORBIDDEN, message=message, ) class BusinessException(AppException): """业务逻辑错误""" def __init__(self, message: str = "业务操作失败"): super().__init__( status_code=status.HTTP_400_BAD_REQUEST, message=message, ) class ModelUnavailableException(AppException): """AI 模型不可用""" def __init__(self, message: str = "AI 模型服务暂时不可用"): super().__init__( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, message=message, ) class TaskFailedException(AppException): """异步任务执行失败""" def __init__(self, message: str = "任务执行失败"): super().__init__( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, message=message, ) # ═══════════════════════════════════════════════════════════════ # 第三方平台异常(PlatformError 体系) # ═══════════════════════════════════════════════════════════════ class PlatformErrorType: """第三方错误类型标准枚举 所有 Adapter 必须将供应商异常映射为以下标准类型之一, 确保前端和网关能够统一处理。 """ RATE_LIMIT = "rate_limit" # 429,可重试 AUTH_FAILED = "auth_failed" # 401/403,不可重试 TIMEOUT = "timeout" # 连接/读取超时,可重试 SERVER_ERROR = "server_error" # 第三方 5xx,可重试 BAD_REQUEST = "bad_request" # 参数错误,不可重试 QUOTA_EXHAUSTED = "quota_exhausted" # 额度用完,不可重试(或延迟重试) NOT_FOUND = "not_found" # 资源不存在,不可重试 UNKNOWN = "unknown" # 兜底 class PlatformError(Exception): """第三方平台调用失败的唯一异常类 Router 层只需 except PlatformError,即可返回标准 HTTP 状态码。 所有 app/services/ 和 app/ai/ 下的代码,对外抛出的异常必须是此类。 Example: raise PlatformError( "Vidu 限流", platform="vidu", retryable=True, error_type=PlatformErrorType.RATE_LIMIT, status_code=429, ) """ def __init__( self, message: str, *, platform: str, retryable: bool = False, error_type: str = PlatformErrorType.UNKNOWN, status_code: int | None = None, ): super().__init__(message) self.platform = platform self.retryable = retryable self.error_type = error_type self.status_code = status_code def to_http_status(self) -> int: """根据 error_type 和 retryable 返回标准 HTTP 状态码""" mapping = { PlatformErrorType.RATE_LIMIT: 429, PlatformErrorType.QUOTA_EXHAUSTED: 429, PlatformErrorType.TIMEOUT: 504, PlatformErrorType.AUTH_FAILED: 401, PlatformErrorType.BAD_REQUEST: 400, PlatformErrorType.NOT_FOUND: 404, } if self.error_type in mapping: return mapping[self.error_type] return 502 if self.retryable else 400