小编近期要着手重构项目,那我们就不能只停留在“怎么用”的层面,得深入到架构设计和最佳实践的层面。
要把一个项目成功迁移到 FastAPI,需要的不只是语法知识,而是如何用 FastAPI 的思维(“FastAPI 式”的写法)去构建应用。
下面这份指南,我依据自己实习改写项目经验把它整理成了 “重构实战笔记” 的风格,同学们可以直接参考这个思路来规划你的代码结构。
🚀 FastAPI 进阶实战:从“能跑”到“优雅重构”
摘要:项目重构在即,如何充分利用 FastAPI 的异步特性与依赖注入系统?本文跳出基础语法,深入解析 Pydantic V2 新特性、依赖注入的高级用法、异步陷阱规避以及推荐的项目目录结构,助你打造高性能、易维护的现代化 API 服务。
前言
哈喽大家好,我是爱摸鱼的打工仔。
最近我也在忙着把老项目往 FastAPI 上迁移。很多小伙伴(包括我自己)刚开始写 FastAPI 时,很容易把它当成“加了类型提示的 Flask”来写。虽然也能跑,但这样就浪费了 FastAPI 最强大的两个武器:依赖注入(Dependency Injection)和异步原生支持。
今天这篇笔记,不讲基础语法,专门聊聊重构时需要注意的“深水区”,帮你避坑,写出真正的 “FastAPI 风格” 代码。
核心思维转变:从“全局变量”到“依赖注入”
在 Flask 或 Django 中,我们习惯用全局变量或者 g 对象来传递数据库会话或当前用户信息。但在 FastAPI 中,依赖注入才是王道。
为什么要改?
重构时,你会发现把数据库连接、用户认证逻辑抽离成“依赖”,你的视图函数(路径操作函数)会变得极其干净,而且测试极其方便。
重构前(伪代码):
# 这种写法在 FastAPI 里不推荐,难以测试
@app.get("/users/{user_id}")
def get_user(user_id: int):
db = SessionLocal() # 每次都要手动连数据库
user = db.query(User).filter(User.id == user_id).first()
return user
重构后(FastAPI 风格):
# 定义依赖(通常在 dependencies.py 或 crud.py 中)
def get_db():
db = SessionLocal()
try:
yield db # 注意这里用 yield,可以在请求结束后自动关闭连接
finally:
db.close()
# 视图函数
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
# db 对象是自动注入的,函数签名即文档
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
打工仔划重点:
重构时,把所有“获取资源”(数据库、Redis、当前登录用户)的逻辑都封装成 Depends。这样你的业务逻辑函数就纯粹得只剩下“处理数据”了。
数据验证升级:拥抱 Pydantic V2
FastAPI 强依赖 Pydantic。如果你之前的项目用的是 Pydantic V1,或者没有严格的数据验证,现在是升级的好时机。
关键变化:
在重构数据模型(Schema)时,注意 Pydantic V2 的一些新特性,比如性能提升和更严格的类型检查。
-
分离输入与输出模型:
不要用一个类通吃。重构时,建议把模型拆细:ItemCreate:创建时用的模型(比如密码必填)。ItemUpdate:更新时用的模型(所有字段都Optional,支持部分更新)。ItemResponse:返回给前端的模型(包含id,create_time,但不包含password)。
-
利用
model_config:from pydantic import BaseModel, ConfigDict class UserOut(BaseModel): # 允许从 ORM 对象直接读取属性 model_config = ConfigDict(from_attributes=True) id: int username: str email: str这样你在返回数据库对象时,FastAPI 能自动把它转成 JSON,不用手写
dict()了。
异步的坑:小心“阻塞”你的高性能
FastAPI 是异步框架,但这并不意味着你可以随便写 async/await。
重构时的最大陷阱:
如果你用了同步的数据库驱动(比如标准的 SQLAlchemy 或 pymysql),却把它放在 async def 函数里,整个服务会被卡死。
解决方案:
-
方案 A(推荐) :使用异步数据库驱动(如
databases库或SQLAlchemy的async支持)。@app.get("/items/") async def read_items(): # 真正的异步查询,不会阻塞主线程 async with database: query = items.select() results = await database.fetch_all(query) return results -
方案 B(兼容旧代码) :如果你暂时不想换数据库驱动,不要把函数定义成
async。
FastAPI 会在后台线程池中运行同步函数,从而避免阻塞。@app.get("/items/") def read_items(): # 注意这里是 def 而不是 async def # 同步代码在后台线程运行,安全 results = db.query(Item).all() return results
打工仔划重点:
重构时检查你的 IO 操作(数据库、文件读写、第三方 API 请求)。如果是同步库,就用 def;如果是异步库,就用 async def。千万别混用!
项目结构重构:拒绝“单文件走天下”
刚开始学习时,我们习惯把所有代码写在 main.py 里。但重构正式项目时,必须模块化。
推荐的标准目录结构(参考 FastAPI 官方推荐):
my_project/
├── app/
│ ├── __init__.py
│ ├── main.py # 入口文件,创建 FastAPI 实例
│ ├── dependencies.py # 存放依赖注入(get_db, get_current_user)
│ ├── models.py # 数据库模型(ORM 表结构)
│ ├── schemas.py # Pydantic 模型(数据验证)
│ ├── crud.py # 数据库增删改查逻辑
│ ├── routers/ # 路由拆分
│ │ ├── items.py
│ │ └── users.py
│ └── core.py # 配置信息(密钥、环境变量)
├── requirements.txt
└── .env
如何拆分路由?
在 app/routers/items.py 中:
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["物品管理"])
@router.get("/")
def read_items():
return [{"name": "Item 1"}, {"name": "Item 2"}]
在 app/main.py 中注册:
from fastapi import FastAPI
from app.routers import items, users
app = FastAPI()
app.include_router(items.router)
app.include_router(users.router)
这样拆分后,你的代码结构清晰,多人协作也不会冲突。
总结
重构不仅仅是换语法,更是思维的重塑。
- 用 依赖注入 解耦业务逻辑和基础设施。
- 用 Pydantic V2 严格定义数据边界,区分输入输出。
- 正确处理 异步与同步,避免性能陷阱。
- 合理规划 目录结构,让项目易于维护。
希望这些实战经验能帮你在重构路上少走弯路。FastAPI 真的很香,一旦上手,你会发现写 API 原来可以这么优雅!
我是爱摸鱼的打工仔,祝大家重构顺利,早点下班!👋