"""全局中间件模块 - 异常处理与请求日志""" import logging import time from fastapi import Request from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware logger = logging.getLogger(__name__) class BusinessError(Exception): """业务逻辑异常基类。""" def __init__(self, message: str, code: str = "BUSINESS_ERROR") -> None: self.message = message self.code = code super().__init__(message) class DataNotFoundError(BusinessError): """数据未找到异常。""" def __init__(self, message: str = "数据不存在", code: str = "DATA_NOT_FOUND") -> None: super().__init__(message=message, code=code) class GlobalExceptionMiddleware(BaseHTTPMiddleware): """全局异常处理中间件 - 捕获 BusinessError / ValueError / Exception。""" async def dispatch(self, request: Request, call_next): start_time = time.time() try: response = await call_next(request) return response except BusinessError as exc: logger.warning("业务异常: [%s] %s", exc.code, exc.message) return JSONResponse( status_code=400, content={"success": False, "message": exc.message, "code": exc.code}, ) except ValueError as exc: logger.warning("参数错误: %s", exc) return JSONResponse( status_code=422, content={"success": False, "message": str(exc), "code": "VALIDATION_ERROR"}, ) except Exception as exc: logger.exception("未预期的服务器错误: %s", exc) return JSONResponse( status_code=500, content={"success": False, "message": "服务器内部错误", "code": "INTERNAL_ERROR"}, ) finally: duration = time.time() - start_time logger.info("%s %s %.3fs", request.method, request.url.path, duration) class RequestLoggingMiddleware(BaseHTTPMiddleware): """请求日志中间件 - 记录每个请求的基本信息。""" async def dispatch(self, request: Request, call_next): logger.info("请求开始: %s %s", request.method, request.url.path) response = await call_next(request) logger.info("请求完成: %s %s -> %s", request.method, request.url.path, response.status_code) return response