FastAPI錯誤處理:HTTP狀態碼與異常捕獲實戰

在API開發中,錯誤處理是確保系統健壯性和用戶體驗的關鍵。當用戶請求出現問題(比如參數錯誤、資源不存在)時,良好的錯誤處理能讓API返回清晰的提示,幫助調用方快速定位問題。FastAPI提供了簡潔高效的錯誤處理機制,讓我們既能靈活使用HTTP狀態碼,又能優雅地捕獲和處理異常。

一、爲什麼需要錯誤處理?

想象一下,如果用戶請求一個不存在的用戶ID,而API直接崩潰或返回空白,用戶會非常困惑。正確的錯誤處理可以:
- 向調用方返回明確的錯誤原因(比如“用戶不存在”)
- 使用標準的HTTP狀態碼(如404、400),讓前端或其他系統更容易解析
- 避免程序因未處理的異常而崩潰,保證服務穩定

二、HTTP狀態碼:API的“紅綠燈”

HTTP狀態碼是錯誤處理的基礎,FastAPI支持所有標準HTTP狀態碼。常見的狀態碼及使用場景如下:

狀態碼 含義 適用場景
200 成功 請求正常處理,返回數據
400 參數錯誤 請求參數不合法(如格式錯誤)
404 資源不存在 請求的資源(如用戶、商品)不存在
401 未授權 需要登錄但未提供有效憑證
403 禁止訪問 權限不足
422 驗證錯誤 請求參數格式正確但驗證失敗
500 服務器內部錯誤 服務端邏輯出錯(如數據庫連接失敗)

FastAPI中使用狀態碼:在路由函數中,可通過returnraise直接指定狀態碼。例如:

from fastapi import FastAPI, HTTPException

app = FastAPI()

# 成功響應(默認200)
@app.get("/health")
def health_check():
    return {"status": "OK"}  # 自動返回200狀態碼

# 明確返回404狀態碼
@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id not in [1, 2, 3]:  # 假設只有1、2、3存在
        return {"status": "error", "detail": "Item not found"}, 404  # 直接返回狀態碼

三、FastAPI異常捕獲:主動“拋出”錯誤

FastAPI推薦使用HTTPException類主動拋出錯誤,它能直接指定狀態碼和錯誤信息,讓錯誤處理更清晰。

1. 基礎用法:HTTPException

from fastapi import HTTPException

@app.get("/users/{user_id}")
def get_user(user_id: int):
    # 模擬數據庫查詢
    users = {1: "Alice", 2: "Bob"}
    if user_id not in users:
        # 拋出HTTP異常,指定狀態碼和詳細信息
        raise HTTPException(
            status_code=404,  # 資源不存在
            detail=f"用戶ID {user_id} 不存在",  # 錯誤描述
            headers={"X-Error-Reason": "User not found"}  # 可選響應頭
        )
    return {"user_id": user_id, "name": users[user_id]}

效果:當請求/users/999時,API會返回:

{
  "detail": "用戶ID 999 不存在"
}

狀態碼爲404,前端可根據狀態碼判斷“資源不存在”。

2. 參數驗證錯誤:自動返回422

FastAPI會自動處理參數格式錯誤(如類型不匹配),並返回422狀態碼。例如:

@app.get("/users/{user_id}")
def get_user(user_id: int):  # 強制user_id爲整數
    # 如果用戶傳非整數(如字符串),FastAPI自動攔截並返回422
    return {"user_id": user_id}

效果:請求/users/abc時,API返回:

{
  "detail": [
    {
      "loc": ["path", "user_id"],
      "msg": "輸入的值不是有效的整數",
      "type": "type_error.integer"
    }
  ]
}

狀態碼爲422(驗證錯誤),包含具體的錯誤字段和原因。

四、自定義異常:業務邏輯的“專屬錯誤”

當業務邏輯出現錯誤(如“餘額不足”“權限不足”)時,可自定義異常類,配合FastAPI的異常處理機制統一管理。

