|
|
# -*- coding: utf-8 -*-
|
|
|
"""
|
|
|
===================================
|
|
|
全局异常处理中间件
|
|
|
===================================
|
|
|
|
|
|
职责:
|
|
|
1. 捕获未处理的异常
|
|
|
2. 统一错误响应格式
|
|
|
3. 记录错误日志
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
import traceback
|
|
|
from typing import Callable
|
|
|
|
|
|
from fastapi import Request, Response
|
|
|
from fastapi.responses import JSONResponse
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
|
|
|
"""
|
|
|
全局异常处理中间件
|
|
|
|
|
|
捕获所有未处理的异常,返回统一格式的错误响应
|
|
|
"""
|
|
|
|
|
|
async def dispatch(
|
|
|
self,
|
|
|
request: Request,
|
|
|
call_next: Callable
|
|
|
) -> Response:
|
|
|
"""
|
|
|
处理请求,捕获异常
|
|
|
|
|
|
Args:
|
|
|
request: 请求对象
|
|
|
call_next: 下一个处理器
|
|
|
|
|
|
Returns:
|
|
|
Response: 响应对象
|
|
|
"""
|
|
|
try:
|
|
|
response = await call_next(request)
|
|
|
return response
|
|
|
|
|
|
except Exception as e:
|
|
|
# 记录错误日志
|
|
|
logger.error(
|
|
|
f"未处理的异常: {e}\n"
|
|
|
f"请求路径: {request.url.path}\n"
|
|
|
f"请求方法: {request.method}\n"
|
|
|
f"堆栈: {traceback.format_exc()}"
|
|
|
)
|
|
|
|
|
|
# 返回统一格式的错误响应
|
|
|
return JSONResponse(
|
|
|
status_code=500,
|
|
|
content={
|
|
|
"error": "internal_error",
|
|
|
"message": "服务器内部错误,请稍后重试",
|
|
|
"detail": str(e) if logger.isEnabledFor(logging.DEBUG) else None
|
|
|
}
|
|
|
)
|
|
|
|
|
|
|
|
|
def add_error_handlers(app) -> None:
|
|
|
"""
|
|
|
添加全局异常处理器
|
|
|
|
|
|
为 FastAPI 应用添加各类异常的处理器
|
|
|
|
|
|
Args:
|
|
|
app: FastAPI 应用实例
|
|
|
"""
|
|
|
from fastapi import HTTPException
|
|
|
from fastapi.exceptions import RequestValidationError
|
|
|
|
|
|
@app.exception_handler(HTTPException)
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
|
|
"""处理 HTTP 异常"""
|
|
|
# 如果 detail 已经是 ErrorResponse 格式的 dict,直接使用
|
|
|
if isinstance(exc.detail, dict) and "error" in exc.detail and "message" in exc.detail:
|
|
|
return JSONResponse(
|
|
|
status_code=exc.status_code,
|
|
|
content=exc.detail
|
|
|
)
|
|
|
# 否则将 detail 包装成 ErrorResponse 格式
|
|
|
return JSONResponse(
|
|
|
status_code=exc.status_code,
|
|
|
content={
|
|
|
"error": "http_error",
|
|
|
"message": str(exc.detail) if exc.detail else "HTTP Error",
|
|
|
"detail": None
|
|
|
}
|
|
|
)
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
|
"""处理请求验证异常"""
|
|
|
return JSONResponse(
|
|
|
status_code=422,
|
|
|
content={
|
|
|
"error": "validation_error",
|
|
|
"message": "请求参数验证失败",
|
|
|
"detail": exc.errors()
|
|
|
}
|
|
|
)
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
|
async def general_exception_handler(request: Request, exc: Exception):
|
|
|
"""处理通用异常"""
|
|
|
logger.error(
|
|
|
f"未处理的异常: {exc}\n"
|
|
|
f"请求路径: {request.url.path}\n"
|
|
|
f"堆栈: {traceback.format_exc()}"
|
|
|
)
|
|
|
return JSONResponse(
|
|
|
status_code=500,
|
|
|
content={
|
|
|
"error": "internal_error",
|
|
|
"message": "服务器内部错误",
|
|
|
"detail": None
|
|
|
}
|
|
|
)
|