2026-01-19 00:09:36 +08:00
|
|
|
|
"""
|
|
|
|
|
|
全局错误处理器
|
|
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
from fastapi import Request, status
|
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
from fastapi.exceptions import RequestValidationError
|
|
|
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
|
|
from app.core.exceptions import BaseAPIException
|
2026-04-09 21:58:53 +08:00
|
|
|
|
from app.core.config import settings
|
2026-01-19 00:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
|
|
|
|
"""处理验证错误"""
|
|
|
|
|
|
errors = []
|
|
|
|
|
|
for error in exc.errors():
|
|
|
|
|
|
field = ".".join(str(loc) for loc in error.get("loc", []))
|
|
|
|
|
|
errors.append({
|
|
|
|
|
|
"field": field,
|
|
|
|
|
|
"message": error.get("msg"),
|
|
|
|
|
|
"type": error.get("type")
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
logger.warning(f"验证错误: {errors}")
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
|
|
|
|
content={
|
|
|
|
|
|
"error": "VALIDATION_ERROR",
|
|
|
|
|
|
"message": "请求参数验证失败",
|
|
|
|
|
|
"details": errors
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def api_exception_handler(request: Request, exc: BaseAPIException):
|
|
|
|
|
|
"""处理自定义API异常"""
|
|
|
|
|
|
logger.error(f"API异常: {exc.detail} (错误码: {exc.error_code})")
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
status_code=exc.status_code,
|
|
|
|
|
|
content={
|
|
|
|
|
|
"error": exc.error_code or "API_ERROR",
|
|
|
|
|
|
"message": exc.detail
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def sqlalchemy_exception_handler(request: Request, exc: SQLAlchemyError):
|
|
|
|
|
|
"""处理数据库错误"""
|
2026-04-09 21:58:53 +08:00
|
|
|
|
orig = getattr(exc, "orig", None)
|
|
|
|
|
|
detail = str(orig) if orig is not None else str(exc)
|
|
|
|
|
|
logger.error(f"数据库错误: {detail}", exc_info=True)
|
|
|
|
|
|
|
|
|
|
|
|
user_msg = "数据库操作失败,请稍后重试"
|
|
|
|
|
|
# MySQL 1054 / 常见 DDL 滞后:模型已增列但库未 alembic upgrade
|
|
|
|
|
|
if "Unknown column" in detail or "(1054," in detail or "1054" in detail:
|
|
|
|
|
|
user_msg = (
|
|
|
|
|
|
"数据库表结构与当前代码不一致(常见为缺少 executions 新列等)。"
|
|
|
|
|
|
"请在 backend 目录执行:alembic upgrade head,然后重启 API 与 Celery。"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
payload: dict = {
|
|
|
|
|
|
"error": "DATABASE_ERROR",
|
|
|
|
|
|
"message": user_msg,
|
|
|
|
|
|
}
|
|
|
|
|
|
if settings.DEBUG:
|
|
|
|
|
|
payload["detail"] = detail[:4000]
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
content=payload,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def general_exception_handler(request: Request, exc: Exception):
|
|
|
|
|
|
"""处理通用异常"""
|
|
|
|
|
|
logger.error(f"未处理的异常: {str(exc)}", exc_info=True)
|
|
|
|
|
|
logger.error(f"异常堆栈: {traceback.format_exc()}")
|
2026-04-09 21:58:53 +08:00
|
|
|
|
|
|
|
|
|
|
payload: dict = {
|
|
|
|
|
|
"error": "INTERNAL_ERROR",
|
|
|
|
|
|
"message": "服务器内部错误,请稍后重试",
|
|
|
|
|
|
}
|
|
|
|
|
|
if settings.DEBUG:
|
|
|
|
|
|
payload["detail"] = str(exc)[:4000]
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
2026-04-09 21:58:53 +08:00
|
|
|
|
content=payload,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
)
|