130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
|
#!/usr/bin/env python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
"""
|
|||
|
错误处理中间件模块
|
|||
|
提供全局异常处理和错误响应格式化
|
|||
|
"""
|
|||
|
|
|||
|
from fastapi import Request, HTTPException
|
|||
|
from fastapi.responses import JSONResponse
|
|||
|
from fastapi.exceptions import RequestValidationError
|
|||
|
import traceback
|
|||
|
|
|||
|
from utils.logger import get_logger
|
|||
|
from routes.common_api import format_response
|
|||
|
from config.error_messages import VALIDATION_ERROR_MESSAGES, HTTP_ERROR_MESSAGES
|
|||
|
from config.settings import settings
|
|||
|
|
|||
|
# 设置日志
|
|||
|
logger = get_logger("middleware.error_handlers")
|
|||
|
|
|||
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|||
|
"""
|
|||
|
处理验证错误,将错误消息转换为中文,并提供更友好的错误提示
|
|||
|
包括显示具体缺失的字段名称
|
|||
|
"""
|
|||
|
errors = exc.errors()
|
|||
|
error_details = []
|
|||
|
missing_fields = []
|
|||
|
|
|||
|
for error in errors:
|
|||
|
error_type = error.get("type", "")
|
|||
|
loc = error.get("loc", [])
|
|||
|
|
|||
|
# 获取完整的字段路径,排除body/query等
|
|||
|
if len(loc) > 1 and loc[0] in ["body", "query", "path", "header"]:
|
|||
|
field_path = ".".join(str(item) for item in loc[1:])
|
|||
|
else:
|
|||
|
field_path = ".".join(str(item) for item in loc)
|
|||
|
|
|||
|
# 获取中文错误消息
|
|||
|
message = VALIDATION_ERROR_MESSAGES.get(error_type, error.get("msg", "验证错误"))
|
|||
|
|
|||
|
# 替换消息中的参数
|
|||
|
context = error.get("ctx", {})
|
|||
|
for key, value in context.items():
|
|||
|
message = message.replace(f"{{{key}}}", str(value))
|
|||
|
|
|||
|
# 收集缺失字段
|
|||
|
if error_type == "missing" or error_type == "value_error.missing":
|
|||
|
missing_fields.append(field_path)
|
|||
|
|
|||
|
error_details.append({
|
|||
|
"field": field_path,
|
|||
|
"message": message,
|
|||
|
"type": error_type
|
|||
|
})
|
|||
|
|
|||
|
# 构建友好的错误响应
|
|||
|
if missing_fields:
|
|||
|
missing_fields_str = ", ".join(missing_fields)
|
|||
|
error_message = f"缺少必填字段: {missing_fields_str}"
|
|||
|
elif error_details:
|
|||
|
# 提取第一个错误的字段和消息
|
|||
|
first_error = error_details[0]
|
|||
|
error_message = f"参数 '{first_error['field']}' 验证失败: {first_error['message']}"
|
|||
|
else:
|
|||
|
error_message = "参数验证失败"
|
|||
|
|
|||
|
# 记录参数验证错误,以便同步到主系统
|
|||
|
logger.warning(f"参数验证失败: {error_message} - 请求路径: {request.url.path}")
|
|||
|
|
|||
|
return JSONResponse(
|
|||
|
status_code=400,
|
|||
|
content={
|
|||
|
"code": 400,
|
|||
|
"message": error_message,
|
|||
|
"data": error_details if len(error_details) > 1 else None
|
|||
|
}
|
|||
|
)
|
|||
|
|
|||
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
|||
|
"""处理HTTP异常,转换为统一的响应格式"""
|
|||
|
status_code = exc.status_code
|
|||
|
# 获取错误消息,优先使用自定义消息,否则使用配置中的错误消息
|
|||
|
message = exc.detail
|
|||
|
if isinstance(message, str) and message == "Not Found":
|
|||
|
message = HTTP_ERROR_MESSAGES.get(status_code, message)
|
|||
|
|
|||
|
# 记录HTTP错误日志,以便同步到主系统
|
|||
|
if status_code >= 500:
|
|||
|
# 5xx错误记录为ERROR级别
|
|||
|
logger.error(f"HTTP {status_code} 错误: {message} - 请求路径: {request.url.path}")
|
|||
|
elif status_code >= 400:
|
|||
|
# 4xx错误记录为WARNING级别
|
|||
|
logger.warning(f"HTTP {status_code} 警告: {message} - 请求路径: {request.url.path}")
|
|||
|
|
|||
|
return JSONResponse(
|
|||
|
status_code=status_code,
|
|||
|
content=format_response(
|
|||
|
code=status_code,
|
|||
|
message=message,
|
|||
|
data=None
|
|||
|
)
|
|||
|
)
|
|||
|
|
|||
|
async def global_exception_handler(request: Request, exc: Exception):
|
|||
|
"""处理所有未捕获的异常"""
|
|||
|
logger.error(f"未捕获异常: {str(exc)}")
|
|||
|
logger.error(traceback.format_exc())
|
|||
|
|
|||
|
return JSONResponse(
|
|||
|
status_code=500,
|
|||
|
content=format_response(
|
|||
|
code=500,
|
|||
|
message="服务器内部错误,请联系管理员",
|
|||
|
data=None if not settings.DEBUG else str(exc)
|
|||
|
)
|
|||
|
)
|
|||
|
|
|||
|
def register_exception_handlers(app):
|
|||
|
"""
|
|||
|
注册所有异常处理器到FastAPI应用
|
|||
|
|
|||
|
Args:
|
|||
|
app: FastAPI应用实例
|
|||
|
"""
|
|||
|
app.exception_handler(RequestValidationError)(validation_exception_handler)
|
|||
|
app.exception_handler(HTTPException)(http_exception_handler)
|
|||
|
app.exception_handler(Exception)(global_exception_handler)
|