response.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """统一响应类与业务错误码定义。
  2. 所有接口响应统一使用 `{code, data, msg}` 信封格式:
  3. - code: 0 表示成功;其他值代表不同的失败类型
  4. - data: 业务数据(成功时)或 None(失败时)
  5. - msg: 描述信息
  6. 错误码使用 `(code, default_msg)` 的元组定义,code 和默认提示一一对应。
  7. """
  8. from __future__ import annotations
  9. from enum import Enum
  10. from typing import Any, Generic, TypeVar
  11. from fastapi.responses import JSONResponse
  12. from pydantic import BaseModel, Field
  13. T = TypeVar("T")
  14. class ResponseCode(Enum):
  15. """业务错误码枚举:每个成员都是 (code, default_msg) 元组。
  16. 分段规则:
  17. 0 成功
  18. 1000-1999 通用错误(参数、请求格式等)
  19. 2000-2999 业务逻辑错误(缓存未就绪、资源不存在等)
  20. 3000-3999 外部依赖错误(Zendesk、上游服务等)
  21. 9000-9999 服务器内部错误
  22. 用法:
  23. ResponseCode.SUCCESS.code # -> 0
  24. ResponseCode.SUCCESS.msg # -> "成功"
  25. """
  26. # 成功
  27. SUCCESS = (0, "成功")
  28. # 通用错误 1xxx
  29. PARAM_INVALID = (1001, "参数验证失败")
  30. REQUEST_INVALID = (1002, "请求格式不正确")
  31. METHOD_NOT_ALLOWED = (1003, "请求方法不允许")
  32. NOT_FOUND = (1004, "资源不存在")
  33. # 业务错误 2xxx
  34. CACHE_NOT_READY = (2001, "FAQ 缓存未就绪,请稍后重试")
  35. RESOURCE_NOT_FOUND = (2002, "业务资源不存在")
  36. # 外部依赖错误 3xxx
  37. UPSTREAM_ERROR = (3001, "上游服务调用失败")
  38. UPSTREAM_TIMEOUT = (3002, "上游服务响应超时")
  39. # 服务器错误 9xxx
  40. INTERNAL_ERROR = (9999, "服务器内部错误,请稍后重试")
  41. def __init__(self, code: int, msg: str) -> None:
  42. self.code = code
  43. self.msg = msg
  44. def __repr__(self) -> str: # pragma: no cover
  45. return f"<{self.__class__.__name__}.{self.name}: code={self.code} msg={self.msg!r}>"
  46. class ApiResponse(BaseModel, Generic[T]):
  47. """统一 API 响应模型。
  48. 用于在路由 `response_model` 中声明,让 OpenAPI 文档展示标准结构。
  49. 用法示例:
  50. @router.get("/search", response_model=ApiResponse[SearchData])
  51. async def search(...) -> ApiResponse[SearchData]:
  52. return ApiResponse.ok(data=SearchData(...))
  53. # 失败响应(自动使用错误码对应的默认 msg)
  54. return ApiResponse.fail(ResponseCode.PARAM_INVALID)
  55. # 失败响应(覆盖默认 msg)
  56. return ApiResponse.fail(ResponseCode.UPSTREAM_ERROR, msg="Zendesk 限流")
  57. """
  58. code: int = Field(default=0, description="业务码,0=成功,非0=失败")
  59. data: T | None = Field(default=None, description="业务数据,失败时为 null")
  60. msg: str = Field(default="成功", description="描述信息")
  61. @classmethod
  62. def ok(
  63. cls,
  64. data: T | None = None,
  65. msg: str | None = None,
  66. ) -> "ApiResponse[T]":
  67. """构造成功响应。msg 为空时使用 SUCCESS 默认提示。"""
  68. return cls(
  69. code=ResponseCode.SUCCESS.code,
  70. data=data,
  71. msg=msg if msg is not None else ResponseCode.SUCCESS.msg,
  72. )
  73. @classmethod
  74. def fail(
  75. cls,
  76. code: ResponseCode,
  77. msg: str | None = None,
  78. data: T | None = None,
  79. ) -> "ApiResponse[T]":
  80. """构造失败响应。msg 为空时使用错误码自带的默认提示。"""
  81. return cls(
  82. code=code.code,
  83. data=data,
  84. msg=msg if msg is not None else code.msg,
  85. )
  86. def success_response(
  87. data: Any = None,
  88. msg: str | None = None,
  89. http_status: int = 200,
  90. ) -> JSONResponse:
  91. """构造成功的 JSONResponse(适用于异常处理器、中间件等场景)。"""
  92. return JSONResponse(
  93. status_code=http_status,
  94. content={
  95. "code": ResponseCode.SUCCESS.code,
  96. "data": data,
  97. "msg": msg if msg is not None else ResponseCode.SUCCESS.msg,
  98. },
  99. )
  100. def error_response(
  101. code: ResponseCode,
  102. msg: str | None = None,
  103. data: Any = None,
  104. http_status: int = 200,
  105. ) -> JSONResponse:
  106. """构造失败的 JSONResponse(适用于异常处理器、中间件等场景)。
  107. Args:
  108. code: 业务错误码枚举
  109. msg: 错误提示,为空时使用错误码自带的默认提示
  110. data: 附加数据(如参数验证错误的详细字段列表)
  111. http_status: HTTP 状态码,默认 200(业务错误也走 200,由 code 区分成败)
  112. 返回示例:
  113. {"code": 1001, "data": [...], "msg": "参数验证失败"}
  114. """
  115. return JSONResponse(
  116. status_code=http_status,
  117. content={
  118. "code": code.code,
  119. "data": data,
  120. "msg": msg if msg is not None else code.msg,
  121. },
  122. )
  123. class BusinessError(Exception):
  124. """业务异常基类,用于在业务层抛出后由全局处理器统一转为 ApiResponse。
  125. 用法:
  126. raise BusinessError(ResponseCode.CACHE_NOT_READY)
  127. raise BusinessError(ResponseCode.UPSTREAM_ERROR, msg="Zendesk 限流")
  128. """
  129. def __init__(
  130. self,
  131. code: ResponseCode,
  132. msg: str | None = None,
  133. data: Any = None,
  134. http_status: int = 200,
  135. ) -> None:
  136. self.response_code = code
  137. self.code = code.code
  138. self.msg = msg if msg is not None else code.msg
  139. self.data = data
  140. self.http_status = http_status
  141. super().__init__(self.msg)