140 lines
3.6 KiB
Python
140 lines
3.6 KiB
Python
"""
|
|
全局异常处理和错误响应
|
|
"""
|
|
|
|
import logging
|
|
import traceback
|
|
|
|
from fastapi import Request, status
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class APIException(Exception):
|
|
"""API 异常基类"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
status_code: int = status.HTTP_400_BAD_REQUEST,
|
|
error_code: str = "API_ERROR",
|
|
):
|
|
self.message = message
|
|
self.status_code = status_code
|
|
self.error_code = error_code
|
|
super().__init__(message)
|
|
|
|
|
|
class AuthenticationError(APIException):
|
|
"""认证错误"""
|
|
|
|
def __init__(self, message: str = "Authentication failed"):
|
|
super().__init__(
|
|
message=message,
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
error_code="AUTH_ERROR",
|
|
)
|
|
|
|
|
|
class PermissionDeniedError(APIException):
|
|
"""权限拒绝"""
|
|
|
|
def __init__(self, message: str = "Permission denied"):
|
|
super().__init__(
|
|
message=message,
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
error_code="PERMISSION_DENIED",
|
|
)
|
|
|
|
|
|
class ResourceNotFoundError(APIException):
|
|
"""资源不存在"""
|
|
|
|
def __init__(self, resource: str = "Resource"):
|
|
super().__init__(
|
|
message=f"{resource} not found",
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
error_code="NOT_FOUND",
|
|
)
|
|
|
|
|
|
class ValidationError(APIException):
|
|
"""验证错误"""
|
|
|
|
def __init__(self, message: str):
|
|
super().__init__(
|
|
message=message,
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
error_code="VALIDATION_ERROR",
|
|
)
|
|
|
|
|
|
async def api_exception_handler(request: Request, exc: APIException):
|
|
"""API 异常处理"""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": {
|
|
"code": exc.error_code,
|
|
"message": exc.message,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
|
"""HTTP 异常处理"""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": {
|
|
"code": f"HTTP_{exc.status_code}",
|
|
"message": exc.detail,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
"""请求验证异常处理"""
|
|
errors = []
|
|
for error in exc.errors():
|
|
errors.append(
|
|
{
|
|
"field": ".".join(str(loc) for loc in error["loc"]),
|
|
"message": error["msg"],
|
|
"type": error["type"],
|
|
}
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
content={
|
|
"error": {
|
|
"code": "VALIDATION_ERROR",
|
|
"message": "Request validation failed",
|
|
"details": errors,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
async def general_exception_handler(request: Request, exc: Exception):
|
|
"""通用异常处理"""
|
|
logger.error(f"Unhandled exception: {exc}", exc_info=True)
|
|
|
|
# 生产环境不返回详细错误信息
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content={
|
|
"error": {
|
|
"code": "INTERNAL_ERROR",
|
|
"message": "An internal error occurred",
|
|
# "detail": str(exc), # 仅在开发环境启用
|
|
}
|
|
},
|
|
)
|