FastAPI 学习教程
一份面向有编程基础学习者的系统性 FastAPI 教程。
读者画像 :初级 Java 开发人员,有面向对象与 Web 开发概念,但没有系统学习过 Python------因此教程开头准备了"第〇章:写给 Java 开发者的 Python 速成",用 Java 概念对照讲清 Python 语法,读完后即可顺畅阅读后续全部章节。
适用环境:Python 3.10+,FastAPI 0.141.x(2026 年中),Pydantic v2。
参考来源:FastAPI 官方文档(含中文版)、官方发行说明及社区最佳实践。
目录
- [预备篇:写给 Java 开发者的 Python 速成](#预备篇:写给 Java 开发者的 Python 速成)
- [初识 FastAPI](#初识 FastAPI)
- 路径操作与参数
- [Pydantic 数据校验](#Pydantic 数据校验)
- 响应处理
- 错误处理与异常
- 依赖注入系统
- [中间件与 CORS](#中间件与 CORS)
- 异步编程与并发
- 高级特性
- [安全与认证(OAuth2 + JWT)](#安全与认证(OAuth2 + JWT))
- 数据库集成(SQLAlchemy)
- 项目结构与工程化
- 测试
- 部署
- 最佳实践与常见坑
- 附录:学习资源
预备篇:写给 Java 开发者的 Python 速成
如果你写过 Java(哪怕只是 Spring Boot 的增删改查),这一章可以让你在 30 分钟内具备阅读 FastAPI 代码所需的全部 Python 知识。每个概念都给出 Java 对照,遇到不懂的语法随时翻回这里查。
0.1 语言与生态对照总表
| 维度 | Java | Python |
|---|---|---|
| 运行方式 | 编译为字节码,跑在 JVM 上 | 解释执行(.pyc 缓存字节码),无需编译 |
| 包管理 | Maven / Gradle + pom.xml |
pip / uv + pyproject.toml |
| 依赖隔离 | 每个 JVM 进程天然隔离 | 虚拟环境 venv(见 0.11,必须掌握) |
| 生态仓库 | Maven Central | PyPI |
| 主流 Web 框架 | Spring Boot | FastAPI / Django / Flask |
| 代码块 | 大括号 {} |
缩进(4 个空格,强制且是语法的一部分) |
| 语句结尾 | 分号 | 换行即可,无分号 |
| 命名习惯 | camelCase 变量、PascalCase 类 |
变量/函数用 snake_case,类也用 PascalCase |
| 空值 | null |
None |
| 打印 | System.out.println() |
print() |
0.2 变量与类型:动态类型 + 类型注解
Python 变量没有类型声明 ,赋什么就是什么;但可以用**类型注解(type hints)**标注类型------注意:注解只是给工具(IDE、FastAPI)看的,解释器运行时基本不强制。FastAPI 的全部魔法都建立在这套注解上。
python
name = "Alice" # 直接赋值,无需声明类型
count = 10
price = 9.99
# 带类型注解的写法(FastAPI 中几乎都这么写)
name: str = "Alice"
count: int = 10
# 没有类型也不报错------这正是它和 Java 最大的不同
count = "现在是字符串了" # 合法!
对应 Java 泛型容器的写法:
python
from typing import Optional
age: int = 20
email: str | None = None # Java: Integer age; String email;(可为 null)
tags: list[str] = [] # Java: List<String>
user_map: dict[str, int] = {} # Java: Map<String, Integer>
point: tuple[int, int] = (1, 2) # 不可变列表,Java 无直接对应(像定长 List)
Java 8+ 的
Optional<T>是包装类,而 Python 的str | None只是注解层面的联合类型 ,运行时值就是None或字符串本身。Python 3.10 之前的老代码会写Optional[str],二者等价。
0.3 基本数据结构
python
# 列表 list ------ 对应 Java 的 ArrayList
items = [1, 2, 3]
items.append(4)
items[0] # 取值,下标从 0 开始
items[-1] # 负下标:倒数第一个
items[1:3] # 切片:取下标 1~2(含头不含尾)
len(items) # 长度(不是 items.size())
# 字典 dict ------ 对应 Java 的 HashMap
user = {"name": "Alice", "age": 20}
user["name"] # 取值
user.get("email") # 不存在返回 None(类似 getOrDefault)
user["email"] = "a@x.com" # 新增/修改
# 集合 set ------ 对应 Java 的 HashSet
s = {1, 2, 3}
s.add(4)
# 元组 tuple ------ 不可变列表
point = (3, 4)
x, y = point # 解构赋值(Java 17 record 模式匹配的味道)
遍历(没有 for(int i=0;...) 的常规写法,直接 for-in):
python
for item in items: # 对应 Java 的 for-each
print(item)
for i in range(5): # 需要"传统 for"时用 range:0,1,2,3,4
print(i)
for k, v in user.items(): # 遍历 Map
print(k, v)
# 列表推导式 ------ Java Stream 的 map/filter 一行版,Python 里到处都是
squares = [x * x for x in range(10)]
evens = [x for x in range(10) if x % 2 == 0]
# Java 对照: IntStream.range(0,10).filter(x -> x%2==0).boxed().toList()
0.4 字符串与 f-string
python
name, age = "Alice", 20
msg = f"{name} 今年 {age} 岁" # f-string,对应 Java 的 String.format / MessageFormat
line = f"{'-'*20}" # 表达式也可以放进去
0.5 控制流:缩进即代码块
python
score = 85
if score >= 90: # 条件不用括号,冒号开头
grade = "A" # 缩进 4 空格 = 进入代码块
elif score >= 80: # 注意是 elif,不是 else if
grade = "B"
else:
grade = "C"
# 逻辑运算符是英文单词,不是 && || !
if score > 0 and score < 100:
print("有效分数")
# 三元表达式(值在前,语法相反)
label = "及格" if score >= 60 else "不及格"
⚠️ Java 程序员最容易犯的错: 复制 Java 的 &&、||、!、else if、大括号习惯。Python 里这些全是语法错误;缩进混用 Tab 和空格也会直接报错(团队规范统一用 4 空格)。
0.6 函数
python
def add(a: int, b: int) -> int: # def 定义;-> 是返回类型注解
return a + b
# 默认参数(Java 没有,最接近的是重载)
def greet(name: str, greeting: str = "你好") -> str:
return f"{greeting}, {name}"
greet("Alice") # 用默认值
greet(name="Bob", greeting="Hi") # 关键字传参,顺序无关(FastAPI 大量使用)
# *args/**kwargs:可变参数,对应 Java 的可变参数 + 传 Map
def flexible(*args, **kwargs):
print(args) # 元组,收集位置参数
print(kwargs) # 字典,收集关键字参数
# lambda:只能是一个表达式(Java lambda 可以写多行)
square = lambda x: x * x
0.7 类与对象
python
class Animal: # 一个文件里可以放多个类,无 public/private 关键字
def __init__(self, name: str): # 构造方法,固定叫 __init__
self.name = name # self ≈ Java 的 this,但必须显式写在每个参数里
def speak(self) -> str: # 实例方法第一个参数永远是 self
return f"{self.name} 发出了声音"
class Dog(Animal): # 继承:括号直接写父类,没有 extends
def speak(self) -> str: # 覆写,没有 @Override(IDE 会提示)
return f"{self.name}:汪汪"
dog = Dog("旺财") # 实例化没有 new 关键字!
print(dog.speak())
要点:
- 没有
new:Dog("旺财")直接创建对象; - 没有访问修饰符 :约定
_name表示"内部使用"(相当于 protected),__name触发名称改写(近似 private),纯靠自觉; - 没有接口 implements 的强制:多态是"鸭子类型"------长得像、能调用就行;
- 所有方法第一个参数是
self,调用时不用传; @dataclass装饰器 ≈ Java 的 Lombok@Data,自动生成构造器、__eq__、__repr__:
python
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
# Point(x=1, y=2) 直接可用;FastAPI 里的 BaseModel 是它的"加强版"
0.8 模块与导入
python
import os # ≈ import java.util.*; 然后 os.xxx 调用
from datetime import datetime # ≈ 静态导入具体类
from myapp.services import UserService
from myapp import services as svc # ≈ import ... as 别名
- 一个
.py文件就是一个模块 (module),一个含__init__.py的目录是一个包(package)≈ Java 的 package; - 没有 classpath 的概念,按模块路径导入;
if __name__ == "__main__":下的代码只在直接运行该文件时执行(≈ Java 的 main 方法所在类的判定)。
0.9 异常
python
try:
result = 10 / 0
except ZeroDivisionError as e: # ≈ catch (ArithmeticException e)
print(f"除零错误: {e}")
except (ValueError, TypeError): # 一个 except 捕多种
print("其他错误")
else:
print("没出错才执行") # Java 没有这个
finally:
print("总会执行") # 同 Java finally
raise ValueError("参数非法") # ≈ throw new IllegalArgumentException(...)
FastAPI 里的 HTTPException 就是普通异常类的子类,raise 出去由框架接住转成 HTTP 响应(见第五章)。
0.10 装饰器:FastAPI 的 @app.get() 是什么
Java 注解(@GetMapping)基本只是元数据标记 ,由 Spring 在运行时反射扫描。Python 装饰器则是真函数:接收一个函数,返回一个新函数------本质是高阶函数语法糖。
python
import functools
def log_call(func): # 装饰器:接收函数
@functools.wraps(func)
def wrapper(*args, **kwargs): # 返回增强版函数
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call # @ 语法糖
def add(a, b):
return a + b
# 完全等价于:add = log_call(add)
add(1, 2) # 会先打印 "调用 add",再返回 3
所以 @app.get("/items") 的含义是:把 read_items 这个函数注册进 FastAPI 的路由表。理解了"装饰器 = 包裹函数的函数",FastAPI 的一切 @ 语法都不再神秘。
0.11 环境搭建:venv 必须 5 分钟搞懂
Java 的依赖在 JVM 层天然按进程隔离,Python 则是全局共享一个 site-packages ------直接 pip install 会把所有项目的依赖装在一起,版本互相打架(Maven 用户想象一下所有项目共用一个全局 lib 目录)。所以每个项目要用虚拟环境:
bash
# 在项目目录下创建虚拟环境(生成一个 .venv 目录,相当于项目私有的 site-packages)
python -m venv .venv
# 激活(Windows PowerShell)
.venv\Scripts\Activate.ps1
# 激活(Windows Git Bash / Linux / macOS)
source .venv/bin/activate
# 激活后,pip 只作用于当前虚拟环境
pip install "fastapi[standard]"
pip freeze > requirements.txt # ≈ mvn dependency 导出
# 退出虚拟环境
deactivate
.venv 目录不要提交到 git(≈ 不提交 target/)。更新的工具可用 uv (Rust 写的 pip 替代品,速度极快,用法 uv venv / uv pip install,正快速成为社区新标准)。
0.12 Java 程序员专属"坑前须知"
| 坑 | Java 直觉 | Python 现实 |
|---|---|---|
== 比较 |
比较值/引用 | == 比较值 (调用对象的 __eq__),is 比较是否同一对象 (近似 Java 的 == 引用比较);判断 None 惯用 x is None |
and/or 短路返回值 |
返回 boolean | 返回操作数本身 :0 or "默认值" → "默认值"(真值判断,0/""/\[\]/None 均为假) |
| 可变默认参数 | 无此概念 | def f(x, lst=[]) 的列表是所有调用共享的 !要用 lst=None + 函数体内新建 |
| 真值判断 | 必须 boolean | if items: 直接判断列表非空,if user: 判断非 None 且非空 |
| GIL | 多线程真并行 | 全局解释器锁使纯 Python 多线程无法 CPU 并行------这就是 FastAPI 世界强调 asyncio 与多进程部署的原因(见第八、十四章) |
| 不可变字符串 | String 不可变 | 相同,+ 拼接大量字符串也建议用 "".join(parts) |
| 深浅拷贝 | clone | b = a 只是引用;要副本用 a.copy() / copy.deepcopy(a) |
读完本章,你已经具备阅读本教程后续所有代码的能力。遇到陌生语法(如
Annotated、yield)时,正文会即时解释;建议把 0.10(装饰器)和 0.12(坑表)多看两遍。
一、初识 FastAPI
1.1 FastAPI 是什么
FastAPI 是一个现代化、高性能的 Python Web 框架,基于标准 Python 类型注解构建,核心特点:
| 特性 | 说明 |
|---|---|
| 高性能 | 基于 Starlette(异步框架)与 Pydantic(Rust 内核数据校验),性能与 Node.js、Go 相当 |
| 开发快 | 类型注解驱动的声明式写法,代码量少、重复少 |
| 自动校验 | 请求参数与请求体自动做类型与约束校验,非法请求自动返回 422 |
| 自动文档 | 自动生成交互式 API 文档(Swagger UI 与 ReDoc) |
| 基于标准 | 完全兼容 OpenAPI(原 Swagger)与 JSON Schema 规范 |
| 原生异步 | 基于 ASGI(asyncio),天然支持 WebSocket、SSE、长连接 |
与 Flask / Django 对比:
- Flask:轻量同步框架,WGI,生态灵活但缺乏内置校验与文档,异步支持是后补的;
- Django:大而全的"全家桶"(ORM、Admin、模板),适合内容型网站,REST API 通常配 DRF;
- FastAPI:专注构建 API,异步原生、类型驱动、文档自动化,是当前 Python 构建 REST API / AI 服务后端的主流选择。
技术栈关系:FastAPI(框架逻辑)→ Starlette(Web/ASGI 基础)→ Uvicorn(ASGI 服务器)。理解这个分层对排错很有帮助。
1.2 安装
bash
# 推荐方式:包含 uvicorn、fastapi-cli 等常用工具
pip install "fastapi[standard]"
# 最小安装(只装框架本体)
pip install fastapi
pip install uvicorn # 需要单独装服务器
注意:早期的
fastapi-slim已停止维护,请统一使用fastapi[standard]或fastapi。
1.3 第一个应用
创建 main.py:
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
启动开发服务器:
bash
fastapi dev main.py # fastapi-cli 提供的开发模式,自动热重载
# 或者:
uvicorn main:app --reload # 传统方式
访问 http://127.0.0.1:8000 得到 {"Hello": "World"}。
交互式文档(FastAPI 的招牌功能):
http://127.0.0.1:8000/docs------ Swagger UI,可直接在浏览器里调试接口;http://127.0.0.1:8000/redoc------ ReDoc,另一种文档风格;http://127.0.0.1:8000/openapi.json------ 生成的 OpenAPI 规范(可导入 Postman/Apifox 等)。
1.4 路径操作装饰器速览
python
@app.get("/items/{item_id}") # GET 查询
@app.post("/items") # POST 创建
@app.put("/items/{item_id}") # PUT 整体更新
@app.patch("/items/{item_id}") # PATCH 部分更新
@app.delete("/items/{item_id}") # DELETE 删除
@app.options("/items") # OPTIONS
@app.head("/items") # HEAD
1.5 一个稍微完整的例子
python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="入门示例")
class Item(BaseModel):
name: str
price: float
description: str | None = None # Python 3.10+ 联合类型写法
tax: float | None = None
@app.post("/items/")
def create_item(item: Item):
item_dict = item.model_dump() # Pydantic v2 的序列化方法
if item.tax:
item_dict["total"] = item.price + item.tax
return item_dict
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
试着用错误的类型访问 /items/abc,你会收到一个结构清晰的 422 错误响应------这就是自动校验在工作。
二、路径操作与参数
2.1 路径参数
python
@app.get("/items/{item_id}")
def read_item(item_id: int): # 声明为 int 后自动转换/校验
return {"item_id": item_id}
- 访问
/items/3→{"item_id": 3}(字符串被转成 int); - 访问
/items/abc→ 422 错误,附详细校验信息。
限制参数取值范围(Annotated 写法,官方推荐):
python
from typing import Annotated
from fastapi import Path
@app.get("/items/{item_id}")
def read_item(item_id: Annotated[int, Path(ge=1, le=1000)]):
return {"item_id": item_id}
Path() 常用约束:ge(≥)、le(≤)、gt(>)、lt(<)。
用枚举限制固定取值:
python
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
return {"model": model_name, "value": model_name.value}
路由顺序陷阱 :/users/me 必须定义在 /users/{user_id} 之前 ,否则 "me" 会被当成 user_id 匹配。
2.2 查询参数
函数中不在路径里的参数自动成为查询参数:
python
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10, q: str | None = None):
return {"skip": skip, "limit": limit, "q": q}
/items/?skip=5&limit=20→{"skip": 5, "limit": 20, "q": null}- 有默认值的参数是可选的;无默认值且无
None的参数是必填的。
必填查询参数 + 校验:
python
from fastapi import Query
@app.get("/search/")
def search(q: Annotated[str, Query(min_length=3, max_length=50)]):
return {"q": q}
Query() 常用参数:min_length、max_length、pattern(正则)、alias(对外参数名)、deprecated=True、title/description(文档用)。
接收多个同名的值 / 列表参数:
python
@app.get("/items/")
def read_items(tags: Annotated[list[str] | None, Query()] = None):
return {"tags": tags}
# 访问 /items/?tags=a&tags=b → {"tags": ["a", "b"]}
2.3 请求体
Pydantic 模型作为参数类型时,FastAPI 自动将其识别为请求体:
python
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.post("/items/")
async def create_item(item: Item):
return item
- 请求体为 JSON,若 JSON 格式错误或缺少必填字段 → 422;
- 自 FastAPI 0.131 起 ,框架默认严格校验
Content-Type头(必须是application/json等 JSON 类型),否则拒绝请求。如需兼容不规范的旧客户端,可在FastAPI(strict_content_type=False)中关闭。
嵌套模型:
python
class Image(BaseModel):
url: str
name: str
class Product(BaseModel):
name: str
images: list[Image] | None = None # 嵌套列表自动递归校验
@app.post("/products/")
def create_product(product: Product):
return product
同时接收路径参数、查询参数与请求体(FastAPI 按类型自动区分):
python
@app.put("/items/{item_id}")
def update_item(
item_id: int, # 路径参数
item: Item, # 请求体(Pydantic 模型)
q: str | None = None, # 查询参数
):
return {"item_id": item_id, "item": item, "q": q}
2.4 表单与文件上传
bash
pip install python-multipart # 表单/文件功能需要额外安装
python
from fastapi import Form, File, UploadFile
@app.post("/login/")
def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
return {"username": username}
@app.post("/upload/")
async def upload_file(file: UploadFile):
contents = await file.read() # UploadFile 是异步读取的
return {"filename": file.filename, "size": len(contents),
"content_type": file.content_type}
Form()声明的参数来自application/x-www-form-urlencoded或multipart/form-data表单;UploadFile相比bytes的优势:不会整体载入内存(大文件友好),且有.filename、.content_type元信息,可用.file拿到底层文件对象;File(...)与UploadFile配合可声明默认值或元数据。
多个文件:
python
@app.post("/uploads/")
async def upload_files(files: list[UploadFile]):
return [{"filename": f.filename} for f in files]
2.5 Header 与 Cookie 参数
python
from fastapi import Header, Cookie
@app.get("/headers/")
def read_headers(
user_agent: Annotated[str | None, Header()] = None, # 自动转小写下划线
x_token: Annotated[str | None, Header()] = None, # X-Token → x_token
):
return {"User-Agent": user_agent, "X-Token": x_token}
@app.get("/cookies/")
def read_cookies(session_id: Annotated[str | None, Cookie()] = None):
return {"session_id": session_id}
注意:HTTP 头中的
-在 Python 参数名中用_代替,FastAPI 自动转换(可通过convert_underscores=False关闭)。
三、Pydantic 数据校验
Pydantic v2 是 FastAPI 的数据校验核心(内核用 Rust 实现,性能极高)。理解它等于掌握了 FastAPI 的半壁江山。
3.1 字段类型一览
python
from pydantic import BaseModel
from datetime import datetime, date
from decimal import Decimal
from uuid import UUID
class Demo(BaseModel):
s: str
i: int
f: float
b: bool # "true"/"1"/1 等都会被正确转换
dt: datetime # ISO 8601 字符串自动解析
d: date
dec: Decimal # 精度敏感场景(金额)用 Decimal 而不是 float
uid: UUID
items: list[int] # 列表内元素也递归校验
mapping: dict[str, int]
nested: "Nested | None" = None
3.2 Field 字段约束
python
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=50, examples=["iPhone"])
price: float = Field(..., gt=0, description="必须为正数")
tags: list[str] = Field(default_factory=list)
tax: float | None = Field(None, ge=0, le=0.5)
- 第一个位置参数
...表示必填,None表示默认为 None; - 也可用
Annotated[float, Field(gt=0)]的写法,两者等价,Annotated更利于复用类型别名。
3.3 校验器
python
from pydantic import BaseModel, field_validator, model_validator
class User(BaseModel):
username: str
password: str
password2: str
@field_validator("username")
@classmethod
def username_alpha(cls, v: str) -> str:
if not v.isalnum():
raise ValueError("用户名只能包含字母和数字")
return v.lower() # 校验器可以"顺手"做数据规范化
@model_validator(mode="after") # 跨字段校验
def check_passwords(self) -> "User":
if self.password != self.password2:
raise ValueError("两次密码不一致")
return self
field_validator:单字段校验,v1 的@validator已废弃;model_validator(mode="after"):多字段联合校验;- 校验器抛出
ValueError会被 FastAPI 捕获并转为 422 响应。
3.4 常用进阶功能
python
from pydantic import BaseModel, computed_field, ConfigDict
class Rectangle(BaseModel):
model_config = ConfigDict(from_attributes=True) # 允许从 ORM 对象构造(原 v1 的 orm_mode)
width: float
height: float
@computed_field # 计算属性也会出现在响应中
@property
def area(self) -> float:
return self.width * self.height
# 序列化
r = Rectangle(width=3, height=4)
r.model_dump() # → dict(含 area)
r.model_dump_json() # → JSON 字符串
r.model_dump(exclude={"width"}) # 排除字段
# 反序列化(自动校验+转换)
Rectangle.model_validate({"width": 3, "height": 4})
Rectangle.model_validate_json('{"width": 3, "height": 4}')
v1 → v2 速查 :
dict()→model_dump(),json()→model_dump_json(),parse_obj()→model_validate(),orm_mode→from_attributes,@validator→@field_validator。网上老教程大量是 v1 写法,注意甄别。
四、响应处理
4.1 response_model:过滤输出
核心原则:永远不要直接返回 ORM 对象或含敏感字段的 dict。 用 response_model 声明输出结构:
python
class UserIn(BaseModel): # 输入模型
username: str
password: str
email: str
class UserOut(BaseModel): # 输出模型(不含密码!)
username: str
email: str
@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
return UserOut(**user.model_dump())
返回值会被 response_model 过滤与转换------多出来的字段自动丢弃,缺少的字段自动补 422 报错(提示你代码有 bug)。这天然实现了"输入/输出模型分离"。
过滤与包含:
python
@app.get("/items/", response_model=list[Item], response_model_exclude_unset=True)
def get_items(): ...
# exclude_unset:不返回未显式赋值的字段(适合 PATCH 场景)
# 其他选项:response_model_exclude_none、response_model_include、response_model_exclude
4.2 状态码
python
from fastapi import status
@app.post("/items/", status_code=status.HTTP_201_CREATED) # 创建类接口返回 201
def create_item(item: Item): ...
# 动态设置状态码(Response 注入)
from fastapi import Response
@app.post("/items/")
def create_item(item: Item, response: Response):
response.status_code = status.HTTP_201_CREATED
return item
常用常量:200 OK、201 CREATED、204 NO_CONTENT、400 BAD_REQUEST、401 UNAUTHORIZED、403 FORBIDDEN、404 NOT_FOUND、409 CONFLICT、422 UNPROCESSABLE_ENTITY。
4.3 自定义 Response 类型
python
from fastapi import FastAPI
from fastapi.responses import (
JSONResponse, HTMLResponse, PlainTextResponse,
RedirectResponse, StreamingResponse, FileResponse,
)
app = FastAPI()
@app.get("/html")
def get_html():
return HTMLResponse("<h1>Hello</h1>")
@app.get("/file")
def get_file():
return FileResponse("report.pdf", filename="报表.pdf") # 大文件高效下载
@app.get("/stream")
def get_stream():
def generate():
for i in range(10):
yield f"line {i}\n"
return StreamingResponse(generate(), media_type="text/plain")
直接返回 JSONResponse(可设 cookie/header/status):
python
@app.get("/custom")
def custom():
content = {"message": "hello"}
return JSONResponse(status_code=200, content=content,
headers={"X-Custom": "value"},
set_cookie={"key": "session", "value": "abc"})
快速校验小知识:当端点返回 Pydantic 模型/声明了 Pydantic 响应类型时,FastAPI(0.130+)会用 Pydantic 的 Rust 内核直接序列化 JSON,速度约是原来 jsonable_encoder 路径的 2 倍以上------所以声明
response_model不只是规范,也是性能优化。
4.4 Server-Sent Events(SSE,0.135+ 新特性)
FastAPI 0.135 起内置了 SSE 支持(fastapi.sse),适合服务端单向推送(如 AI 流式输出):
python
from fastapi.responses import StreamingResponse
import asyncio
@app.get("/sse")
async def sse():
async def event_gen():
for i in range(5):
yield f"data: 消息 {i}\n\n"
await asyncio.sleep(1)
return StreamingResponse(event_gen(), media_type="text/event-stream")
0.134+ 还支持通过 yield 流式返回 JSON Lines 与二进制数据,具体见官方文档 "Streaming JSON Lines" 章节。
五、错误处理与异常
5.1 HTTPException
python
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id not in db:
raise HTTPException(
status_code=404,
detail="Item not found", # detail 会出现在响应的 detail 字段
headers={"X-Error": "SomeError"} # 可选:附加响应头
)
return db[item_id]
5.2 自定义异常 + 全局异常处理器
python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
app = FastAPI()
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"{exc.name} 出了点问题"},
)
@app.exception_handler(ValueError) # 也可以覆盖内置异常
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(status_code=400, content={"message": str(exc)})
5.3 覆盖默认的 422 校验错误
python
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"code": "INVALID_PARAM", "errors": exc.errors()},
)
实战中很多团队会统一重写 422 响应格式,使其与业务错误码体系一致。
六、依赖注入系统
依赖注入(DI)是 FastAPI 的灵魂,用于共享逻辑:数据库会话、鉴权、分页参数、限流等。
6.1 基础用法
python
from fastapi import Depends
async def pagination(skip: int = 0, limit: int = 100):
return {"skip": skip, "limit": limit}
@app.get("/items/")
async def list_items(page: dict = Depends(pagination)):
return page
Depends(pagination) 会:执行该函数 → 把返回值注入参数 → 并且该函数自己的参数也会被自动解析校验(skip/limit 变成了接口的查询参数)。
6.2 类作为依赖
python
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def list_items(commons: Annotated[CommonQueryParams, Depends()]):
return {"q": commons.q}
Annotated[CommonQueryParams, Depends()]是简写,等价于Depends(CommonQueryParams)。
6.3 子依赖与缓存
依赖可以嵌套依赖,且同一请求内默认只执行一次 (结果缓存)。需要每次执行时传 use_cache=False:
python
def query_extractor(q: str | None = None):
return q
def query_or_cookie_extractor(
q: Annotated[str, Depends(query_extractor)],
last_q: Annotated[str | None, Cookie()] = None,
):
if not q:
return last_q
return q
6.4 yield 依赖:请求级资源管理(重点)
yield 依赖是管理数据库会话、事务的标准姿势,相当于"每个请求的 try/finally":
python
async def get_db():
db = AsyncSessionLocal()
try:
yield db # yield 之后的代码在响应发送完成后执行
await db.commit()
except Exception:
await db.rollback()
raise
finally:
await db.close()
@app.get("/users/")
async def list_users(db: Annotated[AsyncSession, Depends(get_db)]):
return await db.execute(select(User))
yield前的代码 ≈ 请求前的初始化;yield抛出的异常可被后置依赖的except捕获(相当于请求内事务回滚);- 不要 再用老式的
@app.on_event("startup")管理这类资源(见 9.2 lifespan)。
6.5 全局依赖
python
# 整个应用生效(如强制鉴权)
app = FastAPI(dependencies=[Depends(verify_token)])
# 某个路由集合生效
router = APIRouter(dependencies=[Depends(get_token_header)])
七、中间件与 CORS
7.1 自定义中间件
python
import time
from fastapi import Request
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request) # 继续处理请求
process_ms = (time.perf_counter() - start) * 1000
response.headers["X-Process-Time"] = f"{process_ms:.2f}ms"
return response
call_next抛异常时中间件不会捕获(异常会直接冒泡),需要处理请用 exception_handler 或再包一层 try。
7.2 CORS(前后端分离必备)
python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "https://your-site.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
生产环境不要 用
allow_origins=["*"]搭配allow_credentials=True(浏览器规范禁止,且不安全)。
7.3 其他内置中间件
python
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000) # 压缩响应
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["example.com"])
八、异步编程与并发
8.1 async def 与 def 的选择(必读)
这是 FastAPI 最容易踩坑的地方,规则只有两条:
async def:函数内部可以await。绝不能在里面执行阻塞调用(同步的 requests、time.sleep、同步数据库驱动、CPU 密集计算),否则会卡死整个事件循环,拖垮所有并发请求。def(普通函数) :FastAPI 会自动把它丢到线程池(run_in_threadpool)里执行,不会阻塞事件循环。同步阻塞代码(如调用旧同步库)应放在def端点里。
python
@app.get("/async-ok")
async def ok():
data = await fetch_from_api() # ✅ 异步库(httpx.AsyncClient 等)
return data
@app.get("/sync-ok")
def also_ok():
data = requests.get(url) # ✅ 同步库 + def,走线程池
return data
@app.get("/wrong")
async def wrong():
data = requests.get(url) # ❌ async 里跑同步阻塞调用,事故现场
return data
在 async 函数中必须调用同步阻塞代码时:
python
import anyio
@app.get("/mixed")
async def mixed():
result = await anyio.to_thread.run_sync(blocking_function) # 手动丢线程池
return result
8.2 并发执行多个 IO
python
import httpx
@app.get("/aggregate")
async def aggregate():
async with httpx.AsyncClient() as client:
# 两个请求并发执行,总耗时 ≈ 最慢的那个
r1, r2 = await asyncio.gather(
client.get("https://api.example.com/a"),
client.get("https://api.example.com/b"),
)
return {"a": r1.json(), "b": r2.json()}
8.3 性能直觉
- IO 密集型 + 异步客户端 → 单进程即可支撑数千并发连接;
- CPU 密集型 → 考虑多进程部署(uvicorn workers)、任务队列(Celery/ARQ)或
run_in_executor; - 不要为了"快"给所有端点加
async------def端点走线程池同样正确,误用 async + 阻塞代码反而更慢。
九、高级特性
9.1 后台任务
python
from fastapi import BackgroundTasks
def send_email(email: str, message: str):
... # 耗时操作,响应返回后执行
@app.post("/notify/")
async def notify(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(send_email, email, message="欢迎注册")
return {"message": "通知将在后台发送"}
适合轻量异步(发邮件、写日志)。不适合重任务(重启丢失、无重试)------那是 Celery / ARQ / RQ 的舞台。
9.2 Lifespan 事件(替代 on_event)
@app.on_event("startup") / shutdown 已废弃,新代码统一使用 lifespan:
python
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动:初始化数据库连接池、加载 ML 模型等
ml_model = load_model()
app.state.ml_model = ml_model
yield
# 关闭:清理资源
release_model(ml_model)
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
def predict(x: float):
model = request.app.state.ml_model # 通过 app.state 访问共享资源
return {"result": model.predict(x)}
9.3 路由组织:APIRouter
python
# routers/users.py
from fastapi import APIRouter, Depends
router = APIRouter(
prefix="/users",
tags=["users"], # 文档分组
dependencies=[Depends(verify_token)],
responses={404: {"description": "Not found"}},
)
@router.get("/me") # 实际路径 /users/me
async def read_user_me(): ...
@router.get("/{user_id}")
async def read_user(user_id: int): ...
python
# main.py
from routers import users, items
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router, prefix="/api/v1")
9.4 WebSocket
python
from fastapi import WebSocket, WebSocketDisconnect
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
try:
while True:
data = await ws.receive_text()
await ws.send_text(f"消息已收到: {data}")
except WebSocketDisconnect:
print("客户端断开")
9.5 自定义 OpenAPI / 文档元信息
python
app = FastAPI(
title="商城 API",
description="电商系统接口文档",
version="1.0.0",
docs_url="/docs", # 设为 None 可关闭文档
redoc_url="/redoc",
openapi_url="/openapi.json",
)
@app.get("/items/", tags=["items"], summary="查询商品",
response_description="商品列表")
def list_items():
"""markdown 格式的详细说明,会显示在文档中。"""
return []
9.6 其他值得知道的特性
- 设置与配置 :
pydantic-settings的BaseSettings自动从环境变量/.env读配置(见 12.3); - 静态文件 :
app.mount("/static", StaticFiles(directory="static"), name="static"); - ** lifespan 中启动调度器**:可搭配 APScheduler 实现 cron 任务;
app.frontend()(0.139+):FastAPI 新增了直接托管前端应用的能力,适合"一个进程同时服务 API + 前端"的轻量场景,详见官方文档。
十、安全与认证(OAuth2 + JWT)
10.1 认证方案速览
| 方案 | 适用场景 |
|---|---|
HTTPBasic |
内部工具、快速演示,明文传输(须配 HTTPS) |
APIKeyHeader / APIKeyQuery |
服务间调用、开放平台 |
OAuth2PasswordBearer + JWT |
主流方案:用户名密码换 token,前后端分离标配 |
10.2 完整 JWT 示例(PyJWT)
bash
pip install "fastapi[standard]" pyjwt
python
from datetime import datetime, timedelta, timezone
from typing import Annotated
import jwt # PyJWT
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
# 生产环境务必放环境变量,不要硬编码
SECRET_KEY = "change-me-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # tokenUrl 会出现在文档的 Authorize 按钮里
# ---------- 模拟用户库 ----------
fake_users_db = {
"alice": {
"username": "alice",
"hashed_password": "$2b$12$...", # bcrypt 哈希,见下文说明
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
# ---------- 工具函数 ----------
def create_access_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
# ---------- 依赖:解析当前用户 ----------
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
credentials_exc = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的认证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exc
except jwt.InvalidTokenError:
raise credentials_exc
user = fake_users_db.get(username)
if user is None:
raise credentials_exc
return user
async def get_current_active_user(
current: Annotated[dict, Depends(get_current_user)],
):
if current["disabled"]:
raise HTTPException(status_code=400, detail="用户已禁用")
return current
# ---------- 端点 ----------
@app.post("/token", response_model=Token)
async def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
# OAuth2 规定的表单格式:username/password 字段
user = fake_users_db.get(form.username)
if user is None or not verify_password(form.password, user["hashed_password"]):
raise HTTPException(status_code=401, detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"})
token = create_access_token({"sub": form.username})
return {"access_token": token, "token_type": "bearer"}
@app.get("/users/me")
async def read_me(current: Annotated[dict, Depends(get_current_active_user)]):
return {"username": current["username"]}
使用流程:
POST /token(表单:username、password)→ 拿到access_token;- 后续请求带
Authorization: Bearer <token>头; - 在
/docs页面点右上角 Authorize,直接输入 token 即可调试受保护接口。
密码哈希建议:
python
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(p: str) -> str: return pwd_context.hash(p)
def verify_password(plain: str, hashed: str) -> bool: return pwd_context.verify(plain, hashed)
官方文档在 2025 年后已将 JWT 教程从 python-jose 迁移到 PyJWT ;比较密码时使用
secrets.compare_digest等常数时间比较以防时序攻击。老教程里的jose写法可以放弃。
10.3 更省事的选择
- fastapi-users:内置注册/登录/验证/重置密码的完整用户体系;
- fastapi-login / authlib:轻量认证组件;
- OAuth2 第三方登录(GitHub/Google):Authlib 或官方文档 Security 章节示例。
十一、数据库集成(SQLAlchemy)
11.1 异步 SQLAlchemy 2.0(推荐模板)
bash
pip install sqlalchemy[asyncio] asyncpg # PostgreSQL;MySQL 用 aiomysql/asyncmy
python
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
class Base(DeclarativeBase):
pass
python
# models.py
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from database import Base
class UserORM(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
email: Mapped[str] = mapped_column(String(120), unique=True)
python
# main.py
from typing import Annotated
from fastapi import FastAPI, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database import AsyncSessionLocal
from models import UserORM
from pydantic import BaseModel, ConfigDict
app = FastAPI()
# ------ yield 依赖:每请求一会话 ------
async def get_db():
async with AsyncSessionLocal() as session: # async with 自动关闭
yield session
DbSession = Annotated[AsyncSession, Depends(get_db)]
# ------ Schema:对外接口的输入输出模型 ------
class UserCreate(BaseModel):
name: str
email: str
class UserRead(BaseModel):
model_config = ConfigDict(from_attributes=True) # 关键:允许 ORM → Pydantic
id: int
name: str
email: str
@app.post("/users/", response_model=UserRead)
async def create_user(user_in: UserCreate, db: DbSession):
user = UserORM(**user_in.model_dump())
db.add(user)
await db.commit()
await db.refresh(user) # 拿回自增 id
return user # response_model + from_attributes 自动转换
@app.get("/users/", response_model=list[UserRead])
async def list_users(db: DbSession, skip: int = 0, limit: int = 20):
result = await db.execute(select(UserORM).offset(skip).limit(limit))
return result.scalars().all()
建表(开发期):
python
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# 生产环境建议用 Alembic 管理 migration
11.2 同步方案与 NoSQL
- 同步 SQLAlchemy:把端点写成
def(自动走线程池),依赖里yield session同理; - MySQL 异步驱动:
asyncmy或aiomysql; - MongoDB:
beanie(基于 motor + Pydantic,和 FastAPI 契合度最高)或 ODMantic; - Redis:
redis.asyncio(原 aioredis 已并入 redis-py)。
11.3 关键实践
- 永远用 yield 依赖管理会话,保证请求结束即释放连接;
- ORM 模型与 API Schema 分离 (
UserORMvsUserRead),不要把数据库表结构直接暴露给客户端; - 分页必须有
limit上限,防止被恶意大查询打爆; - 用 Alembic 管理表结构演进,别手工改表。
十二、项目结构与工程化
12.1 推荐目录结构
text
myproject/
├── app/
│ ├── __init__.py
│ ├── main.py # 创建 FastAPI 实例、挂载路由
│ ├── core/
│ │ ├── config.py # pydantic-settings 配置
│ │ ├── security.py # JWT、密码哈希
│ │ └── lifespan.py # 启动/关闭逻辑
│ ├── api/
│ │ ├── deps.py # 公共依赖(get_db、get_current_user)
│ │ └── v1/
│ │ ├── router.py # 汇总 v1 全部子路由
│ │ ├── users.py
│ │ └── items.py
│ ├── models/ # SQLAlchemy ORM 模型
│ ├── schemas/ # Pydantic 输入/输出模型
│ ├── crud/ # 数据访问层(CRUD 函数)
│ └── services/ # 业务逻辑层
├── tests/
│ ├── conftest.py
│ └── test_users.py
├── alembic/ # 数据库迁移
├── .env # 本地环境变量(不进 git)
├── pyproject.toml
└── Dockerfile
分层思路:router(参数解析)→ service(业务)→ crud(数据库),schema 负责出入口数据形态。小项目可以简化,但"路由里不要直接写 SQL"这条底线建议守住。
12.2 配置管理:pydantic-settings
bash
pip install pydantic-settings
python
# core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env")
app_name: str = "My API"
debug: bool = False
database_url: str
secret_key: str
access_token_expire_minutes: int = 30
settings = Settings() # 环境变量 / .env 自动注入,类型自动校验
.env:
ini
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/db
SECRET_KEY=xxxx
DEBUG=true
12.3 API 版本化
python
from fastapi import APIRouter
api_v1 = APIRouter(prefix="/api/v1")
api_v1.include_router(users.router)
api_v2 = APIRouter(prefix="/api/v2")
app.include_router(api_v1)
app.include_router(api_v2)
也可以直接挂在路径前缀 /v1/users,或通过网关(Nginx/Traefik)在部署层做版本分流。
十三、测试
13.1 TestClient 快速上手
python
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_item():
resp = client.get("/items/1")
assert resp.status_code == 200
assert resp.json() == {"item_id": 1}
def test_create_item_validation():
resp = client.post("/items/", json={"name": "x"}) # 缺 price
assert resp.status_code == 422
TestClient(基于 httpx)不需要真正启动服务器,同步调用即可。异步测试可用 httpx.AsyncClient + ASGITransport + pytest-asyncio。
13.2 pytest + fixture 组织测试
python
# conftest.py
import pytest
from fastapi.testclient import TestClient
from main import app
@pytest.fixture
def client():
with TestClient(app) as c: # with 语法会触发 lifespan
yield c
13.3 依赖覆盖(重点)
测试时把"真数据库依赖"换成"内存数据库依赖",不需要改业务代码:
python
def override_get_db():
db = TestSessionLocal() # 指向测试库 / SQLite 内存库
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
def test_list_users(client):
resp = client.get("/users/")
assert resp.status_code == 200
鉴权接口同理:覆盖 get_current_user,返回一个伪造用户即可。
13.4 运行
bash
pip install pytest
pytest -v
pytest tests/test_users.py -k "create" # 只跑匹配的用例
FastAPI 自身的测试覆盖率极高,你只需要测好自己的业务逻辑。官方 0.139+ 还修复了并行测试下路由构建的线程安全问题,pytest-xdist 并行跑测试没问题。
十四、部署
14.1 命令行
bash
# 开发
fastapi dev main.py
# 生产:多进程 worker(CPU 核心数参考值)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
--workers多进程:IO 密集型通常设为 CPU 核数 ~ 2 倍,压测为准;- Windows 下 uvicorn 多 worker 支持有限,生产建议 Docker/Linux;
- 也可以用
gunicorn -k uvicorn.workers.UvicornWorker(Linux)统一管理进程。
14.2 Dockerfile
dockerfile
FROM python:3.12-slim
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
bash
docker build -t myapi .
docker run -p 8000:8000 --env-file .env myapi
14.3 生产拓扑
text
客户端 → Nginx/Caddy(HTTPS 终止、静态资源、限流)
→ uvicorn workers(FastAPI 应用,可多实例横向扩展)
→ PostgreSQL / Redis
要点:
- HTTPS 必须在网关层终结(Let's Encrypt / 云证书);
- 多实例部署时,
--workers与实例数二选一扩容,注意数据库连接池总数; - 健康检查端点:
@app.get("/health")返回{"status": "ok"},供 K8s/负载均衡探活; - 日志:结构化 JSON(structlog/loguru)+ 统一 request id 便于排障;
- 配置全部走环境变量,与镜像解耦(12-Factor 原则)。
十五、最佳实践与常见坑
15.1 最佳实践清单
- 输入/输出分离 :
UserCreate/UserRead独立建模,密码等敏感字段绝不出现在输出模型; - 始终声明
response_model:安全 + 文档 + 性能三重收益; - 公共逻辑沉入依赖:鉴权、DB 会话、分页,别在端点里复制粘贴;
Annotated[...]写法优先:官方推荐,类型可复用、可导出;- 金额用
Decimal,时间统一 UTC(timezone.utc); - schema → service → crud 分层,路由函数保持"薄";
- 环境变量管理配置(pydantic-settings),禁止硬编码密钥;
- Alembic 管理迁移,禁止手改生产表。
15.2 常见坑(血泪总结)
| 坑 | 症状 | 解法 |
|---|---|---|
async def 里调用同步阻塞库 |
所有请求集体变慢/卡死 | 改 def 或 anyio.to_thread.run_sync |
路由顺序颠倒(/users/me 写在 /users/{id} 后) |
me 被当成 id,422 |
固定路径放前面 |
on_event("startup") 不生效/警告 |
旧代码迁移 | 改用 lifespan |
返回 ORM 对象但没设 from_attributes |
500 校验错误 | Schema 加 model_config = ConfigDict(from_attributes=True) |
| Pydantic v1 写法 | AttributeError: model_dump |
用 v2 API(见 3.4 速查表) |
POST 请求没带 Content-Type: application/json |
0.131+ 直接被拒 | 客户端补头,或 strict_content_type=False |
CORS 配了 * + credentials |
浏览器报错 | 显式列出 allow_origins |
| 文件上传 422 | 缺 python-multipart | pip install python-multipart |
生产用 --reload |
性能差且不稳定 | reload 只用于开发 |
| JWT 密钥硬编码/过弱 | 安全事故 | 环境变量 + 32 字节以上随机密钥 |
十六、附录:学习资源
- 官方文档(有中文版) :https://fastapi.tiangolo.com/zh/ ------ 毫不夸张地说,这是 Python 圈最好的框架文档,本教程的深度不及它的 1/3,强烈建议通读;
- 官方教程进阶部分:Security、SQL databases、Bigger Applications、Testing 章节必读;
- 发行说明 :https://fastapi.tiangolo.com/release-notes/ ------ 关注破坏性变更(如 0.131 的 Content-Type 严格校验);
- Pydantic 文档 :https://docs.pydantic.dev/ ------ 数据校验的完整参考;
- SQLAlchemy 2.0 文档 :https://docs.sqlalchemy.org/ ------ ORM 与 asyncio 支持;
- full-stack-fastapi-template (官方模板仓库):https://github.com/fastapi/full-stack-fastapi-template ------ FastAPI + SQLAlchemy + Docker + 前端的完整工程范例,学完本教程后照着读一遍,工程能力会质变。
学习路线建议(针对 Java 背景)
- 第 1 周:先通读"预备篇"补齐 Python 语法,再跑通教程一~五章,每个示例亲手敲一遍(不要复制粘贴);
- 第 2 周:掌握依赖注入 + 异步模型(重点体会与 Spring DI 的异同),写一个带 JWT 登录的增删改查 API;
- 第 3 周:接数据库(SQLAlchemy + Alembic,对照 MyBatis/JPA 理解),补测试,分层组织项目;
- 第 4 周:Docker 化部署,读官方 full-stack 模板源码,开始自己的真实项目。
最后更新:2026-09-01 · 基于 FastAPI 0.141.x / Pydantic v2 / Python 3.10+ 编写