易错点、速查表与自测
初学者常见问题
| 问题 | 更准确的理解 |
|---|---|
| “返回字典就等于创建成功并保存了” | 返回数据和写数据库是两件事 |
| “加了类型注解,普通函数就会自动校验” | 框架和 Pydantic 读取声明并执行校验,普通 Python 不会自动如此 |
“str | None 一定能不传” | 是否可省略要看默认值和参数来源 |
| “PATCH 把模型字典全覆盖上去就行” | 需要区分显式字段与默认值,常用 exclude_unset=True |
| “async 能让任意计算变快” | 异步主要帮助等待场景,需要真正异步的调用 |
| “全局事务连接更方便” | 连接池由 lifespan 管理,事务连接只在自己的业务范围内使用 |
| “CORS 配了就安全了” | CORS 是浏览器跨域规则,认证与授权仍由后端完成 |
| “JWT 可以存密码,因为有签名” | 签名不等于加密,不要存秘密 |
| “后台任务一定执行完” | 应用进程任务没有天然持久性和自动重试 |
| “数据库模型改完,旧表会自动变化” | 表结构变化通常需要迁移 |
| “响应模型报错就删掉它” | 先检查返回数据是否违反接口约定 |
| “多进程共享全局字典” | 各进程有独立内存,共享业务状态要用外部存储 |
按现象排查
| 现象 | 排查顺序 |
|---|---|
| 服务没启动 | 读终端错误 → 查 Python 环境 → 查模块与入口 |
| 连接被拒绝 | 服务是否运行 → IP/端口是否对 → 网络入口 |
| 404 | 路径是否对 → 前缀是否对 → 路由是否登记 → 资源是否存在 |
| 405 | 路径可能存在,但 HTTP 方法不匹配 |
| 422 | 读 loc → 对字段名与类型 → 对 JSON 外层结构 → 对参数来源 |
| 401 | Token 是否存在 → 格式、签名、过期和声明 → 用户是否有效 |
| 403 | 身份已识别,但权限或资源归属是否满足 |
| 500 | 读服务器 traceback → 查输出校验、数据库和代码异常 |
| 浏览器跨域失败 | 查实际 Origin → 查预检 → 查方法与请求头 → 查 CORS 配置 |
| 数据重启后消失 | 是否仍存内存 → 是否提交 → 数据库路径或持久卷是否变化 |
| 请求整体卡顿 | 是否阻塞事件循环 → 数据库慢查询 → 外部接口等待 → 资源容量 |
常用导入速查
python
from typing import Annotated, Literal
from fastapi import (
APIRouter,
BackgroundTasks,
Body,
Cookie,
Depends,
FastAPI,
File,
Form,
Header,
HTTPException,
Path,
Query,
Request,
Response,
UploadFile,
WebSocket,
WebSocketDisconnect,
status,
)
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict实际文件只导入用到的内容,不必每次复制全部导入。
核心知识速查
| 需求 | 常用工具 |
|---|---|
| 创建应用 | FastAPI() |
| 登记接口 | @app.get()、@app.post() 等 |
| 路径范围 | Annotated[int, Path(gt=0)] |
| 查询校验 | Annotated[int, Query(ge=1)] |
| JSON 结构 | BaseModel |
| 模型字段规则 | Field(...) |
| 自定义字段规则 | field_validator |
| 限制公开返回字段 | response_model=... |
| 预期 HTTP 错误 | raise HTTPException(...) |
| 复用准备步骤 | Depends(...) |
| 请求资源清理 | 带 yield 的依赖 |
| 应用资源清理 | lifespan |
| 多文件路由 | APIRouter + include_router |
| 局部更新字段 | model_dump(exclude_unset=True) |
| 浏览器跨域 | CORSMiddleware |
| 表单 | Form |
| 上传文件 | UploadFile、File |
| 响应后工作 | BackgroundTasks |
| 接口测试 | TestClient + pytest |
不看前文,回答这些问题
GET /todos/3?done=true中,3 与 true 分别来自哪里?- 为什么 JSON 请求体要用模型描述?
name: str | None与name: str | None = None有什么区别?response_model为什么能帮助隐藏内部字段?- 找不到任务时为什么要
raise HTTPException? Depends(get_demo_user)为什么不写成Depends(get_demo_user())?- 异步接口中直接使用
time.sleep会有什么问题? - ORM 表模型与请求模型有什么不同?
- Tortoise 的
save与refresh_from_db分别做什么? - PATCH 中为什么不应随意用
exclude_none=True? - 登录成功为什么还需要检查任务归属?
- JWT 有签名为什么仍然不能存密码?
- CORS 为什么不能代替登录认证?
- 测试为什么要隔离数据库?
- 为什么生产运行不应使用开发自动重载?
自测参考答案
| 题号 | 答案要点 |
|---|---|
| 1 | 3 是路径参数;true 是查询参数 |
| 2 | 声明结构、类型和规则,便于校验、文档和编辑器提示 |
| 3 | 前者必填但可空;后者可省略且默认为空,模型字段尤其如此 |
| 4 | 按公开模型组织和过滤返回数据;直接 Response 需另行处理 |
| 5 | 用异常中断正常处理,并生成相应 HTTP 错误响应 |
| 6 | 传可调用对象给框架,由它在请求处理时解析参数并调用 |
| 7 | 它是同步阻塞操作,可能阻塞事件循环 |
| 8 | 一个定义持久表结构,一个定义客户端允许提交的数据 |
| 9 | save 写入对象;refresh_from_db 从数据库重新读取字段;多步提交与回滚由事务管理 |
| 10 | 显式 null 可能表示清空字段,不能一律丢掉 |
| 11 | 身份认证只证明是谁,访问具体资源仍需授权 |
| 12 | 普通签名 JWT 载荷可被读取,签名主要提供完整性校验 |
| 13 | 它由浏览器执行跨域规则,不阻止所有客户端调用 |
| 14 | 保持可重复性,避免真实数据和测试互相污染 |
| 15 | 开发重载和正式进程管理目标不同,正式运行还需资源与重启管理 |
官方资料的使用方式
先按本手册建立概念,再按遇到的问题查具体章节。查阅时注意所用 Python、FastAPI、Pydantic 和数据库库的版本,不要混用不同年代的写法。
- FastAPI 官方教程:继续按主题深入。
- Pydantic 官方模型说明:查看解析、校验和序列化细节。
- Tortoise 查询 API:深入异步 ORM 和关联查询。
- MySQL 8.0 官方手册:查 SQL、索引、约束和事务。
- FastAPI 官方测试说明:扩展行为测试。
- FastAPI 官方部署说明:学习持续运行与部署。
版本与代码验证记录
本次数据库修订使用 Python 3.11、FastAPI 0.143.0、Pydantic 2.13.5、Tortoise ORM 1.1.7、asyncmy 0.2.16;数据库固定为 MySQL 8.0,不使用 8.4 代替。
第 1~14、16~20、22~23 章保留基础示例。新数据库示例、真实 MySQL 验证范围及结果在文末记录;它们用于核对本地教学行为,不代表已做生产负载或线上部署验证。