本文 AI 產出,尚未審核

FastAPI – File 上傳(File() 與 UploadFile)


簡介

在 Web 應用中,檔案上傳是常見需求,無論是使用者上傳頭像、PDF 報表,或是系統接收批次資料,都離不開 HTTP POST 的 multipart/form-data 格式。FastAPI 內建的 File()UploadFile 讓開發者可以以 宣告式 的方式輕鬆處理檔案,同時保留非同步 I/O 的效能優勢。

本篇文章將說明如何在 FastAPI 中正確使用 File()UploadFile,從基本範例到進階的檔案驗證、儲存與錯誤處理,協助你在實務專案中快速上手、寫出安全且易維護的檔案上傳 API。


核心概念

1. File()UploadFile 的差異

File() UploadFile
回傳型別 bytes(完整讀入記憶體) UploadFile 物件(包含檔案名稱、content_type、檔案指標)
適用情境 小檔案(例如圖示、簡短文字) 中大型檔案(圖片、影片、CSV 等)
效能 會一次性載入全部內容,記憶體佔用較高 stream 方式讀取,支援非同步 I/O,記憶體佔用低

小技巧:若不確定檔案大小,建議預設使用 UploadFile,只在確定檔案極小且需立即處理時才改用 File()

2. 基本使用方式

FastAPI 透過 依賴注入(Dependency Injection)把上傳的檔案自動轉換成相應的型別。只要在路由函式的參數上加上 File(...),框架就會把 multipart/form-data 中的檔案欄位注入進來。

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

# 1️⃣ 使用 File() 直接取得 bytes
@app.post("/upload-bytes")
async def upload_bytes(file: bytes = File(...)):
    size = len(file)
    return {"filename": "unknown", "size_bytes": size}
# 2️⃣ 使用 UploadFile 取得檔案資訊與檔案指標
@app.post("/upload-stream")
async def upload_stream(file: UploadFile = File(...)):
    # 取得檔名與 MIME type
    filename = file.filename
    content_type = file.content_type
    # 非同步讀取前 100 bytes 作簡易檢查
    head = await file.read(100)
    await file.seek(0)  # 重設指標,之後可再次讀取完整內容
    return {"filename": filename, "content_type": content_type, "head_bytes": len(head)}

3. 多檔案上傳

File()UploadFile 都支援 list 型別,讓一次上傳多個檔案變得輕鬆。

@app.post("/upload-multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
    results = []
    for f in files:
        content = await f.read()
        results.append({"filename": f.filename, "size": len(content)})
        await f.seek(0)  # 若後續還要使用,可回到開頭
    return {"files": results}

4. 檔案驗證與限制

雖然 FastAPI 不會自動檢查檔案大小或類型,但我們可以在端點內自行驗證,或利用 Pydantic 的自訂驗證器。

MAX_SIZE = 5 * 1024 * 1024  # 5 MB

@app.post("/upload-validate")
async def upload_validate(file: UploadFile = File(...)):
    # 1. 限制 MIME type(只接受 JPEG 或 PNG 圖片)
    if file.content_type not in {"image/jpeg", "image/png"}:
        return {"error": "僅支援 JPEG / PNG 圖片"}

    # 2. 限制檔案大小(以 stream 方式計算)
    size = 0
    async for chunk in file.iter_chunks():
        size += len(chunk)
        if size > MAX_SIZE:
            return {"error": "檔案過大,請限制在 5 MB 以內"}
    await file.seek(0)  # 讀取完後回到開頭

    # 若驗證通過,儲存檔案
    path = f"uploads/{file.filename}"
    async with aiofiles.open(path, "wb") as out_file:
        while content := await file.read(1024):
            await out_file.write(content)
    return {"filename": file.filename, "size": size}

5. 非同步儲存檔案

使用 aiofiles(或其他非同步檔案 I/O 套件)可避免阻塞事件迴圈,提升 API 的併發效能。

import aiofiles
from pathlib import Path

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

@app.post("/upload-async")
async def upload_async(file: UploadFile = File(...)):
    dest_path = UPLOAD_DIR / file.filename
    async with aiofiles.open(dest_path, "wb") as out_file:
        while chunk := await file.read(1024):  # 每次讀 1KB
            await out_file.write(chunk)
    return {"saved_to": str(dest_path)}

常見陷阱與最佳實踐

陷阱 說明 解決方式
一次性讀入過大檔案 (File() 回傳 bytes) 大檔案會把整個檔案放入記憶體,導致 OOM 改用 UploadFile 並以 stream 方式處理
忘記重設檔案指標 (seek(0)) 讀取部分內容後,後續再讀會從結尾開始,得到空結果 讀完後 await file.seek(0) 或重新開啟檔案
同步寫檔阻塞 使用內建 open() 會阻塞事件迴圈,降低併發 使用 aiofiles 或其他非同步 I/O
缺乏 MIME/大小驗證 讓惡意使用者上傳危險檔案或過大檔案 在端點內自行檢查 content_type、檔案大小
路徑穿越攻擊 (../) 直接使用上傳檔名作為儲存路徑,可能寫入系統敏感目錄 透過 os.path.basenamepathlibsecure_filename 方式過濾檔名

最佳實踐

  1. 預設使用 UploadFile,僅在確定檔案極小且需立即在記憶體內處理時才改用 File()
  2. 非同步寫檔:搭配 aiofiles,確保高併發環境不會被阻塞。
  3. 檔名安全:使用 uuid4() 或時間戳記重新命名檔案,避免原始檔名衝突與路徑穿越。
  4. 統一錯誤回應:建立自訂例外或使用 HTTPException(status_code=400, detail=...),讓前端容易捕捉。
  5. 限制上傳大小:在 FastAPI 的 FastAPI(..., max_upload_size=...)(若使用 Starlette 0.24+)或自行在端點檢查。

實際應用場景

場景 為何需要檔案上傳 範例實作
使用者頭像 前端上傳圖片,後端存儲或傳至 CDN 使用 UploadFile,驗證 MIME 為 image/*,儲存至 avatars/,回傳 CDN URL。
CSV 批次匯入 企業系統允許管理員一次匯入大量資料 UploadFile 讀取 CSV,使用 pandas.read_csv 逐行驗證,再寫入資料庫。
影片上傳 影片平台需要接受大型影片檔案 採用 UploadFile 搭配分塊寫入(chunked upload),同時在前端實作斷點續傳。
PDF 報表生成 後端根據使用者上傳的模板產生 PDF 先用 UploadFile 讀取模板檔案,使用 ReportLabWeasyPrint 產生 PDF,最後回傳檔案流。
機器學習模型上傳 使用者可自行上傳訓練好的模型檔案 UploadFile 接收 .pt.h5 等二進位檔,儲存至安全目錄,並在服務重啟時載入。

總結

  • File() 直接回傳 bytes,適合小檔案;UploadFile 提供檔案資訊與非同步流式讀寫,適用於大多數實務需求。
  • 使用 非同步 I/Oaiofiles)可以保持 FastAPI 的高併發特性,避免因寫檔而阻塞事件迴圈。
  • 檔案驗證(MIME、大小)與 安全檔名 是防止惡意上傳的重要防線。
  • 依照不同 業務場景(頭像、CSV、影片、PDF、模型)選擇合適的上傳策略與儲存方式,能讓系統更具彈性與可維護性。

掌握了這些概念與實作技巧後,你就能在 FastAPI 中自信地處理各種檔案上傳需求,為你的 Web 服務增添完整且安全的功能。祝開發順利!