学习 FastAPI 的 Day 1:看懂接口与请求流程

只写几行 Python,浏览器里就出现了一套可以直接测试的接口文档;参数传错时,框架还会主动告诉我们错在哪里。这种"写完马上看到结果"的体验,正是 FastAPI 对新手最有吸引力的地方。

今天讲述一下 FastAPI 的自动接口文档、参数类型和 Pydantic 数据模型,并结合项目代码重点学习依赖注入与中间件的作用、执行顺序及使用场景。

一、自动接口文档与项目启动

截图上半部分是接口列表:蓝色 GET 用于查询,绿色 POST 用于提交。下半部分的 Schemas 来自 Pydantic 模型,点开后可以查看字段名称、类型和必填项。

python 复制代码
from fastapi import FastAPI

# 创建 FastAPI 应用对象,后续路由都注册到 app 上
app = FastAPI()


# 注册一个处理 GET / 请求的路由
@app.get("/")
async def root():
    # 字典会被 FastAPI 自动转换成 JSON 响应
    return {"message": "Hello World"}

app 是应用对象,@app.get("/") 声明请求方式和路径,root() 负责处理请求。修改或新增路由后,/docs 中的接口也会跟着更新

项目可以选择一种依赖管理方式启动,不要混用不同环境:

powershell 复制代码
# uv:当前项目推荐
uv sync
uv run uvicorn main:app --reload

# pip
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install fastapi uvicorn
python -m uvicorn main:app --reload

# Poetry
poetry install
poetry run uvicorn main:app --reload

main:app 中,main 是模块名,app 是应用对象;--reload 会在代码变化后自动重启服务。启动成功后访问 http://127.0.0.1:8000/docs。📌

二、参数类型

FastAPI 接口常见的参数分为路径参数、查询参数和请求体参数。它们出现的位置不同,用途也不同。

路径参数

路径参数写在 URL 路径中,用来确定具体对象:

python 复制代码
# 路径中的 {id} 会传给同名参数 id
@app.get("/book/{id}")
async def get_book(
    # id 必须是整数,并且大于 0、小于 101
    id: int = Path(..., gt=0, lt=101, description="书籍id,取值范围1-100"),
):
    return {"id": id, "title": f"这是第{id}本书"}

/book/3 可以通过校验;/book/abc 不是整数,/book/101 不满足 lt=101,都会返回 422。

查询参数

查询参数写在 ? 后面。分页时,page 表示当前页,pageSize 表示每页数量:

python 复制代码
@app.get("/news/news_list")
async def get_news_list(
    # 不传 page 时默认为第 1 页,页码最小为 1
    page: int = Query(default=1, ge=1, description="当前页码"),
    # 每页默认 10 条,允许的范围是 1~60 条
    pageSize: int = Query(default=10, ge=1, le=60, description="每页数量"),
):
    return {"page": page, "pageSize": pageSize}

请求第 2 页、每页 20 条,可以访问 /news/news_list?page=2&pageSize=20

🔎 路径参数回答"要找哪一个",查询参数回答"要怎么查询"。

请求体参数

POST 接口通常通过请求体接收一组 JSON。user: User 表示请求体需要符合 User 类型:

python 复制代码
@app.post("/register")
async def register(user: User):
    # FastAPI 已经按照 User 模型完成请求体解析和校验
    return user

User 的完整定义会在下一节介绍。

常用参数约束

PathQueryField 都可以使用约束参数,FastAPI 会在执行路由前完成检查:

参数 通俗解释 示例
default 客户端不传时采用的值 default=10
... 参数必须提供,没有默认值 Path(...)
min_length 字符串最少字符数 min_length=2
max_length 字符串最多字符数 max_length=10
gt 必须大于,不包含边界值 gt=0
ge 必须大于等于 ge=1
lt 必须小于,不包含边界值 lt=101
le 必须小于等于 le=60
description 显示在 /docs 中的说明 description="当前页"

💡 g 是 greater,l 是 less,e 是 equal,缩写末尾带 e 就表示包含等号。

三、Pydantic 请求与响应模型

请求模型、响应模型和注册路由放在同一个代码块中,更容易看出它们的关系:

python 复制代码
# 请求模型:检查客户端提交的用户名和密码
class User(BaseModel):
    username: str = Field(default="张三", min_length=2, max_length=10)
    password: str = Field(min_length=3, max_length=20)


# 响应模型:只允许接口返回 username
class RegisterResponse(BaseModel):
    username: str


# response_model 会检查并过滤路由的返回结果
@app.post("/register", response_model=RegisterResponse)
async def register(user: User):
    # 不返回 password,避免泄露用户密码
    return {"username": user.username}

user: User 用来约束请求参数的类型,response_model=RegisterResponse 用来约束响应结果的类型。

Field 的常用参数

Field 用来给模型字段补充默认值、长度、数值范围和文档说明:

参数 通俗解释 示例
default 客户端不传时使用默认值 default="张三"
default_factory 调用函数生成默认值 default_factory=list
min_length 字符串最少字符数 min_length=2
max_length 字符串最多字符数 max_length=10
gt 数字必须大于指定值 gt=0
ge 数字必须大于等于指定值 ge=1
lt 数字必须小于指定值 lt=101
le 数字必须小于等于指定值 le=100
description /docs 中说明字段 description="用户名"
examples /docs 中提供示例值 examples=["小明"]

