serializer.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. # -*- coding: utf-8 -*-
  2. # @Author : YY
  3. import dataclasses
  4. from datetime import date
  5. import decimal
  6. import uuid
  7. import typing as t
  8. from flask import Response
  9. from flask.json.provider import DefaultJSONProvider
  10. from werkzeug.exceptions import HTTPException, default_exceptions
  11. from werkzeug.http import http_date
  12. from ruoyi_common.base.model import AjaxResponse
  13. from ruoyi_common.constant import HttpStatus
  14. from ruoyi_common.utils.base import UtilException
  15. WSGIEnvironment: t.TypeAlias = dict[str, t.Any]
  16. def _update_exceptions():
  17. """
  18. 更新异常
  19. """
  20. for code in default_exceptions.keys():
  21. exc = default_exceptions[code]
  22. if isinstance(exc, HTTPException):
  23. new_exc = HttpException.from_http_exception(exc)
  24. default_exceptions[code] = new_exc
  25. else:
  26. continue
  27. _update_exceptions()
  28. del _update_exceptions
  29. class HttpException(HTTPException):
  30. code: int | None = None
  31. description: str | None = None
  32. def __init__(
  33. self,
  34. description: str | None = None,
  35. response: Response | None = None,
  36. ) -> None:
  37. super().__init__()
  38. if description is not None:
  39. self.description = description
  40. self.response = response
  41. @classmethod
  42. def from_http_exception(cls, exc: HTTPException) -> "HttpException":
  43. """
  44. 从HTTPException转换为HttpException
  45. Args:
  46. exc (HTTPException): werkezeug的HTTPException
  47. Returns:
  48. HttpException: HttpException
  49. """
  50. error = cls(description=exc.description, response=exc.response)
  51. error.code = exc.code
  52. return error
  53. @property
  54. def name(self) -> str:
  55. """
  56. 状态名称
  57. Returns:
  58. str: 状态名称
  59. """
  60. from werkzeug.http import HTTP_STATUS_CODES
  61. return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore
  62. def get_description(
  63. self,
  64. environ: WSGIEnvironment | None = None,
  65. scope: dict[str, t.Any] | None = None,
  66. ) -> str:
  67. """
  68. 异常描述
  69. Args:
  70. environ (WSGIEnvironment, optional): 环境变量. Defaults to None.
  71. scope (dict[str, t.Any], optional): 作用域. Defaults to None.
  72. Returns:
  73. str: 异常描述
  74. """
  75. return self.description or ""
  76. def get_body(
  77. self,
  78. environ: WSGIEnvironment | None = None,
  79. scope: dict[str, t.Any] | None = None,
  80. ) -> str:
  81. """
  82. 异常响应体
  83. Args:
  84. environ (WSGIEnvironment, optional): 环境变量. Defaults to None.
  85. scope (dict[str, t.Any], optional): 作用域. Defaults to None.
  86. Returns:
  87. str: 异常响应体
  88. """
  89. ajax_resposne = AjaxResponse.from_error(msg=self.description)
  90. ajax_resposne.code = self.code
  91. return ajax_resposne.model_dump_json(
  92. exclude_unset=True,
  93. exclude_none=True,
  94. )
  95. def get_headers(
  96. self,
  97. environ: WSGIEnvironment | None = None,
  98. scope: dict[str, t.Any] | None = None,
  99. ) -> list[tuple[str, str]]:
  100. """
  101. 异常请求头
  102. Args:
  103. environ (WSGIEnvironment, optional): 环境变量. Defaults to None.
  104. scope (dict[str, t.Any], optional): 作用域. Defaults to None.
  105. Returns:
  106. list[tuple[str, str]]: 异常请求头
  107. """
  108. return [("Content-Type", "application/json")]
  109. def json_default(obj):
  110. """
  111. 转化成可序列化对象
  112. Args:
  113. obj : 待序列化对象
  114. Returns:
  115. _type_: 可序列化对象
  116. """
  117. if isinstance(obj, date):
  118. return http_date(obj)
  119. if isinstance(obj, decimal.Decimal):
  120. return str(obj)
  121. if isinstance(obj, uuid.UUID):
  122. return obj.hex
  123. if dataclasses and dataclasses.is_dataclass(obj):
  124. return dataclasses.asdict(obj)
  125. if isinstance(obj, AjaxResponse):
  126. return obj.model_dump_json()
  127. if hasattr(obj, "__html__"):
  128. return str(obj.__html__())
  129. raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
  130. class JsonProvider(DefaultJSONProvider):
  131. """
  132. 自定义json序列化
  133. Args:
  134. DefaultJSONProvider: 默认flask的json序列化
  135. """
  136. default = staticmethod(json_default)
  137. def handle_http_exception(error: HTTPException) -> Response:
  138. """
  139. 处理http异常
  140. Args:
  141. error (HttpException): http异常
  142. Returns:
  143. ResponseReturnValue: 响应体
  144. """
  145. if not isinstance(error, HttpException):
  146. error = HttpException.from_http_exception(error)
  147. return error.get_response()
  148. def handle_util_exception(error: UtilException) -> Response:
  149. """
  150. 处理业务工具类异常,保持和若依Java版一致的json结构
  151. 注意:HTTP状态码始终返回200,业务状态码放在响应体的code字段中
  152. 这样前端可以正确读取msg字段,而不是显示"接口XXX异常"
  153. """
  154. status = getattr(error, "status", HttpStatus.ERROR)
  155. ajax_response = AjaxResponse.from_error(msg=str(error))
  156. ajax_response.code = status
  157. response = Response(
  158. response=ajax_response.model_dump_json(
  159. exclude_unset=True,
  160. exclude_none=True,
  161. ),
  162. status=HttpStatus.SUCCESS, # HTTP状态码始终返回200,业务状态码在响应体的code中
  163. mimetype="application/json"
  164. )
  165. return response