response.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. from collections.abc import Mapping
  2. from datetime import date, datetime, time
  3. from typing import Any, Generic
  4. from fastapi import status
  5. from fastapi.encoders import jsonable_encoder
  6. from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
  7. from pydantic import BaseModel, Field
  8. from pydantic.types import T
  9. from starlette.background import BackgroundTask
  10. from app.common.constant import DATE_DISPLAY_FMT, DATETIME_DISPLAY_FMT, RET, TIME_DISPLAY_FMT
  11. # 裸 datetime/date/time(未走 Pydantic 的 dict 等)JSON 输出与 constant 中展示格式一致
  12. _JSON_DATETIME_CUSTOM_ENCODER: dict[type[Any], Any] = {
  13. datetime: lambda d: d.strftime(DATETIME_DISPLAY_FMT),
  14. date: lambda d: d.strftime(DATE_DISPLAY_FMT),
  15. time: lambda t: t.strftime(TIME_DISPLAY_FMT),
  16. }
  17. def jsonable_response_content(content: Any) -> Any:
  18. return jsonable_encoder(content, custom_encoder=_JSON_DATETIME_CUSTOM_ENCODER)
  19. class ResponseSchema(BaseModel, Generic[T]):
  20. """响应模型"""
  21. code: int = Field(default=RET.OK.code, description="业务状态码")
  22. msg: str = Field(default=RET.OK.msg, description="响应消息")
  23. data: T | None = Field(default=None, description="响应数据")
  24. status_code: int = Field(default=status.HTTP_200_OK, description="HTTP状态码")
  25. success: bool = Field(default=True, description="操作是否成功")
  26. class SuccessResponse(JSONResponse):
  27. """成功响应类"""
  28. def __init__(
  29. self,
  30. data: Any | None = None,
  31. msg: str = RET.OK.msg,
  32. code: int = RET.OK.code,
  33. status_code: int = status.HTTP_200_OK,
  34. success: bool = True,
  35. ) -> None:
  36. """
  37. 初始化成功响应类
  38. 参数:
  39. - data (Any | None): 响应数据。
  40. - msg (str): 响应消息。
  41. - code (int): 业务状态码。
  42. - status_code (int): HTTP 状态码。
  43. - success (bool): 操作是否成功。
  44. 返回:
  45. - None
  46. """
  47. content = ResponseSchema(
  48. code=code,
  49. msg=msg,
  50. data=data,
  51. status_code=status_code,
  52. success=success,
  53. ).model_dump()
  54. super().__init__(content=jsonable_response_content(content), status_code=status_code)
  55. class ErrorResponse(JSONResponse):
  56. """错误响应类"""
  57. def __init__(
  58. self,
  59. data: Any = None,
  60. msg: str = RET.ERROR.msg,
  61. code: int = RET.ERROR.code,
  62. status_code: int = status.HTTP_400_BAD_REQUEST,
  63. success: bool = False,
  64. ) -> None:
  65. """
  66. 初始化错误响应类
  67. 参数:
  68. - data (Any): 响应数据。
  69. - msg (str): 响应消息。
  70. - code (int): 业务状态码。
  71. - status_code (int): HTTP 状态码。
  72. - success (bool): 操作是否成功。
  73. 返回:
  74. - None
  75. """
  76. content = ResponseSchema(
  77. code=code,
  78. msg=msg,
  79. data=data,
  80. status_code=status_code,
  81. success=success,
  82. ).model_dump()
  83. super().__init__(content=jsonable_response_content(content), status_code=status_code)
  84. class StreamResponse(StreamingResponse):
  85. """流式响应类"""
  86. def __init__(
  87. self,
  88. data: Any = None,
  89. status_code: int = status.HTTP_200_OK,
  90. headers: Mapping[str, str] | None = None,
  91. media_type: str | None = None,
  92. background: BackgroundTask | None = None,
  93. ) -> None:
  94. """
  95. 初始化流式响应类
  96. 参数:
  97. - data (Any): 响应数据。
  98. - status_code (int): HTTP 状态码。
  99. - headers (Mapping[str, str] | None): 响应头。
  100. - media_type (str | None): 媒体类型。
  101. - background (BackgroundTask | None): 后台任务。
  102. 返回:
  103. - None
  104. """
  105. super().__init__(
  106. content=data,
  107. status_code=status_code,
  108. media_type=media_type, # 文件类型
  109. headers=headers, # 文件名
  110. background=background, # 文件大小
  111. )
  112. class UploadFileResponse(FileResponse):
  113. """
  114. 文件响应
  115. """
  116. def __init__(
  117. self,
  118. file_path: str,
  119. filename: str,
  120. media_type: str = "application/octet-stream",
  121. headers: Mapping[str, str] | None = None,
  122. background: BackgroundTask | None = None,
  123. status_code: int = 200,
  124. ) -> None:
  125. """
  126. 初始化文件响应类
  127. 参数:
  128. - file_path (str): 文件路径。
  129. - filename (str): 文件名。
  130. - media_type (str): 文件类型。
  131. - headers (Mapping[str, str] | None): 响应头。
  132. - background (BackgroundTask | None): 后台任务。
  133. - status_code (int): HTTP 状态码。
  134. 返回:
  135. - None
  136. """
  137. super().__init__(
  138. path=file_path,
  139. status_code=status_code,
  140. headers=headers,
  141. media_type=media_type,
  142. background=background,
  143. filename=filename,
  144. stat_result=None,
  145. method=None,
  146. content_disposition_type="attachment",
  147. )