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) |