mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 21:06:32 +00:00
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码 2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑 3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件 4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
278 lines
9.3 KiB
Python
278 lines
9.3 KiB
Python
import traceback
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, Request, status
|
|
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
|
from pydantic_validation_decorator import FieldValidationError
|
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
|
from starlette.exceptions import HTTPException
|
|
from starlette.responses import JSONResponse
|
|
|
|
from app.common.constant import RET
|
|
from app.common.response import ErrorResponse
|
|
from app.core.logger import logger
|
|
|
|
|
|
class CustomException(Exception):
|
|
"""
|
|
自定义异常基类
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
msg: str = RET.EXCEPTION.msg,
|
|
code: int = RET.EXCEPTION.code,
|
|
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
data: Any | None = None,
|
|
success: bool = False,
|
|
) -> None:
|
|
"""
|
|
初始化异常对象。
|
|
|
|
参数:
|
|
- msg (str): 错误消息。
|
|
- code (int): 业务状态码。
|
|
- status_code (int): HTTP 状态码。
|
|
- data (Any | None): 附加数据。
|
|
- success (bool): 是否成功标记,默认 False。
|
|
|
|
返回:
|
|
- None
|
|
"""
|
|
super().__init__(msg) # 调用父类初始化方法
|
|
self.status_code = status_code
|
|
self.code = code
|
|
self.msg = msg
|
|
self.data = data
|
|
self.success = success
|
|
|
|
def __str__(self) -> str:
|
|
"""返回异常消息
|
|
|
|
返回:
|
|
- str: 异常消息
|
|
"""
|
|
return self.msg
|
|
|
|
|
|
def handle_exception(app: FastAPI) -> None:
|
|
"""
|
|
注册全局异常处理器。
|
|
|
|
参数:
|
|
- app (FastAPI): 应用实例。
|
|
|
|
返回:
|
|
- None
|
|
"""
|
|
|
|
@app.exception_handler(CustomException)
|
|
async def CustomExceptionHandler(request: Request, exc: CustomException) -> JSONResponse:
|
|
"""自定义异常处理器
|
|
|
|
参数:
|
|
request: 请求对象。
|
|
exc: 自定义异常实例。
|
|
|
|
返回:
|
|
JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
# 从 traceback 提取异常抛出的真实位置
|
|
source = "unknown"
|
|
if exc.__traceback__:
|
|
tb = traceback.extract_tb(exc.__traceback__)
|
|
if tb:
|
|
frame = tb[-1]
|
|
source = f"{frame.filename}:{frame.lineno}:{frame.name}"
|
|
|
|
logger.error(
|
|
f"[异常] {request.method} {request.url.path} | "
|
|
f"来源: {source} | "
|
|
f"错误码: {exc.code} | "
|
|
f"错误信息: {exc.msg} | "
|
|
f"详情: {exc.data}"
|
|
)
|
|
return ErrorResponse(
|
|
msg=exc.msg,
|
|
code=exc.code,
|
|
status_code=exc.status_code,
|
|
data=exc.data,
|
|
)
|
|
|
|
@app.exception_handler(HTTPException)
|
|
async def HttpExceptionHandler(request: Request, exc: HTTPException) -> JSONResponse:
|
|
"""
|
|
HTTP异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (HTTPException): HTTP异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
logger.error(
|
|
f"[HTTP异常] {request.method} {request.url.path} | 状态码: {exc.status_code} | 错误信息: {exc.detail}"
|
|
)
|
|
return ErrorResponse(msg=exc.detail, status_code=exc.status_code)
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def ValidationExceptionHandler(
|
|
request: Request, exc: RequestValidationError
|
|
) -> JSONResponse:
|
|
"""
|
|
请求参数验证异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (RequestValidationError): 请求参数验证异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
error_mapping = {
|
|
"Field required": "请求失败,缺少必填项!",
|
|
"value is not a valid list": "类型错误,提交参数应该为列表!",
|
|
"value is not a valid int": "类型错误,提交参数应该为整数!",
|
|
"value could not be parsed to a boolean": "类型错误,提交参数应该为布尔值!",
|
|
"Input should be a valid list": "类型错误,输入应该是一个有效的列表!",
|
|
}
|
|
raw_msg = exc.errors()[0].get("msg")
|
|
msg = error_mapping.get(raw_msg, raw_msg)
|
|
# 去掉Pydantic默认的前缀“Value error”, 仅保留具体提示内容
|
|
if isinstance(msg, str) and msg.startswith("Value error"):
|
|
msg = (
|
|
msg.split(",", 1)[1].strip()
|
|
if "," in msg
|
|
else msg.replace("Value error", "").strip()
|
|
)
|
|
logger.error(
|
|
f"[参数验证异常] {request.method} {request.url.path} | 错误信息: {msg} | 原始错误: {exc.errors()}"
|
|
)
|
|
return ErrorResponse(
|
|
msg=str(msg),
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
data=exc.body,
|
|
)
|
|
|
|
@app.exception_handler(ResponseValidationError)
|
|
async def ResponseValidationHandle(
|
|
request: Request, exc: ResponseValidationError
|
|
) -> JSONResponse:
|
|
"""
|
|
响应参数验证异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (ResponseValidationError): 响应参数验证异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
logger.error(
|
|
f"[响应验证异常] {request.method} {request.url.path} | 错误信息: 响应数据格式错误 | 详情: {exc.errors()}"
|
|
)
|
|
return ErrorResponse(
|
|
msg="服务器响应格式错误",
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
data=exc.body,
|
|
)
|
|
|
|
@app.exception_handler(SQLAlchemyError)
|
|
async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse:
|
|
"""
|
|
数据库异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (SQLAlchemyError): 数据库异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
exc_type = type(exc).__name__
|
|
logger.error(
|
|
f"[数据库异常] {request.method} {request.url.path} | 错误类型: {exc_type} | 错误详情: {exc!s}"
|
|
)
|
|
|
|
# 完整性约束(唯一键冲突 / 外键引用 / NOT NULL 等)
|
|
if isinstance(exc, IntegrityError):
|
|
detail = str(exc.orig) if exc.orig else str(exc)
|
|
msg = "数据已存在或违反完整性约束"
|
|
if "Duplicate entry" in detail:
|
|
msg = "数据重复,请检查唯一字段"
|
|
elif "foreign key constraint" in detail:
|
|
msg = "存在关联数据,无法删除"
|
|
elif "cannot be null" in detail:
|
|
msg = "必填字段缺失"
|
|
return ErrorResponse(msg=msg, status_code=status.HTTP_409_CONFLICT, data=detail)
|
|
|
|
# 数据库连接类异常
|
|
if "connect" in str(exc).lower() or "connection" in str(exc).lower():
|
|
return ErrorResponse(
|
|
msg="数据库连接失败",
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
data=exc_type,
|
|
)
|
|
|
|
return ErrorResponse(
|
|
msg=f"数据库操作失败: {exc_type}",
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
data=str(exc),
|
|
)
|
|
|
|
@app.exception_handler(ValueError)
|
|
async def ValueExceptionHandler(request: Request, exc: ValueError) -> JSONResponse:
|
|
"""
|
|
值异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (ValueError): 值异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
logger.error(f"[值异常] {request.method} {request.url.path} | 错误信息: {exc!s}")
|
|
return ErrorResponse(msg=str(exc), status_code=status.HTTP_400_BAD_REQUEST)
|
|
|
|
@app.exception_handler(FieldValidationError)
|
|
async def FieldValidationExceptionHandler(
|
|
request: Request, exc: FieldValidationError
|
|
) -> JSONResponse:
|
|
"""
|
|
字段验证异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (FieldValidationError): 字段验证异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
logger.error(f"[字段验证异常] {request.method} {request.url.path} | 错误信息: {exc.message}")
|
|
return ErrorResponse(msg=exc.message, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
|
|
|
@app.exception_handler(Exception)
|
|
async def AllExceptionHandler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""
|
|
全局异常处理器
|
|
|
|
参数:
|
|
- request (Request): 请求对象。
|
|
- exc (Exception): 异常实例。
|
|
|
|
返回:
|
|
- JSONResponse: 包含错误信息的 JSON 响应。
|
|
"""
|
|
exc_type = type(exc).__name__
|
|
logger.error(
|
|
f"[未捕获异常] {request.method} {request.url.path} | 错误类型: {exc_type} | 错误详情: {exc!s}"
|
|
)
|
|
# 对于未捕获的异常,返回通用错误信息
|
|
return ErrorResponse(
|
|
msg="服务器内部错误",
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
data=None,
|
|
)
|