1. 定義自定義異常

class InsufficientFundsError(Exception):
    """自定義異常:餘額不足"""
    def __init__(self, balance: float, needed: float):
        self.balance = balance  # 當前餘額
        self.needed = needed    # 需要的金額
        self.detail = f"餘額不足,當前餘額: {balance},需要: {needed}"

2. 全局異常處理

通過@app.exception_handler註冊自定義異常的處理函數,實現全局統一處理:

from fastapi import Request, status
from fastapi.responses import JSONResponse

# 處理InsufficientFundsError異常
@app.exception_handler(InsufficientFundsError)
async def handle_insufficient_funds(request: Request, exc: InsufficientFundsError):
    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,  # 400狀態碼
        content={"detail": exc.detail}  # 返回自定義錯誤信息
    )

3. 在路由中使用自定義異常

@app.post("/withdraw")
def withdraw(amount: float):
    balance = 100.0  # 假設當前餘額
    if amount > balance:
        # 拋出自定義異常
        raise InsufficientFundsError(balance=balance, needed=amount)
    return {"message": "轉賬成功", "balance": balance - amount}

效果:請求/withdraw?amount=200時,API返回:

{
  "detail": "餘額不足,當前餘額: 100.0,需要: 200.0"
}

五、全局錯誤處理:統一“兜底”策略

當出現未預料到的異常(如數據庫連接失敗)時,全局異常處理能避免重複代碼,統一返回標準錯誤。

from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse

# 處理所有未捕獲的異常(通用兜底)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    # 記錄錯誤日誌(生產環境建議使用logging)
    print(f"未處理異常: {str(exc)}")
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={"detail": "服務器內部錯誤,請聯繫管理員"}
    )

六、最佳實踐總結

  1. 用HTTPException處理HTTP標準錯誤:如404(資源不存在)、400(參數錯誤)、422(驗證失敗),直接拋出並指定狀態碼。
  2. 用自定義異常處理業務邏輯錯誤:如“餘額不足”“權限不足”,配合exception_handler統一返回。
  3. 自動參數驗證:FastAPI會自動處理類型錯誤,返回422狀態碼,無需手動捕獲。
  4. 全局異常處理:通過@app.exception_handler統一兜底未捕獲異常,避免重複代碼。

示例代碼整合

from fastapi import FastAPI, HTTPException, Request, status
from fastapi.responses import JSONResponse

app = FastAPI()

# 模擬用戶數據
users = {1: "Alice", 2: "Bob"}

# 自定義異常
class InsufficientFundsError(Exception):
    def __init__(self, balance: float, needed: float):
        self.balance = balance
        self.needed = needed
        self.detail = f"餘額不足,當前餘額: {balance},需要: {needed}"

# 全局異常處理:兜底所有未捕獲異常
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    print(f"全局錯誤: {str(exc)}")
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={"detail": "服務器內部錯誤"}
    )

# 處理自定義異常:餘額不足
@app.exception_handler(InsufficientFundsError)
async def handle_insufficient_funds(request: Request, exc: InsufficientFundsError):
    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content={"detail": exc.detail}
    )

# 1. 獲取用戶(處理404)
@app.get("/users/{user_id}")
def get_user(user_id: int):
    if user_id not in users:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"用戶ID {user_id} 不存在"
        )
    return {"user_id": user_id, "name": users[user_id]}

# 2. 模擬轉賬(處理自定義異常)
@app.post("/withdraw")
def withdraw(amount: float):
    balance = 100.0
    if amount > balance:
        raise InsufficientFundsError(balance=balance, needed=amount)
    return {"message": "轉賬成功", "remaining": balance - amount}

通過以上內容,你已經掌握了FastAPI錯誤處理的核心方法:用HTTP狀態碼規範錯誤類型,用HTTPException和自定義異常處理具體錯誤場景,並通過全局異常處理統一兜底。這些技巧能幫助你構建更健壯、友好的API服務。

小夜