01 项目结构设计
从本章开始,我们把前面学的所有知识组装起来,开发一个完整的博客 API 项目,功能包括:
- 用户管理:注册、查询、修改、删除
- 文章管理:发布、查询、修改、删除(关联作者)
- 标签管理:文章打标签(多对多)
- 列表功能:分页、搜索、排序
- (第五部分再加上:登录鉴权、测试、部署)
1. 为什么不能都写在 main.py 里?
前面练习时我们把所有代码塞在一个 main.py。项目一大这就是灾难:几千行的文件没法维护,模型、校验、路由、业务逻辑搅在一起,改一处怕碰坏另一处。
专业的做法是按职责拆分文件。
2. 项目结构总览
在你的项目目录下建立如下结构(本部分会逐个文件写出来):
blog-api/
├── venv/ # 虚拟环境(不提交 git)
├── requirements.txt # 依赖清单
├── blog.db # SQLite 数据库文件(运行后生成)
└── app/ # 应用主包
├── __init__.py # 空文件,声明这是一个 Python 包
├── main.py # 应用入口:创建 app、挂载路由
├── database.py # 数据库:engine、SessionLocal、get_db 依赖
├── models.py # SQLAlchemy 模型(数据库表结构)
├── schemas.py # Pydantic 模型(接口的输入输出结构)
├── crud.py # 数据库操作函数(业务与 SQL 的隔离层)
└── routers/ # 路由(接口定义),按资源分文件
├── __init__.py
├── users.py # /users 相关接口
└── posts.py # /posts 相关接口
3. 各文件的职责与数据流
一次「创建用户」请求的完整旅程:
POST /users + JSON
│
▼
routers/users.py ← 接口层:接收请求,声明参数和响应模型
│ 用 schemas.UserCreate 校验请求体
│ 通过 Depends(get_db) 拿到数据库会话
▼
crud.py ← 数据访问层:create_user(db, user_in)
│ 把 Pydantic 模型转成 SQLAlchemy 模型对象
▼
models.py + database.py ← 数据层:User 对象经 Session 写入数据库
│
▼
返回 User 对象 → 按 schemas.UserOut 序列化成 JSON → 响应
各层职责一句话:
| 文件 | 职责 | 一句话 |
|---|---|---|
models.py | SQLAlchemy 模型 | 数据库里长什么样 |
schemas.py | Pydantic 模型 | 接口收/发的 JSON 长什么样 |
crud.py | 数据库操作 | 怎么查、怎么存 |
routers/ | 接口定义 | 有哪些 URL,各自的参数和响应 |
database.py | 连接配置 | 连哪个库、怎么拿 Session |
main.py | 组装 | 把上面的东西拼成一个应用 |
新手最容易混淆的一点:models vs schemas
两者都叫「模型」,但完全是两码事:
- models.py(SQLAlchemy):描述数据库表。有主键、外键、索引。
- schemas.py(Pydantic):描述API 的 JSON。有校验规则、字段过滤(比如不返回密码)。
为什么不合成一个?因为它们天然不一致:数据库存密码哈希,API 绝不返回它;API 创建时不传 id,数据库必有 id。分开定义,各管各的。
有个叫 SQLModel 的库尝试把两者合一,感兴趣可以以后了解。教学上分开写更能理解本质。
4. 关于路由拆分:APIRouter
之前接口都注册在 app 上(@app.get(...))。拆分文件后,每个路由文件用 APIRouter 创建一个「小 app」,最后统一挂到主 app 上:
# routers/users.py(示意,下一章写完整版)
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["用户"])
@router.get("") # 实际路径 = prefix + "" = /users
def list_users(): ...
@router.get("/{user_id}") # 实际路径 = /users/{user_id}
def get_user(user_id: int): ...
# main.py(示意)
from fastapi import FastAPI
from app.routers import users, posts
app = FastAPI()
app.include_router(users.router)
app.include_router(posts.router)
prefix="/users":该文件所有接口自动加上/users前缀,不用每个接口重复写tags=["用户"]:/docs里会按 tag 分组展示,文档更清晰
5. 动手:搭好骨架
mkdir blog-api
cd blog-api
python -m venv venv
# 激活虚拟环境(见 01-入门准备/02)
pip install "fastapi[standard]" sqlalchemy
pip freeze > requirements.txt
mkdir app
mkdir app/routers
创建空的包标记文件 app/__init__.py 和 app/routers/__init__.py(内容为空即可)。
__init__.py是干嘛的? 它告诉 Python「这个文件夹是一个包」,这样才能from app import models这样导入。留空即可。
骨架搭好后,从下一章开始逐个填充文件。
6. 运行方式说明
因为代码在 app 包里,启动命令在项目根目录(blog-api/)执行:
fastapi dev app/main.py
# 或
uvicorn app.main:app --reload
本章小结
- 按职责拆分:models(表)、schemas(JSON)、crud(数据库操作)、routers(接口)、database(连接)、main(组装)
- SQLAlchemy 模型和 Pydantic 模型是两码事,必须分开
APIRouter+include_router实现路由文件拆分- 记住数据流:router → crud → models/database → 原路返回
下一章:02-数据库会话与依赖注入 —— 写 database.py 和 models.py。