在FastAPI后端开发中,随着业务需求的迭代,代码量会不断增加,若将所有代码堆砌在单个文件中,会导致代码可读性差、维护成本高、难以协作开发。规范的工程结构与模块化路由设计,能实现代码分层解耦、职责清晰,是企业级FastAPI项目的核心开发规范。本文将详细拆解标准工程结构的各模块职责,结合可直接运行的代码示例,讲解模块化路由的创建、封装与挂载,全程围绕工程结构和模块化路由核心展开,确保内容实用、可落地。
一、FastAPI 标准工程结构(树形结构解析)
一个规范的FastAPI后端工程,需按照“分层解耦”的原则设计目录结构,将不同职责的代码拆分到对应目录,实现“各司其职、互不干扰”。以下是适配中小型后端项目的标准工程结构,可直接用于实际开发,结构清晰且易于扩展。
book_backend/
├─ crud/ # 数据库操作封装层(核心业务逻辑)
│ ├─ book.py # 书籍相关增删改查操作封装
│ └─ user.py # 用户相关增删改查操作封装
├─ models/ # 数据库模型层(SQLAlchemy ORM)
│ ├─ book.py # 书籍表模型定义
│ └─ user.py # 用户表模型定义
├─ routers/ # 模块化路由层(按业务模块拆分)
│ ├─ book_router.py # 书籍相关接口路由
│ └─ user_router.py # 用户相关接口路由
├─ schemas/ # 数据校验层(Pydantic模型)
│ ├─ book.py # 书籍请求/响应数据校验模型
│ └─ user.py # 用户请求/响应数据校验模型
├─ config/ # 项目配置层
│ └─ db_config.py # 数据库连接配置、会话初始化
├─ utils/ # 通用工具层
│ └─ common.py # 全局通用工具函数(异常处理、格式转换等)
├─ main.py # 项目入口文件(App初始化、路由挂载)
└─ test_api.http # 接口测试文件(快速调试接口)
该结构的核心优势的是“分层解耦”,每个目录都有明确的职责,避免代码混乱,同时便于团队协作开发——开发人员可专注于某一模块(如路由层、业务层),无需关注其他模块的实现细节。
二、各层目录详细解析(职责与核心作用)
标准工程结构的每一层都有严格的职责划分,结合SQLAlchemy ORM与Pydantic技术,实现从请求接收、数据校验到数据库操作的完整链路,各层协同工作且互不依赖。以下是各目录的详细解析,搭配核心代码片段说明其作用。
1. config/:项目配置层
用于存放项目全局配置,核心是数据库连接配置、SQLAlchemy异步引擎与会话初始化,将配置与业务代码分离,便于后续修改配置(如切换数据库环境)。
# config/db_config.py
from datetime import datetime
from sqlalchemy import func
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase
# 数据库连接配置(可根据实际环境修改)
ASYNC_DATABASE_URL = "mysql+aiomysql://root:654321@localhost:3306/book_backend?charset=utf8"
# 创建异步数据库引擎
async_engine = create_async_engine(
ASYNC_DATABASE_URL,
echo=True, # 输出SQL日志,便于调试
pool_size=8, # 连接池活跃连接数
max_overflow=15 # 额外允许的临时连接数
)
# 异步会话工厂(用于创建数据库会话)
AsyncSessionLocal = async_sessionmaker(
bind=async_engine,
class_=AsyncSession,
expire_on_commit=False # 提交后会话不过期
)
# 数据库模型基类(所有模型类继承此类)
class Base(DeclarativeBase):
create_time: Mapped[datetime] = mapped_column(
DateTime,
insert_default=func.now(),
comment="创建时间"
)
update_time: Mapped[datetime] = mapped_column(
DateTime,
insert_default=func.now(),
onupdate=func.now(),
comment="修改时间"
)
# 数据库会话依赖项(供路由层调用,自动管理会话生命周期)
async def get_db():
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
2. models/:数据库模型层
基于SQLAlchemy ORM定义数据库表模型,每个模型类与数据库中的一张表一一对应,用于映射数据库结构,是数据库操作的基础。模型类仅定义表结构,不包含任何业务逻辑。
# models/book.py
from sqlalchemy import String, Float, Integer
from sqlalchemy.orm import Mapped, mapped_column
from config.db_config import Base
# 书籍表模型(与数据库book表一一对应)
class Book(Base):
__tablename__ = "book"
id: Mapped[int] = mapped_column(Integer, primary_key=True, comment="书籍主键ID")
book_title: Mapped[str] = mapped_column(String(255), comment="书籍标题")
book_author: Mapped[str] = mapped_column(String(100), comment="书籍作者")
book_price: Mapped[float] = mapped_column(Float, comment="书籍售价")
book_publisher: Mapped[str] = mapped_column(String(255), comment="出版社")
3. schemas/:数据校验层
基于Pydantic定义请求体和响应体模型,用于校验前端传入的请求参数(如数据类型、必填项),同时规范接口返回数据的格式,避免无效数据进入业务层和数据库。
# schemas/book.py
from pydantic import BaseModel, Field
# 书籍新增请求体模型(校验前端传入的新增参数)
class BookCreate(BaseModel):
book_title: str = Field(..., description="书籍标题,不能为空")
book_author: str = Field(..., description="书籍作者,不能为空")
book_price: float = Field(gt=0, description="书籍售价,必须大于0")
book_publisher: str = Field(..., description="出版社,不能为空")
# 书籍更新请求体模型(校验前端传入的更新参数)
class BookUpdate(BaseModel):
book_title: str | None = Field(None, description="书籍标题,可选")
book_author: str | None = Field(None, description="书籍作者,可选")
book_price: float | None = Field(None, gt=0, description="书籍售价,必须大于0(可选)")
book_publisher: str | None = Field(None, description="出版社,可选")
# 书籍响应体模型(规范接口返回数据格式)
class BookResponse(BaseModel):
id: int
book_title: str
book_author: str
book_price: float
book_publisher: str
# 允许返回ORM对象的属性(自动转换为字典)
class Config:
from_attributes = True
4. crud/:业务逻辑层(数据库操作封装)
封装所有数据库增删改查操作,将业务逻辑与路由层分离,路由层仅负责调用crud层的方法,不编写任何原生数据库操作代码。这样既能提高代码复用性,也便于后续维护和修改业务逻辑。
# crud/book.py
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from models.book import Book
from schemas.book import BookCreate, BookUpdate
# 新增书籍(封装新增逻辑)
async def create_book(db: AsyncSession, book: BookCreate):
book_obj = Book(**book.model_dump())
db.add(book_obj)
await db.commit()
await db.refresh(book_obj)
return book_obj
# 根据ID查询书籍
async def get_book_by_id(db: AsyncSession, book_id: int):
stmt = select(Book).where(Book.id == book_id)
result = await db.execute(stmt)
return result.scalar_one_or_none()
# 更新书籍信息
async def update_book(db: AsyncSession, book_id: int, book_data: BookUpdate):
# 先查询书籍是否存在
book = await get_book_by_id(db, book_id)
if not book:
return None
# 批量更新字段(仅更新传入的非None参数)
update_data = book_data.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(book, key, value)
await db.commit()
await db.refresh(book)
return book
# 删除书籍
async def delete_book(db: AsyncSession, book_id: int):
book = await get_book_by_id(db, book_id)
if not book:
return False
await db.delete(book)
await db.commit()
return True
# 查询书籍列表(支持分页)
async def get_book_list(db: AsyncSession, skip: int = 0, limit: int = 10):
stmt = select(Book).offset(skip).limit(limit)
result = await db.execute(stmt)
return result.scalars().all()
5. routers/:模块化路由层
按业务模块拆分路由,将不同业务的接口(如书籍接口、用户接口)拆分到独立文件中,使用FastAPI的APIRouter创建模块化路由,再统一挂载到主应用中。这样能避免所有接口堆砌在main.py中,提升代码可读性和可维护性。
# routers/book_router.py
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from config.db_config import get_db
from crud import book as book_crud
from schemas.book import BookCreate, BookUpdate, BookResponse
# 创建模块化路由实例
# prefix:路由前缀,所有接口URL都会自动添加该前缀
# tags:接口分组标签,用于API文档自动分组
book_router = APIRouter(prefix="/api/books", tags=["书籍管理"])
# 新增书籍接口
@book_router.post("/add", response_model=BookResponse)
async def add_book(book: BookCreate, db: AsyncSession = Depends(get_db)):
return await book_crud.create_book(db, book)
# 根据ID查询书籍接口
@book_router.get("/{book_id}", response_model=BookResponse)
async def get_book(book_id: int, db: AsyncSession = Depends(get_db)):
book = await book_crud.get_book_by_id(db, book_id)
if not book:
raise HTTPException(status_code=404, detail="未查询到该书籍")
return book
# 更新书籍接口
@book_router.put("/{book_id}", response_model=BookResponse)
async def update_book(
book_id: int,
book_data: BookUpdate,
db: AsyncSession = Depends(get_db)
):
book = await book_crud.update_book(db, book_id, book_data)
if not book:
raise HTTPException(status_code=404, detail="未查询到该书籍,无法更新")
return book
# 删除书籍接口
@book_router.delete("/{book_id}")
async def delete_book(book_id: int, db: AsyncSession = Depends(get_db)):
success = await book_crud.delete_book(db, book_id)
if not success:
raise HTTPException(status_code=404, detail="未查询到该书籍,无法删除")
return {"code": 200, "message": "书籍删除成功"}
# 查询书籍列表接口(分页)
@book_router.get("/list", response_model=list[BookResponse])
async def get_book_list(
skip: int = Query(0, description="跳过的条数,默认0"),
limit: int = Query(10, description="每页显示的条数,默认10"),
db: AsyncSession = Depends(get_db)
):
return await book_crud.get_book_list(db, skip, limit)
6. main.py:项目入口文件
项目的启动入口,负责初始化FastAPI应用、挂载模块化路由、配置全局中间件(如跨域),不包含任何业务逻辑,仅负责项目的启动和全局配置。
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from routers.book_router import book_router
from config.db_config import async_engine, Base
from utils.common import register_exception_handlers
# 初始化FastAPI应用
app = FastAPI(title="书籍管理系统", version="1.0.0", description="基于FastAPI的模块化路由实战")
# 注册全局异常处理器(统一处理接口异常)
register_exception_handlers(app)
# 配置跨域中间件(解决前端跨域问题)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 开发环境允许所有源,生产环境需指定具体源
allow_credentials=True,
allow_methods=["*"], # 允许所有请求方法
allow_headers=["*"] # 允许所有请求头
)
# 挂载模块化路由(将书籍路由挂载到主应用)
app.include_router(book_router)
# 基础测试接口
@app.get("/")
async def root():
return {"message": "FastAPI 模块化路由项目启动成功"}
# 启动项目:uvicorn main:app --reload
7. utils/:通用工具层
存放全局通用工具函数,如异常处理、日志记录、数据格式转换等,供其他各层调用,避免代码冗余。
# utils/common.py
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
# 全局异常处理器(统一返回异常格式)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail, "data": None}
)
# 注册异常处理器到FastAPI应用
def register_exception_handlers(app: FastAPI):
app.add_exception_handler(HTTPException, http_exception_handler)
三、模块化路由核心实现逻辑
模块化路由的核心是“拆分+挂载”,即将不同业务的接口拆分到独立的路由文件,通过APIRouter创建路由实例,再通过app.include_router()方法将路由挂载到主应用,实现接口的模块化管理。其核心优势主要有3点:
-
接口按业务模块拆分,避免代码堆砌,便于快速查找和修改某一模块的接口;
-
路由前缀(prefix)和标签(tags)的使用,使API文档自动分组,提升接口文档的可读性;
-
各路由模块独立,便于团队协作开发,不同开发人员可同时开发不同模块的接口,互不干扰。
四、完整请求调用链路
规范的工程结构和模块化路由,形成了清晰的请求调用链路,从前端请求到数据库操作,各层各司其职,流程如下:
前端请求 → 路由层(routers)接收请求 → 数据校验层(schemas)校验参数 → 业务逻辑层(crud)处理业务、操作数据库 → 模型层(models)映射数据库 → 路由层返回响应
该链路实现了完全解耦,任何一层的修改都不会影响其他层,例如修改数据库操作逻辑,只需修改crud层,无需改动路由层和校验层;修改接口参数,只需修改schemas层,无需改动业务层。
五、工程结构与模块化路由使用注意事项
-
各层职责需严格分离,禁止跨层编写代码(如路由层直接编写数据库操作代码、crud层编写参数校验逻辑);
-
模块化路由的前缀(prefix)需规范命名,建议按业务模块划分(如/api/books、/api/users),便于接口管理;
-
所有数据库操作必须封装在crud层,路由层仅负责调用crud方法,确保业务逻辑的复用性和可维护性;
-
Pydantic模型需严格校验请求参数,避免无效数据进入业务层,同时规范响应数据格式,提升接口一致性;
-
配置文件(config)需集中管理,便于后续切换环境(如开发环境、测试环境、生产环境),无需修改业务代码。
总结
本文详细讲解了FastAPI标准工程结构的设计与各层职责,结合独立构思的代码示例,实现了模块化路由的创建、封装与挂载,完整呈现了企业级FastAPI项目的开发规范。规范的工程结构能解决代码冗余、维护困难、协作不便等问题,而模块化路由则实现了接口的分层管理,提升了代码的可读性和可扩展性。
通过本文的实现方式,可快速搭建一个结构规范、职责清晰的FastAPI后端项目,后续可根据业务需求,新增更多业务模块(如用户模块、订单模块),只需按现有结构新增对应目录和文件,即可实现无缝扩展,完全适配中小型后端项目的开发需求。