FastAPI 学习教程(写给想学 FastAPI 的 Java 工程师的)

FastAPI 学习教程

一份面向有编程基础学习者的系统性 FastAPI 教程。

读者画像 :初级 Java 开发人员,有面向对象与 Web 开发概念,但没有系统学习过 Python------因此教程开头准备了"第〇章:写给 Java 开发者的 Python 速成",用 Java 概念对照讲清 Python 语法,读完后即可顺畅阅读后续全部章节。

适用环境:Python 3.10+,FastAPI 0.141.x(2026 年中),Pydantic v2。

参考来源:FastAPI 官方文档(含中文版)、官方发行说明及社区最佳实践。


目录

  1. [预备篇:写给 Java 开发者的 Python 速成](#预备篇:写给 Java 开发者的 Python 速成)
  2. [初识 FastAPI](#初识 FastAPI)
  3. 路径操作与参数
  4. [Pydantic 数据校验](#Pydantic 数据校验)
  5. 响应处理
  6. 错误处理与异常
  7. 依赖注入系统
  8. [中间件与 CORS](#中间件与 CORS)
  9. 异步编程与并发
  10. 高级特性
  11. [安全与认证(OAuth2 + JWT)](#安全与认证(OAuth2 + JWT))
  12. 数据库集成(SQLAlchemy)
  13. 项目结构与工程化
  14. 测试
  15. 部署
  16. 最佳实践与常见坑
  17. 附录:学习资源

预备篇:写给 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())

要点:

  • 没有 newDog("旺财") 直接创建对象;
  • 没有访问修饰符 :约定 _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)

读完本章,你已经具备阅读本教程后续所有代码的能力。遇到陌生语法(如 Annotatedyield)时,正文会即时解释;建议把 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_lengthmax_lengthpattern(正则)、alias(对外参数名)、deprecated=Truetitle/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-urlencodedmultipart/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]
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_modefrom_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 OK201 CREATED204 NO_CONTENT400 BAD_REQUEST401 UNAUTHORIZED403 FORBIDDEN404 NOT_FOUND409 CONFLICT422 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 最容易踩坑的地方,规则只有两条:

  1. async def :函数内部可以 await绝不能在里面执行阻塞调用(同步的 requests、time.sleep、同步数据库驱动、CPU 密集计算),否则会卡死整个事件循环,拖垮所有并发请求。
  2. 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-settingsBaseSettings 自动从环境变量/.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"]}

使用流程:

  1. POST /token(表单:username、password)→ 拿到 access_token
  2. 后续请求带 Authorization: Bearer <token> 头;
  3. /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 异步驱动:asyncmyaiomysql
  • MongoDB:beanie(基于 motor + Pydantic,和 FastAPI 契合度最高)或 ODMantic;
  • Redis:redis.asyncio(原 aioredis 已并入 redis-py)。

11.3 关键实践

  • 永远用 yield 依赖管理会话,保证请求结束即释放连接;
  • ORM 模型与 API Schema 分离UserORM vs UserRead),不要把数据库表结构直接暴露给客户端;
  • 分页必须有 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 最佳实践清单

  1. 输入/输出分离UserCreate / UserRead 独立建模,密码等敏感字段绝不出现在输出模型;
  2. 始终声明 response_model:安全 + 文档 + 性能三重收益;
  3. 公共逻辑沉入依赖:鉴权、DB 会话、分页,别在端点里复制粘贴;
  4. Annotated[...] 写法优先:官方推荐,类型可复用、可导出;
  5. 金额用 Decimal ,时间统一 UTC(timezone.utc);
  6. schema → service → crud 分层,路由函数保持"薄";
  7. 环境变量管理配置(pydantic-settings),禁止硬编码密钥;
  8. Alembic 管理迁移,禁止手改生产表。

15.2 常见坑(血泪总结)

症状 解法
async def 里调用同步阻塞库 所有请求集体变慢/卡死 defanyio.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 字节以上随机密钥

十六、附录:学习资源

学习路线建议(针对 Java 背景)

  1. 第 1 周:先通读"预备篇"补齐 Python 语法,再跑通教程一~五章,每个示例亲手敲一遍(不要复制粘贴);
  2. 第 2 周:掌握依赖注入 + 异步模型(重点体会与 Spring DI 的异同),写一个带 JWT 登录的增删改查 API;
  3. 第 3 周:接数据库(SQLAlchemy + Alembic,对照 MyBatis/JPA 理解),补测试,分层组织项目;
  4. 第 4 周:Docker 化部署,读官方 full-stack 模板源码,开始自己的真实项目。

最后更新:2026-09-01 · 基于 FastAPI 0.141.x / Pydantic v2 / Python 3.10+ 编写

相关推荐
飞Link21 分钟前
动作方法中的分割与过度分割方法全解析(含代码实战与踩坑案例)
人工智能·python·算法·计算机视觉
小鹿的周先生31 分钟前
第四章-SpringAI-函数调用&ToolCalling
开发语言·人工智能·python
一只小阿乐1 小时前
java 语法学习 1
java·开发语言·学习
dear_bi_MyOnly1 小时前
函数模块化:企业级项目高效之道
c++·后端·学习
QQ14220784491 小时前
知识库管理和工作学习系统--guada_ai
学习
方知我1 小时前
NumPy一小时速成
开发语言·python·numpy
IPdodo_1 小时前
静态 IP 访问异常排查:403/429 归因与迁移验收
服务器·网络·数据库·python·网络协议·php·性能测试
夕除1 小时前
redis--010
笔记·分布式·学习
andrsted1 小时前
在线刷小程序推荐
学习·微信小程序·小程序·学习方法