在 FastAPI 中,默认情况下它期望客户端发送的是 JSON 格式的请求体。如果你需要接收 HTML 表单提交的数据(application/x-www-form-urlencoded 或 multipart/form-data),或者需要处理文件上传,你需要使用 FastAPI 提供的 Form、File 和 UploadFile 依赖。
下面我将从环境准备、基础表单、文件上传到进阶技巧,为你系统梳理 FastAPI 处理表单数据的完整指南。
⚠️ 第一步:安装必备依赖(新手最常踩的坑)
FastAPI 本身不包含解析表单数据的库。在处理任何表单或文件上传之前,必须安装 python-multipart:
pip install python-multipart如果不安装,当你尝试访问带有 Form 或 File 参数的路由时,FastAPI 会抛出 ImportError。
二、 处理普通表单字段 (Form)
使用 Form 可以接收 HTML 表单中的文本字段。它的用法与 Query 或 Path 非常相似。
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
def login(
username: str = Form(..., description="用户名"),
password: str = Form(..., description="密码"),
remember_me: bool = Form(default=False)
):
# Form(...) 中的 ... 表示该字段是必填项 (Required)
return {
"username": username,
"password_length": len(password),
"remember_me": remember_me
}💡 提示:在 Swagger UI (/docs) 中,FastAPI 会自动将此接口渲染为一个真实的 HTML 表单,你可以直接在浏览器中填写并测试提交。
三、 处理文件上传 (File 与 UploadFile)
处理文件时,FastAPI 提供了两个选项:File 和 UploadFile。强烈推荐使用 UploadFile。
| 特性 | File (bytes) | UploadFile (推荐 🌟) |
|---|---|---|
| 返回值 | 整个文件的字节流 (bytes) | 类似 Python 文件对象的接口 |
| 内存占用 | 整个文件加载到内存,大文件会导致 OOM | 存储在临时文件中,内存占用极小 |
| 异步支持 | 否 (同步读取) | 是 (await file.read()) |
| 元数据 | 无 | 包含 filename, content_type, size 等 |
1. 单文件上传示例
from fastapi import FastAPI, File, UploadFile
import shutil
app = FastAPI()
@app.post("/upload/")
async def upload_file(file: UploadFile = File(..., description="请选择要上传的文件")):
# 1. 获取文件元数据
filename = file.filename
content_type = file.content_type
# 2. 读取文件内容 (异步操作,不会阻塞事件循环)
contents = await file.read()
# 3. 保存文件到本地 (示例)
# with open(f"uploads/{filename}", "wb") as buffer:
# shutil.copyfileobj(file.file, buffer)
return {
"filename": filename,
"content_type": content_type,
"size_bytes": len(contents)
}2. 多文件上传示例
只需将类型提示改为列表 list[UploadFile] 即可。
from typing import List
@app.post("/upload-multiple/")
async def upload_multiple_files(files: list[UploadFile] = File(...)):
filenames = [file.filename for file in files]
return {"uploaded_files": filenames, "count": len(files)}四、 混合场景:表单字段 + 文件上传
在实际业务中(如用户修改资料、发布文章),通常需要同时提交文本字段和文件。FastAPI 完美支持这种混合模式,它会自动将请求识别为 multipart/form-data。
from fastapi import FastAPI, Form, File, UploadFile
app = FastAPI()
@app.post("/create-post/")
async def create_post(
title: str = Form(..., max_length=100),
content: str = Form(...),
category: str = Form(default="general"),
cover_image: UploadFile = File(default=None) # 文件是可选的
):
post_data = {
"title": title,
"content": content,
"category": category,
}
if cover_image:
post_data["cover_image_name"] = cover_image.filename
# 可以在这里处理图片保存逻辑
return {"status": "success", "data": post_data}五、 进阶技巧:如何让表单数据支持 Pydantic 模型?
痛点:FastAPI 原生不支持直接使用 Pydantic BaseModel 来接收表单数据(BaseModel 默认只解析 JSON)。如果你有一个包含 10 个字段的表单,写 10 个 Form(...) 会非常繁琐。
解决方案:使用自定义依赖,手动将表单数据映射到 Pydantic 模型。
from fastapi import FastAPI, Form, Depends
from pydantic import BaseModel, Field
# 1. 定义你的 Pydantic 模型 (和接收 JSON 时一样)
class UserRegistration(BaseModel):
username: str = Field(..., min_length=3)
email: str
age: int | None = None
# 2. 创建一个依赖函数,负责从 Form 中提取数据并构建模型
def parse_user_form(
username: str = Form(...),
email: str = Form(...),
age: int | None = Form(default=None)
) -> UserRegistration:
# 将表单数据组装成 Pydantic 模型,享受自动验证
return UserRegistration(username=username, email=email, age=age)
# 3. 在路由中使用
@app.post("/register-form/")
def register_user(user: UserRegistration = Depends(parse_user_form)):
# 此时 user 已经是一个验证通过的 UserRegistration 对象
return {"message": f"用户 {user.username} 注册成功", "email": user.email}(注:对于更复杂的项目,也可以使用第三方库如 fastapi-form 或 pydantic-form 来简化这个过程)
六、 最佳实践与避坑指南
- 始终使用
async def处理文件:UploadFile的.read(),.write(),.seek()等方法都是异步的。使用async def可以让 FastAPI 在处理大文件上传时,依然能够响应其他客户端的请求,保持高并发性能。 - 限制文件大小:为了防止恶意上传超大文件耗尽服务器内存或磁盘,建议在 Nginx 层面或代码层面限制大小。
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
@app.post("/upload/")
async def upload(file: UploadFile = File(...)):
contents = await file.read()
if len(contents) > MAX_FILE_SIZE:
raise HTTPException(status_code=413, detail="文件过大")
# ...- 验证文件类型:不要只信任
file.content_type(客户端可以伪造),最好检查文件扩展名或使用python-magic库检查文件的真实 MIME 类型。 - 清理临时文件:
UploadFile会在请求结束后自动关闭,但如果你的逻辑中创建了临时文件,请确保在finally块或依赖项的yield中清理它们。
总结:表单处理速查表
| 需求 | 推荐用法 | 示例 |
|---|---|---|
| 接收普通文本表单 | str = Form(...) | username: str = Form(...) |
| 接收单个文件 | UploadFile = File(...) | file: UploadFile = File(...) |
| 接收多个文件 | list[UploadFile] = File(...) | files: list[UploadFile] = File(...) |
| 可选的表单字段 | 赋予默认值 | bio: str = Form(default="") |
| 优雅处理复杂表单 | 自定义 Depends 映射到 Pydantic | user: User = Depends(parse_form) |
你现在是否有一个具体的表单上传需求(例如:如何接收前端传来的 Base64 图片,或者如何结合 SQLAlchemy 保存带有图片的记录)?告诉我,我可以为你提供针对性的代码模板!