You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
68 lines
2.4 KiB
68 lines
2.4 KiB
"""全局中间件模块 - 异常处理与请求日志"""
|
|
|
|
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
|