exception_handler.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """
  2. 全局异常处理器
  3. 类似Java的@ControllerAdvice,统一处理所有异常
  4. """
  5. from fastapi import Request, status
  6. from fastapi.responses import JSONResponse
  7. from fastapi.exceptions import RequestValidationError
  8. from starlette.exceptions import HTTPException as StarletteHTTPException
  9. from app.core.exceptions import (
  10. BaseAPIException,
  11. BusinessException,
  12. UnauthorizedException,
  13. ForbiddenException,
  14. NotFoundException,
  15. ValidationException,
  16. InternalServerException,
  17. StatusCode
  18. )
  19. from app.utils.logger_utils import logger
  20. async def base_api_exception_handler(request: Request, exc: BaseAPIException) -> JSONResponse:
  21. """
  22. 处理自定义API异常
  23. 返回统一的错误响应格式
  24. """
  25. logger.warning(
  26. f"API异常: {exc.detail} | "
  27. f"Status: {exc.status_code} | "
  28. f"Code: {exc.code} | "
  29. f"Path: {request.url.path}"
  30. )
  31. return JSONResponse(
  32. status_code=exc.status_code,
  33. content={
  34. "code": exc.code,
  35. "message": exc.detail,
  36. "data": None
  37. }
  38. )
  39. async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
  40. """
  41. 处理FastAPI的HTTPException
  42. 转换为统一的响应格式
  43. """
  44. logger.warning(
  45. f"HTTP异常: {exc.detail} | "
  46. f"Status: {exc.status_code} | "
  47. f"Path: {request.url.path}"
  48. )
  49. # 根据状态码确定错误码
  50. error_code = exc.status_code
  51. if exc.status_code == 401:
  52. error_code = StatusCode.UNAUTHORIZED
  53. elif exc.status_code == 403:
  54. error_code = StatusCode.FORBIDDEN
  55. elif exc.status_code == 404:
  56. error_code = StatusCode.NOT_FOUND
  57. elif exc.status_code >= 500:
  58. error_code = StatusCode.INTERNAL_SERVER_ERROR
  59. return JSONResponse(
  60. status_code=exc.status_code,
  61. content={
  62. "code": error_code,
  63. "message": exc.detail,
  64. "data": None
  65. }
  66. )
  67. async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
  68. """
  69. 处理请求参数验证异常
  70. """
  71. errors = exc.errors()
  72. error_messages = []
  73. for error in errors:
  74. field = ".".join(str(loc) for loc in error.get("loc", []))
  75. msg = error.get("msg", "参数验证失败")
  76. error_messages.append(f"{field}: {msg}")
  77. detail = "; ".join(error_messages) if error_messages else "参数验证失败"
  78. logger.warning(
  79. f"参数验证失败: {detail} | "
  80. f"Path: {request.url.path}"
  81. )
  82. return JSONResponse(
  83. status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
  84. content={
  85. "code": StatusCode.BAD_REQUEST,
  86. "message": detail,
  87. "data": None
  88. }
  89. )
  90. async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
  91. """
  92. 处理所有未捕获的异常
  93. 这是最后的异常处理兜底
  94. """
  95. logger.error(
  96. f"未处理的异常: {str(exc)} | "
  97. f"Type: {type(exc).__name__} | "
  98. f"Path: {request.url.path}",
  99. exc_info=True
  100. )
  101. # 检查异常消息中是否包含特定业务错误信息
  102. error_message = str(exc)
  103. # 权限相关错误
  104. if "无该货主权限" in error_message or "无权限" in error_message:
  105. return JSONResponse(
  106. status_code=403,
  107. content={
  108. "code": StatusCode.FORBIDDEN,
  109. "message": error_message if error_message else "无该货主权限!",
  110. "data": None
  111. }
  112. )
  113. # Token相关错误
  114. if "登录已过期" in error_message or "token" in error_message.lower() or "登录" in error_message:
  115. return JSONResponse(
  116. status_code=401,
  117. content={
  118. "code": StatusCode.INVALID_TOKEN,
  119. "message": error_message if error_message else "无效的登录信息",
  120. "data": None
  121. }
  122. )
  123. # 资源不存在
  124. if "不存在" in error_message or "not found" in error_message.lower():
  125. return JSONResponse(
  126. status_code=404,
  127. content={
  128. "code": StatusCode.NOT_FOUND,
  129. "message": error_message if error_message else "资源不存在",
  130. "data": None
  131. }
  132. )
  133. # 默认返回500错误
  134. return JSONResponse(
  135. status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  136. content={
  137. "code": StatusCode.INTERNAL_SERVER_ERROR,
  138. "message": error_message if error_message else "服务器内部错误",
  139. "data": None
  140. }
  141. )
  142. def register_exception_handlers(app):
  143. """
  144. 注册所有异常处理器到FastAPI应用
  145. """
  146. # 自定义异常处理器(优先级最高)
  147. app.add_exception_handler(BaseAPIException, base_api_exception_handler)
  148. app.add_exception_handler(BusinessException, base_api_exception_handler)
  149. app.add_exception_handler(UnauthorizedException, base_api_exception_handler)
  150. app.add_exception_handler(ForbiddenException, base_api_exception_handler)
  151. app.add_exception_handler(NotFoundException, base_api_exception_handler)
  152. app.add_exception_handler(ValidationException, base_api_exception_handler)
  153. app.add_exception_handler(InternalServerException, base_api_exception_handler)
  154. # FastAPI的HTTPException
  155. app.add_exception_handler(StarletteHTTPException, http_exception_handler)
  156. # 请求验证异常
  157. app.add_exception_handler(RequestValidationError, validation_exception_handler)
  158. # 通用异常处理器(兜底,必须最后注册)
  159. app.add_exception_handler(Exception, general_exception_handler)