1. 背景:为什么需要 FastAPI
1.1 Python Web 框架的两代架构
Python Web 框架经历了两个时代:
| 时代 | 规范 | 代表框架 | 模型 | 并发模型 |
|---|---|---|---|---|
| WSGI(Web Server Gateway Interface) | PEP 3333 | Flask、Django、Tornado | 同步请求-响应 | 多进程 / 多线程 |
| ASGI(Asynchronous Server Gateway Interface) | PEP 4843 | Starlette、FastAPI、Quart | 异步事件驱动 | 单进程事件循环 + 协程 |
WSGI 时代的根本矛盾:同步阻塞模型难以承载高并发 I/O。每个请求占用一个线程,线程切换与内存开销大;即使 Flask 内部也有线程池,但整体仍以同步为主,长连接、WebSocket、SSE 支持困难。
ASGI 的出现让 Python 拥有了与 Node.js 同构的单线程事件循环 + 协程 模型:一个进程内可以同时挂起成千上万个 I/O 等待,I/O 期间让出 CPU,事件循环调度其他协程,从而在不增加线程的前提下大幅提升并发吞吐。
1.2 FastAPI 是什么
FastAPI 由 Sebastián Ramírez(@tiangolo)于 2018 年发布,是构建在现代 ASGI 框架 Starlette (负责路由、中间件、WebSocket、测试客户端)与 Pydantic (负责数据校验、序列化、JSON Schema 生成)之上的现代、快速(高性能)的 Web 框架。
它的三个核心卖点:
- 高性能:官方基准测试与 NodeJS、Go 处于同一水平(主要得益于异步与无模板渲染的原生 JSON 序列化)。
- 开发效率极高 :基于 Python 类型提示(Type Hints)自动完成参数解析、数据校验、OpenAPI 文档生成(Swagger UI / ReDoc),一套代码同时是文档、校验器与路由声明。
- 减少缺陷:类型系统 + 自动校验让"参数传错、漏传、类型不对"在请求入口就被拦截,而不是在业务代码深处报错。
1.3 生态定位
Python Web 生态全景
├── 全栈大而全:Django(自带 ORM/Admin/Form)
├── 轻量同步:Flask(最小核心,自由组合)
├── 异步极简:Starlette(只有 ASGI 核心能力)
└── 异步 + 类型 + 文档:FastAPI(Starlette 超集 + Pydantic 自动校验)
FastAPI 与 Starlette 是"超集"关系:FastAPI 里能用的路由、中间件、WebSocket、TestClient 均来自 Starlette,FastAPI 在其上叠加了依赖注入系统 、Pydantic 集成 、OpenAPI 自动文档。
2. 核心 API 说明
2.1 应用实例与路由装饰器
python
from fastapi import FastAPI
app = FastAPI(
title="Demo API", # OpenAPI 文档标题
version="1.0.0",
description="示例服务",
docs_url="/docs", # Swagger UI 地址,设为 None 可关闭
redoc_url="/redoc", # ReDoc 地址
openapi_url="/openapi.json",
)
路由装饰器家族:
| 装饰器 | 用途 |
|---|---|
| @app.get("/path") | GET 请求 |
| @app.post("/path") | POST 请求(创建) |
| @app.put("/path") | PUT 请求(整体更新) |
| @app.patch("/path") | PATCH 请求(部分更新) |
| @app.delete("/path") | DELETE 请求 |
| @app.websocket("/ws") | WebSocket 连接 |
| @app.api_route("/path", methods="GET", "POST") | 多方法 |
每个装饰器均支持 status_code、response_model、tags、summary、deprecated 等参数,这些参数会直接反映到 OpenAPI 文档中。
2.2 参数声明体系(FastAPI 的灵魂)
FastAPI 通过函数签名声明所有参数,自动完成解析与校验:
python
from fastapi import FastAPI, Path, Query, Header, Cookie, Body
from pydantic import BaseModel, Field
class ItemIn(BaseModel):
name: str = Field(min_length=1, max_length=50) # 字段级校验
price: float = Field(gt=0, le=1_000_000)
tags: list[str] = []
@app.get("/items/{item_id}")
def get_item(
item_id: int = Path(gt=0), # 路径参数 + 校验
q: str | None = Query(default=None, max_length=50), # 查询参数
x_token: str | None = Header(default=None), # 请求头
session: str | None = Cookie(default=None), # Cookie
):
return {"item_id": item_id, "q": q}
参数来源推断规则:
| 参数声明位置 | 来源 | 说明 |
|---|---|---|
| 函数签名中与路径 {xxx} 同名 | 路径参数 | 类型自动转换(str/int/float/bool) |
| 继承自 Pydantic BaseModel | 请求体(JSON) | 自动校验、嵌套模型支持 |
| Query() 显式声明 | 查询参数 | 支持校验、别名、废弃标记 |
| Path() 显式声明 | 路径参数 | 同上 |
| Header() / Cookie() | 请求头 / Cookie | Header 自动转下划线、忽略大小写 |
| Body() 显式声明 | 请求体 | 单一标量值入体时需显式声明 |
| File() / UploadFile | 文件(multipart/form-data) | 文件上传 |
| Form() | 表单字段 | 表单提交 |
| Depends() | 依赖注入 | 见 2.3 |
推荐写法(现代最佳实践):使用 Annotated 把校验信息与类型放一起,函数签名更干净:
python
from typing import Annotated
@app.get("/items/{item_id}")
def get_item(
item_id: Annotated[int, Path(gt=0)],
q: Annotated[str | None, Query(max_length=50)] = None,
):
...
2.3 响应模型(response_model)
response_model 是 FastAPI 防止"接口字段泄漏"的核心机制:
python
class ItemOut(BaseModel):
id: int
name: str
price: float
# 不包含内部字段 created_by
@app.post("/items", response_model=ItemOut, status_code=201)
def create_item(item: ItemIn):
return {"id": 1, "name": item.name, "price": item.price, "created_by": "admin"}
特性:
- 自动过滤:返回 dict 中多余的字段(created_by)不会出现在响应中。
- 自动序列化:Pydantic 模型、datetime、UUID 等自动转 JSON 兼容类型。
- 文档联动:OpenAPI 的 response schema 自动生成。
- 常用参数:response_model_exclude_unset=True(只返回显式设置过的字段)、response_model_exclude={"password"}、response_model_include={...}。
如果返回的是裸 dict 且内含 datetime,FastAPI 不会自动序列化,需要用 fastapi.encoders.jsonable_encoder 手动转换,或直接声明 response_model。
2.4 依赖注入(Depends)
依赖注入是 FastAPI 的杀手锏:把"获取资源"(数据库连接、用户、配置)从业务逻辑中剥离,由框架按需解析。
python
from fastapi import Depends
# 依赖函数:每个请求调用一次
def get_db():
db = create_connection()
try:
yield db # yield 之后的部分在请求结束时执行(清理)
finally:
db.close()
def get_current_user(token: str = Header(...)):
if token != "secret":
raise HTTPException(status_code=401, detail="未授权")
return {"username": "admin"}
@app.get("/profile")
def profile(
db=Depends(get_db),
user=Depends(get_current_user),
):
return {"user": user, "db_ok": db is not None}
要点:
- 依赖可以嵌套:Depends(get_db) 中还能再 Depends 其他依赖。
- yield 依赖:yield 之前是初始化,之后是清理(无论请求是否异常都会执行 finally),等价于"上下文管理器"。
- 类依赖:class CommonQueryParams: ... 也可作为 Depends 使用。
- 默认缓存:同一请求内多次 Depends(same_func) 默认只执行一次(use_cache=True),结果共享;需要每次新建实例时可传 use_cache=False。
- 全局依赖:FastAPI(dependencies=Depends(verify_api_key)) 或 APIRouter(dependencies=...),对路由组统一生效。
2.5 中间件
python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 或 ["*"]
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
内置常用中间件:
| 中间件 | 用途 |
|---|---|
| CORSMiddleware | 跨域资源共享(浏览器跨域必备) |
| GZipMiddleware | 响应 gzip 压缩 |
| TrustedHostMiddleware | 校验 Host 头防 DNS 重绑定 |
| BaseHTTPMiddleware / @app.middleware("http") | 自定义请求/响应处理 |
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)
response.headers["X-Process-Time"] = str(time.perf_counter() - start)
return response
注意:@app.middleware("http") 基于 BaseHTTPMiddleware,性能略低于 ASGI 纯中间件;对性能极端敏感可写原生 ASGI 中间件(call(scope, receive, send))。
2.6 异常处理
python
from fastapi import HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@app.exception_handler(RequestValidationError)
async def validation_handler(request, exc):
return JSONResponse(status_code=422, content={"detail": exc.errors()})
class BizError(Exception):
def __init__(self, code: int, msg: str):
self.code, self.msg = code, msg
@app.exception_handler(BizError)
async def biz_handler(request, exc: BizError):
return JSONResponse(status_code=200, content={"code": exc.code, "msg": exc.msg})
@app.get("/risk")
def risk():
raise BizError(10001, "业务校验失败")
- HTTPException:标准 HTTP 错误,自带 status_code 与 detail。
- RequestValidationError:参数校验失败默认返回 422,可自定义格式。
- 自定义异常 + @app.exception_handler:实现业务错误码体系的标准姿势。
- 注意异常处理器的作用域:路由级 < 应用级,局部优先。
2.7 其他核心 API
| 能力 | API | 说明 |
|---|---|---|
| 路由模块化 | APIRouter(prefix="/users", tags="用户") + app.include_router(router) | 大型项目按业务拆分 |
| 后台任务 | BackgroundTasks 参数 + tasks.add_task(func, arg) | 请求返回后异步执行(轻量) |
| 文件上传 | file: UploadFile = File(...) | 流式读取,await file.read() |
| WebSocket | @app.websocket("/ws") + await ws.accept()/send_text()/receive_text() | 实时双向通信 |
| 静态文件 | app.mount("/static", StaticFiles(directory="static")) | 挂载静态目录 |
| 生命周期 | lifespan 异步上下文管理器 | 替代废弃的 on_event("startup") |
| 安全认证 | OAuth2PasswordBearer(tokenUrl="/login") | 配合 JWT 做登录鉴权 |
| 测试 | from fastapi.testclient import TestClient | 基于 httpx 的同步测试客户端 |
lifespan 正确写法(新项目必须使用):
python
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动时:初始化连接池、加载模型
app.state.pool = await create_pool()
yield
# 关闭时:释放资源
await app.state.pool.close()
app = FastAPI(lifespan=lifespan)
3. 详细使用说明(完整可运行示例)
3.1 工程骨架
myapi/
├── main.py # 应用入口
├── routers/
│ ├── __init__.py
│ ├── items.py # 商品路由
│ └── users.py # 用户路由
├── schemas.py # Pydantic 模型
├── deps.py # 依赖(get_db/get_current_user)
└── requirements.txt
requirements.txt:
fastapi==0.115.* uvicorn[standard]==0.30.* pydantic==2.* sqlalchemy==2.* aiosqlite==0.20.* httpx==0.27.* # 测试客户端依赖
3.2 最小可运行应用
python
# main.py
from fastapi import FastAPI
app = FastAPI(title="最小示例")
@app.get("/")
def root():
return {"message": "Hello Marvis"}
启动:
uvicorn main:app --reload --port 8000
# 浏览器访问 http://127.0.0.1:8000/docs 查看自动生成的 Swagger 文档
3.3 完整 CRUD + 依赖注入 + 异步数据库
python
# schemas.py
from pydantic import BaseModel, Field
class ItemCreate(BaseModel):
name: str = Field(min_length=1, max_length=50)
price: float = Field(gt=0)
class ItemOut(ItemCreate):
id: int
python
# deps.py
import asyncio
from fastapi import Header, HTTPException
# 演示用的"伪用户"依赖,实际项目替换为 JWT 校验
def get_current_user(x_token: str = Header(...)):
if x_token != "marvis-secret":
raise HTTPException(status_code=401, detail="无效令牌")
return {"username": "marvis"}
async def get_db():
# 演示连接池,真实项目用 SQLAlchemy async_sessionmaker
db = {"conn": f"conn-{id(object())}"}
try:
yield db
finally:
await asyncio.sleep(0) # 模拟释放
python
# routers/items.py
from fastapi import APIRouter, Depends, HTTPException
from deps import get_current_user, get_db
router = APIRouter(prefix="/items", tags=["商品"])
_items: dict[int, dict] = {}
_counter = 0
@router.post("", response_model=dict, status_code=201)
async def create_item(payload: dict, db=Depends(get_db)):
global _counter
_counter += 1
item = {"id": _counter, **payload}
_items[_counter] = item
return item
@router.get("/{item_id}")
async def get_item(item_id: int, user=Depends(get_current_user)):
if item_id not in _items:
raise HTTPException(status_code=404, detail="商品不存在")
return _items[item_id]
@router.delete("/{item_id}", status_code=204)
async def delete_item(item_id: int):
_items.pop(item_id, None)
return None
python
# main.py
from fastapi import FastAPI
from routers import items
app = FastAPI(title="CRUD 示例")
app.include_router(items.router)
3.4 文件上传 + 后台任务
python
import shutil
from pathlib import Path
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import FileResponse
from fastapi import BackgroundTasks
UPLOAD_DIR = Path("./uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
app = FastAPI()
def _compress(path: Path):
"""后台任务:模拟耗时压缩"""
import time
time.sleep(2)
print(f"压缩完成: {path.name}")
@app.post("/upload")
async def upload(
background: BackgroundTasks,
file: UploadFile = File(...),
):
dest = UPLOAD_DIR / file.filename
with dest.open("wb") as f:
shutil.copyfileobj(file.file, f) # 流式写入,避免大文件占满内存
background.add_task(_compress, dest)
return {"saved": dest.name, "size": dest.stat().st_size}
3.5 WebSocket 回声服务
python
from fastapi import WebSocket, WebSocketDisconnect
@app.websocket("/ws/echo")
async def ws_echo(websocket: WebSocket):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"echo: {data}")
except WebSocketDisconnect:
print("客户端断开")
3.6 测试(pytest + TestClient)
python
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_item():
resp = client.post("/items", json={"name": "咖啡", "price": 29.9})
assert resp.status_code == 201
assert resp.json()["id"] == 1
def test_auth_required():
resp = client.get("/items/1")
assert resp.status_code == 401
4. 常错点 / 坑(20 条高发坑点)
坑 1:async def 里调同步阻塞库,卡死整个事件循环
python
# ❌ 错误:requests 是同步阻塞的,在事件循环线程里执行会阻塞所有并发请求
@app.get("/proxy")
async def proxy():
return requests.get("https://example.com").text
# ✅ 正确:用异步 HTTP 客户端
import httpx
@app.get("/proxy")
async def proxy():
async with httpx.AsyncClient() as client:
r = await client.get("https://example.com")
return r.text
判别规则:I/O 密集用 async def + 异步库;CPU 密集(压缩、加密、图像处理)用普通 def(FastAPI 会自动丢到线程池),或显式 await run_in_threadpool(func, ...)。
坑 2:路由函数用 def 还是 async def 的选择失误
- 普通 def 路由:FastAPI 放入线程池执行,适合阻塞/CPU 任务,不阻塞事件循环。
- async def 路由:事件循环直接执行,必须保证函数内没有阻塞调用。
- 误区:认为"想快就必须全部 async"。实际上混合使用没问题------同步路由自动在线程池,两者性能对 IO 场景差异不大,关键是async 路由内绝不能阻塞。
坑 3:response_model 缺失导致字段泄漏 / 序列化报错
python
class User(BaseModel):
username: str
password_hash: str # 内部字段
# ❌ 直接返回 model 或 dict,password_hash 会泄漏到响应里
@app.get("/user")
def get_user():
return {"username": "admin", "password_hash": "xxx"}
# ✅ 声明 response_model 自动过滤
@app.get("/user", response_model=UserOut)
def get_user():
...
同时 datetime 等类型在裸 dict 中不会自动转字符串,会抛 TypeError: Object of type datetime is not JSON serializable。
坑 4:Pydantic v1/v2 迁移踩坑(API 大改)
| v1 | v2 | 说明 |
|---|---|---|
| @validator("x") | @field_validator("x") | 校验器装饰器 |
| class Config: orm_mode = True | model_config = ConfigDict(from_attributes=True) | ORM 模式 |
| obj.dict() | obj.model_dump() | 转 dict |
| Model.parse_obj(d) | Model.model_validate(d) | 从 dict 构建 |
| Model.parse_raw(s) | Model.model_validate_json(s) | 从 JSON 构建 |
| fields | model_fields | 字段元信息 |
| SecretStr.get_secret_value() | 同左 | 不变 |
2023 年 6 月起 FastAPI 0.100+ 默认 Pydantic v2,旧代码迁移务必按表对照修改。
坑 5:Depends 的缓存行为导致"每次请求共享同一对象"
python
# 危险模式:依赖返回可变对象且被修改
def get_counter():
return {"count": 0}
@app.get("/inc")
def inc(c=Depends(get_counter)):
c["count"] += 1
return c
默认同一请求内缓存没问题,但跨请求不共享 (每次请求重新解析)。真正的坑在同一请求内多次 Depends 默认只执行一次依赖函数,如果依赖内部有随机数/时间戳且希望每次不同,需 Depends(func, use_cache=False)。
坑 6:路径顺序导致静态路由被动态路由吞掉
# ❌ /users/{user_id} 先声明,/users/me 永远匹配不到(user_id="me" 转换失败报 422)
@app.get("/users/{user_id}")
def get_user(user_id: int): ...
@app.get("/users/me")
def get_me(): ...
# ✅ 静态路由放在动态路由之前
坑 7:同步 SQLAlchemy 在 async 路由中使用,阻塞事件循环
python
# ❌ 同步 Session 查询是阻塞的
@app.get("/items")
async def list_items():
return db.query(Item).all() # 阻塞事件循环
# ✅ 方案 A:路由改普通 def,交给线程池
@app.get("/items")
def list_items():
return db.query(Item).all()
# ✅ 方案 B:用 SQLAlchemy 2.0 异步扩展 + async_sessionmaker
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
engine = create_async_engine("sqlite+aiosqlite:///./app.db")
Session = async_sessionmaker(engine)
@app.get("/items")
async def list_items(session=Depends(get_async_db)):
return (await session.execute(select(Item))).scalars().all()
坑 8:请求体解析失败的 422 与 Content-Type
- POST/PUT 带请求体必须设置 Content-Type: application/json,否则 422。
- 前端传 application/x-www-form-urlencoded 时需改用 Form() 声明。
- 客户端 SDK 经常漏配 Content-Type,排查 422 先看请求头。
坑 9:类型提示缺失,OpenAPI 文档与校验失效
python
# ❌ 没有类型注解:q 不会被识别为查询参数,文档缺失
@app.get("/search")
def search(q): ...
# ✅ 必须写类型
@app.get("/search")
def search(q: str): ...
FastAPI 的一切魔法都建立在类型提示之上,漏写类型 = 参数变成"黑洞"。
坑 10:可选参数默认值写法错误
python
# ❌ Python 3.10+ 语法错误/语义错误
def f(q: str | None): ... # 无默认值 → FastAPI 视为必填!
def f(q: Optional[str]): ... # 同上,必填!
# ✅ 正确:必须给默认值 None
def f(q: str | None = None): ...
# 或 Annotated 风格
def f(q: Annotated[str | None, Query()] = None): ...
Pydantic 字段同理:tags: liststr = \[\](注意不要用可变默认值 \[\] 做函数默认参数------Pydantic 会深拷贝,这里安全;但普通函数里仍要避免)。
坑 11:CORS 未配置,浏览器跨域请求被拦截
前端(localhost:3000)调用后端(localhost:8000)报 CORS 错误,但 curl/Postman 正常------不是代码问题,是浏览器同源策略。必须显式配置 CORSMiddleware。注意 allow_origins="\*" 与 allow_credentials=True 不能同时使用(浏览器规范限制)。
坑 12:继续用废弃的 on_event
python
# ❌ 已废弃(FastAPI 0.93+ 起推荐 lifespan)
@app.on_event("startup")
async def init(): ...
# ✅ 用 lifespan
@asynccontextmanager
async def lifespan(app):
app.state.redis = await redis.from_url(...)
yield
await app.state.redis.close()
多个 on_event("startup") 的执行顺序不保证,lifespan 单一入口,顺序由代码控制。
坑 13:上传文件不关闭、不限制大小
- UploadFile 底层是 SpooledTemporaryFile,不关闭会泄漏临时文件句柄。
- 不限制上传大小可被恶意打满磁盘/内存------流式写入(shutil.copyfileobj)且设置 Content-Length 上限校验。
坑 14:BackgroundTasks 的适用边界被滥用
BackgroundTasks 只是"响应返回后在同一进程里跑个函数",没有重试、持久化、分布式 。邮件通知、简单日志可用;真正的重任务(批量处理、定时任务)应上 Celery / ARQ / RQ。另外 BackgroundTasks 里抛异常不会影响响应,但也不会被记录,需自行 try/except。
坑 15:WebSocket 与 HTTP 的异常处理不同
- WebSocket 不能用 HTTPException(那是 HTTP 通道的),断线要捕获 WebSocketDisconnect。
- await websocket.send_* 与 receive_* 不能并发调用,多路推送需自己管理队列。
- 不 await websocket.accept() 就 send 会抛 RuntimeError。
坑 16:多 worker 下内存状态不一致
# --workers 4 时,每个 worker 是独立进程,内存 dict 不共享!
uvicorn main:app --workers 4
用 _items: dict 存数据的 Demo 在单 worker 下没问题,多 worker 部署后请求打到不同进程,数据互相看不见。有状态数据必须落数据库/Redis。另外 --reload 与 --workers 不能同时使用。
坑 17:统一响应包装(code/message/data)的实现误区
python
# ❌ 每个路由手写包装,字段名不一致、漏包装
# ✅ 用 response_model 包装 + 业务异常处理器兜底
class ApiResponse(BaseModel):
code: int = 0
message: str = "ok"
data: dict | list | None = None
@app.get("/x", response_model=ApiResponse)
def x():
return ApiResponse(data={"a": 1})
也可以写一个 @app.middleware 统一包装,但要小心静态文件、健康检查等非 JSON 响应被误包装。
坑 18:jsonable_encoder 与 datetime 序列化
裸 dict 返回 datetime.now() 报错。三种解法:
- 声明 response_model(推荐);
- jsonable_encoder(data) 手动转换;
- 自定义 JSONResponse 的 default=str。
坑 19:依赖里 raise 与 yield 的异常清理顺序
yield 依赖中,如果 yield 之前抛异常,后面的清理代码不会执行;请求处理中抛异常,清理代码(finally)会执行。数据库连接池务必用 try/finally 包住,确保连接归还。
坑 20:测试时事件循环与 TestClient 混用
- TestClient 是同步的(内部跑一个独立事件循环),在 pytest-asyncio 的 async def 测试里直接调用 client.get 会创建新的事件循环,导致状态不一致------要么全同步测试,要么用 async with AsyncClient(...)。
- TestClient 依赖 httpx,记得装 httpx,否则 ModuleNotFoundError。
5. 总结:适用场景与选型
5.1 适用场景
| 场景 | 推荐度 | 说明 |
|---|---|---|
| 快速搭建 REST API / 内部工具服务 | ★★★★★ | 类型 + 文档 + 校验三合一,开发效率极高 |
| 机器学习模型推理服务 | ★★★★★ | 异步吞吐 + Pydantic 校验入参,行业标配 |
| 中小型微服务 | ★★★★☆ | 依赖注入天然适合业务拆分 |
| 实时通信(WebSocket/SSE) | ★★★★☆ | ASGI 原生支持 |
| 超大型全栈项目(含模板渲染、复杂 Admin) | ★★★☆☆ | 模板能力弱于 Django,Admin 需第三方 |
| 高 CPU 密集计算服务 | ★★☆☆☆ | 需要配合线程池/外部计算集群 |
5.2 与主流框架对比
| 维度 | FastAPI | Flask | Django | Starlette |
|---|---|---|---|---|
| 异步 | 原生 ASGI | 需扩展(Quart) | 3.1+ 异步视图 | 原生 |
| 自动 OpenAPI | 内置 | 需 flasgger | 需 drf-spectacular | 无 |
| 数据校验 | Pydantic 自动 | 手写/序列化库 | DRF Serializer | 手写 |
| 依赖注入 | 内置 | 需 flask-injector | 需 django-injector | 无 |
| 全家桶 | 无(微框架) | 微框架 | 全栈 | 微框架 |
| 学习曲线 | 平缓 | 平缓 | 陡 | 平缓 |
5.3 生产部署要点
# 开发
uvicorn main:app --reload
# 生产(单机多进程)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# 生产(推荐:反向代理 + 进程管理)
# Nginx → (gunicorn -k uvicorn.workers.UvicornWorker 或 裸 uvicorn) → 应用
生产环境标配:Nginx 反代(TLS 终结)+ uvicorn 多 worker + 超时配置 + 日志收集 + 健康检查端点(/healthz 返回 200)。需要横向扩展时前面加负载均衡,会话状态放 Redis,任务队列上 Celery。
6. FAQ 速查表
| 问题 | 答案 |
|---|---|
| 如何查看接口文档? | 启动后访问 /docs(Swagger)或 /redoc(ReDoc) |
| 为什么我的接口在文档里没有参数? | 参数缺类型注解,FastAPI 无法识别 |
| 422 是什么错误? | 请求参数/请求体验证失败,看响应里的 detail 定位具体字段 |
| 如何让某个字段必填? | 不设默认值即为必填;Query(..., min_length=1) |
| 如何统一异常返回格式? | 自定义异常类 + @app.exception_handler |
| 如何做登录鉴权? | OAuth2PasswordBearer + python-jose 签发/校验 JWT |
| 如何连接数据库? | SQLAlchemy 2.0 + async_sessionmaker(异步)或同步 Session + def 路由 |
| 为什么 curl 正常但浏览器报 CORS? | 缺 CORSMiddleware 配置 |
| 如何在启动时初始化资源? | lifespan 异步上下文管理器 |
| 如何处理大文件上传? | UploadFile + 流式写入 + 大小限制 |
| 后台任务和 Celery 怎么选? | 轻量非关键任务用 BackgroundTasks;重任务用 Celery/ARQ |
| 如何做性能压测? | locust / wrk,关注 p99 延迟与错误率 |
| 版本兼容注意什么? | FastAPI 0.100+ 用 Pydantic v2;Python 3.8 以下需 typing.Optional |