跳到主要内容

4.1 项目结构设计

从本章开始,我们把前面学的所有知识组装起来,开发一个完整的博客 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.pySQLAlchemy 模型数据库里长什么样
schemas.pyPydantic 模型接口收/发的 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__.pyapp/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 → 原路返回

下一章:4.2 数据库会话与依赖注入 —— 写 database.py 和 models.py。