| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- """统一响应类与业务错误码定义。
- 所有接口响应统一使用 `{code, data, msg}` 信封格式:
- - code: 0 表示成功;其他值代表不同的失败类型
- - data: 业务数据(成功时)或 None(失败时)
- - msg: 描述信息
- 错误码使用 `(code, default_msg)` 的元组定义,code 和默认提示一一对应。
- """
- from __future__ import annotations
- from enum import Enum
- from typing import Any, Generic, TypeVar
- from fastapi.responses import JSONResponse
- from pydantic import BaseModel, Field
- T = TypeVar("T")
- class ResponseCode(Enum):
- """业务错误码枚举:每个成员都是 (code, default_msg) 元组。
- 分段规则:
- 0 成功
- 1000-1999 通用错误(参数、请求格式等)
- 2000-2999 业务逻辑错误(缓存未就绪、资源不存在等)
- 3000-3999 外部依赖错误(Zendesk、上游服务等)
- 9000-9999 服务器内部错误
- 用法:
- ResponseCode.SUCCESS.code # -> 0
- ResponseCode.SUCCESS.msg # -> "成功"
- """
- # 成功
- SUCCESS = (0, "成功")
- # 通用错误 1xxx
- PARAM_INVALID = (1001, "参数验证失败")
- REQUEST_INVALID = (1002, "请求格式不正确")
- METHOD_NOT_ALLOWED = (1003, "请求方法不允许")
- NOT_FOUND = (1004, "资源不存在")
- # 业务错误 2xxx
- CACHE_NOT_READY = (2001, "FAQ 缓存未就绪,请稍后重试")
- RESOURCE_NOT_FOUND = (2002, "业务资源不存在")
- # 外部依赖错误 3xxx
- UPSTREAM_ERROR = (3001, "上游服务调用失败")
- UPSTREAM_TIMEOUT = (3002, "上游服务响应超时")
- # 服务器错误 9xxx
- INTERNAL_ERROR = (9999, "服务器内部错误,请稍后重试")
- def __init__(self, code: int, msg: str) -> None:
- self.code = code
- self.msg = msg
- def __repr__(self) -> str: # pragma: no cover
- return f"<{self.__class__.__name__}.{self.name}: code={self.code} msg={self.msg!r}>"
- class ApiResponse(BaseModel, Generic[T]):
- """统一 API 响应模型。
- 用于在路由 `response_model` 中声明,让 OpenAPI 文档展示标准结构。
- 用法示例:
- @router.get("/search", response_model=ApiResponse[SearchData])
- async def search(...) -> ApiResponse[SearchData]:
- return ApiResponse.ok(data=SearchData(...))
- # 失败响应(自动使用错误码对应的默认 msg)
- return ApiResponse.fail(ResponseCode.PARAM_INVALID)
- # 失败响应(覆盖默认 msg)
- return ApiResponse.fail(ResponseCode.UPSTREAM_ERROR, msg="Zendesk 限流")
- """
- code: int = Field(default=0, description="业务码,0=成功,非0=失败")
- data: T | None = Field(default=None, description="业务数据,失败时为 null")
- msg: str = Field(default="成功", description="描述信息")
- @classmethod
- def ok(
- cls,
- data: T | None = None,
- msg: str | None = None,
- ) -> "ApiResponse[T]":
- """构造成功响应。msg 为空时使用 SUCCESS 默认提示。"""
- return cls(
- code=ResponseCode.SUCCESS.code,
- data=data,
- msg=msg if msg is not None else ResponseCode.SUCCESS.msg,
- )
- @classmethod
- def fail(
- cls,
- code: ResponseCode,
- msg: str | None = None,
- data: T | None = None,
- ) -> "ApiResponse[T]":
- """构造失败响应。msg 为空时使用错误码自带的默认提示。"""
- return cls(
- code=code.code,
- data=data,
- msg=msg if msg is not None else code.msg,
- )
- def success_response(
- data: Any = None,
- msg: str | None = None,
- http_status: int = 200,
- ) -> JSONResponse:
- """构造成功的 JSONResponse(适用于异常处理器、中间件等场景)。"""
- return JSONResponse(
- status_code=http_status,
- content={
- "code": ResponseCode.SUCCESS.code,
- "data": data,
- "msg": msg if msg is not None else ResponseCode.SUCCESS.msg,
- },
- )
- def error_response(
- code: ResponseCode,
- msg: str | None = None,
- data: Any = None,
- http_status: int = 200,
- ) -> JSONResponse:
- """构造失败的 JSONResponse(适用于异常处理器、中间件等场景)。
- Args:
- code: 业务错误码枚举
- msg: 错误提示,为空时使用错误码自带的默认提示
- data: 附加数据(如参数验证错误的详细字段列表)
- http_status: HTTP 状态码,默认 200(业务错误也走 200,由 code 区分成败)
- 返回示例:
- {"code": 1001, "data": [...], "msg": "参数验证失败"}
- """
- return JSONResponse(
- status_code=http_status,
- content={
- "code": code.code,
- "data": data,
- "msg": msg if msg is not None else code.msg,
- },
- )
- class BusinessError(Exception):
- """业务异常基类,用于在业务层抛出后由全局处理器统一转为 ApiResponse。
- 用法:
- raise BusinessError(ResponseCode.CACHE_NOT_READY)
- raise BusinessError(ResponseCode.UPSTREAM_ERROR, msg="Zendesk 限流")
- """
- def __init__(
- self,
- code: ResponseCode,
- msg: str | None = None,
- data: Any = None,
- http_status: int = 200,
- ) -> None:
- self.response_code = code
- self.code = code.code
- self.msg = msg if msg is not None else code.msg
- self.data = data
- self.http_status = http_status
- super().__init__(self.msg)
|