""" 全局异常体系。 只统一*错误*响应格式 {error_code, message, request_id}。 成功响应直接返回 Pydantic model / FileResponse / 原始结构,不强制包装。 """ from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from loguru import logger class AppError(Exception): """Base application exception.""" def __init__( self, message: str = "内部服务器错误", *, status_code: int = 500, error_code: str = "INTERNAL_ERROR", ): self.message = message self.status_code = status_code self.error_code = error_code super().__init__(message) class NotFoundError(AppError): def __init__(self, message: str = "资源不存在"): super().__init__(message, status_code=404, error_code="NOT_FOUND") class AuthenticationError(AppError): def __init__(self, message: str = "认证失败"): super().__init__(message, status_code=401, error_code="AUTHENTICATION_FAILED") class AuthorizationError(AppError): def __init__(self, message: str = "权限不足"): super().__init__(message, status_code=403, error_code="FORBIDDEN") class ValidationError(AppError): def __init__(self, message: str = "请求参数无效"): super().__init__(message, status_code=422, error_code="VALIDATION_ERROR") class ProviderError(AppError): def __init__(self, message: str = "外部服务异常", *, provider: str = ""): super().__init__(message, status_code=502, error_code="PROVIDER_ERROR") self.provider = provider class QuotaExceededError(AppError): def __init__(self, message: str = "配额已用尽"): super().__init__(message, status_code=429, error_code="QUOTA_EXCEEDED") # ── Exception handler registration ────────────────────────── def _get_request_id(request: Request) -> str: return getattr(request.state, "request_id", "-") def register_exception_handlers(app: FastAPI) -> None: """Register global exception handlers on the FastAPI app.""" @app.exception_handler(AppError) async def app_error_handler(request: Request, exc: AppError): request_id = _get_request_id(request) logger.warning( "AppError: error_code={} message={} request_id={}", exc.error_code, exc.message, request_id, ) return JSONResponse( status_code=exc.status_code, content={ "error_code": exc.error_code, "message": exc.message, "request_id": request_id, }, ) @app.exception_handler(Exception) async def unhandled_error_handler(request: Request, exc: Exception): request_id = _get_request_id(request) logger.exception("Unhandled exception: request_id={}", request_id) return JSONResponse( status_code=500, content={ "error_code": "INTERNAL_ERROR", "message": "服务器内部错误", "request_id": request_id, }, )