fastapi: 第九章: 自定义业务异常

一,为什么要自定义业务异常?

在开发框架中,原生框架自带的异常(如 HTTPExceptionValueError 等)通常只能处理通用的网络错误或语言底层的报错。
自定义业务异常(Business Exception) 的核心目的,是为了将“业务逻辑错误”与“系统/网络错误”完全解耦
它的好处:
使用自定义业务异常有以下几个决定性的好处:
1. 统一异常捕获与响应格式
如果不用自定义异常,当用户余额不足时,你可能需要在 Controller 层频繁地写:
python
return {"code": 4001, "msg": "余额不足", "data": None}
这会导致错误处理逻辑散落在各个路由中。
使用自定义异常后,你只需要 raise WalletInsufficientError(),结合全局异常处理器(Exception Handler),框架会自动将其转化为标准、统一的 JSON 格式输出给前端。
2. 保持代码的“高内聚”与“干净”
自定义异常允许你在复杂的业务 Service 层、DAO 层甚至工具类中直接中断程序,而不需要层层包裹 if-else 返回值。
             坏代码(层层传递错误码)
              python
  • status, msg = user_service.reduce_balance(user_id, money)
    if not status:
        return {"code": 4001, "msg": msg}
    
    好代码(使用自定义异常)
    python
    # Service层直接抛出,Controller层一行代码不用管,交给全局捕获
    user_service.reduce_balance(user_id, money) 
    
3. 语义清晰,实现“代码即文档”
原生的错误(如 ValueErrorHTTPException(status_code=400))表达的信息太宽泛。自定义异常具有极强的业务语义
  • UserNotFoundError:一眼就知道是用户不存在。
  • TokenExpiredError:一眼就知道是登录凭证过期。
  • StockInsufficientError:商品库存不足。
    这不仅让开发人员在阅读和维护代码时一目了然,在查看生产环境日志(Logs)时,也能瞬间定位是哪个业务环节出了问题。
4. 精准分类与差异化处理
不同的业务错误,后续的处理手段是不同的。自定义异常可以让你在全局针对性地做出不同响应:
  • 用户输入错(如 InvalidSMSCodeError):返回 200 或 400,提示用户重新输入,不记录严重日志。
  • 第三方接口崩溃(如 PaymentGatewayError):返回 500,触发系统报警(发邮件/钉钉通知运维),并记录详细的堆栈信息。
5. 前后端分离开发的“解耦利器”
在前后端分离的架构中,前端通常不关心服务端的业务逻辑长什么样,只关心状态码(Code)
你可以为自定义异常绑定固定的业务状态码:
python
class OrderPayException(BusinessException):
    code = 2003
    msg = "订单支付失败"
这样,前端接收到 2003 时,就可以直接跳转到对应的“支付失败页面”或弹出特定提示,极大地降低了前后的沟通成本。
 

二,在fastapi中自定义业务异常

自定义业务异常

# app/core/BusinessException.py

# 自定义业务异常(用于手动 raise)
class BusinessException(Exception):
    def __init__(self, msg: str, code: int = 400, status_code: int = 200):
        self.msg = msg
        self.code = code
        self.status_code = status_code # HTTP 状态码,通常业务报错仍返回 200

添加业务异常到app

# app/core/exceptions.py
from fastapi import Request, FastAPI
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

from app.core.BusinessException import BusinessException
from app.utils.BaseResponse import BaseResponse

def register_exception_handlers(app: FastAPI):
    # 1. 捕获自定义业务异常 (如: 密码错误、余额不足)
    @app.exception_handler(BusinessException)
    async def business_exception_handler(request: Request, exc: BusinessException):
        content = BaseResponse(code=exc.code, msg=exc.msg, data=None).model_dump()
        return JSONResponse(status_code=exc.status_code, content=content)

    # 2. 捕获系统未知异常 (如: 代码崩溃、数据库连接断开 500)
    @app.exception_handler(Exception)
    async def all_exception_handler(request: Request, exc: Exception):
        # 注意:生产环境应将 exc 记录到日志系统(如 loguru)
        content = BaseResponse(code=500, msg="服务器内部错误,请稍后再试", data=None).model_dump()
        return JSONResponse(status_code=500, content=content)

三,在代码中应用抛出异常 

@router.post("/login")
def get_all_products(user_id: int = Form(...), user_name: str = Form(...)):
    if user_id == 123:   # 例如用户状态被锁定
        raise BusinessException(code=10001, msg="当前用户状态错误")
    user_info = {"id": user_id, "name": f"用户{user_name}"}
    return success(data=user_info)

四,测试效果:

正常时:

image

抛出业务异常时:

image

 

 

 

 
posted @ 2026-06-10 15:19  刘宏缔的架构森林  阅读(5)  评论(0)    收藏  举报