✅ 开头截图中的 UserNewsSchemas,就是 FastAPI 根据 Pydantic 模型生成的数据结构说明。

四、依赖注入的执行过程

新闻列表和用户列表都需要分页参数。为了避免重复,可以把参数提取成公共依赖:

python 复制代码
# 公共依赖:集中接收并校验分页参数
async def common_parameters(
    page: int = Query(1, ge=1),
    pageSize: int = Query(10, le=60),
):
    # 返回值会注入使用该依赖的路由参数中
    return {"page": page, "pageSize": pageSize}

需要分页的路由通过 Depends 使用它:

python 复制代码
# Depends 会先执行 common_parameters,再把结果传给 commons
@app.get("/news/news_list")
async def get_news_list(commons=Depends(common_parameters)):
    return commons


# 多个路由可以复用同一个依赖,不必重复编写分页校验
@app.get("/user/user_list")
async def get_user_list(commons=Depends(common_parameters)):
    return commons

Depends(common_parameters) 可以理解为:"执行当前路由前,先运行 common_parameters,再把结果交给我。"

请求 /user/user_list?page=2&pageSize=20 时,FastAPI 会:

  1. 找到目标路由,并发现其中的 Depends
  2. 调用 common_parameters,读取并校验分页参数。
  3. {"page": 2, "pageSize": 20} 放入 commons
  4. 最后执行 get_user_list 中的代码。

🧩 Depends 让公共逻辑可以复用,并由 FastAPI 自动安排执行顺序。

五、中间件的执行顺序

当前项目注册了两个 HTTP 中间件:

python 复制代码
# 先注册的中间件位于内层
@app.middleware("http")
async def middleware2(request, call_next):
    # call_next 之前:处理进入应用的请求
    print("中间件2 start")
    # 把请求交给下一层中间件或路由
    response = await call_next(request)
    # call_next 之后:处理路由返回的响应
    print("中间件2 end")
    return response


# 后注册的中间件位于外层,因此请求会先进入这里
@app.middleware("http")
async def middleware1(request, call_next):
    print("中间件1 start")
    # 等待内层中间件和路由执行完成
    response = await call_next(request)
    print("中间件1 end")
    # 将最终响应返回给客户端
    return response

request 是当前请求,call_next 表示继续执行下一层,response 是后续流程返回的响应。await call_next(request) 之前处理进入的请求,之后处理返回的响应。

从代码位置看,middleware2 在上面,middleware1 在下面。后注册的 middleware1 会包在外层,所以顺序是:

text 复制代码
middleware1 start
  middleware2 start
    路由执行
  middleware2 end
middleware1 end

请求进入时按代码位置自下往上,响应返回时再反向执行,这就是"洋葱模型"。

🔄 start 部分处理进入的请求,end 部分处理返回的响应。

六、中间件与依赖注入的选择方法

常见需求可以直接通过下表判断:

实际需求 是否所有请求都需要 路由是否需要结果 选择 原因
记录访问日志 中间件 每个请求都要执行
统计完整请求耗时 中间件 需要包住请求与响应全过程
给所有响应添加响应头 中间件 需要在路由返回后统一修改
统一处理跨域 中间件 属于整个应用的共同规则
维护期间拦截请求 中间件 可以不调用 call_next,直接返回响应
列表接口共用分页参数 依赖注入 只有列表需要,路由还要使用结果
部分接口读取当前用户 依赖注入 用户信息要交给路由继续使用
管理接口检查权限 不一定 依赖注入 权限要求与具体路由有关
多个接口共用查询参数 依赖注入 可以统一校验并注入结果

🧭 关心"每个请求都要做什么",选择中间件;关心"这个路由需要什么",选择依赖注入。

七、结语

现在,我们已经从 /docs 看到了接口如何生成,也理清了参数类型、Pydantic、依赖注入和中间件之间的关系。文章会随着后续学习持续完善,欢迎讨论、补充与纠错。💬

相关推荐
会编程的吕洞宾1 小时前
Spring Boot多环境配置实战 配置文件加载顺序与切换不再翻车
java·后端
额鹅恶饿呃1 小时前
随着CentOS官方停服的时间越来越久,大量仍在使用CentOS7的企业和运维从业者
java·python·算法·c#·ruby
Csvn1 小时前
🐍 Day 11: 调试与诊断 — 从 print 到 pdb 的进阶之路
后端·python
泡海椒1 小时前
告别代码重启部署:JQuick-Java动态规则加载机制原理与实战
后端
HLeiDev1 小时前
邮箱登录与 Google 登录的账号模型设计与实现
后端
苏三说技术2 小时前
阿里开源了一个神级Agent项目
后端
天衍四九-2 小时前
Agent Skills从入门到工程化(十六):面试中如何讲清楚 Agent Skills?
大数据·数据库·人工智能·python·chatgpt·面试
2601_962077982 小时前
Python中数据可视化的新层次
python·plotly·数据可视化·交互式图表·单行代码
zz-zjx2 小时前
Python实用转换模板
开发语言·前端·python