FastAPI 实战:工程结构规范与模块化路由实现

10 阅读11分钟

在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点:

  1. 接口按业务模块拆分,避免代码堆砌,便于快速查找和修改某一模块的接口;

  2. 路由前缀(prefix)和标签(tags)的使用,使API文档自动分组,提升接口文档的可读性;

  3. 各路由模块独立,便于团队协作开发,不同开发人员可同时开发不同模块的接口,互不干扰。

四、完整请求调用链路

规范的工程结构和模块化路由,形成了清晰的请求调用链路,从前端请求到数据库操作,各层各司其职,流程如下:

前端请求 → 路由层(routers)接收请求 → 数据校验层(schemas)校验参数 → 业务逻辑层(crud)处理业务、操作数据库 → 模型层(models)映射数据库 → 路由层返回响应

该链路实现了完全解耦,任何一层的修改都不会影响其他层,例如修改数据库操作逻辑,只需修改crud层,无需改动路由层和校验层;修改接口参数,只需修改schemas层,无需改动业务层。

五、工程结构与模块化路由使用注意事项

  1. 各层职责需严格分离,禁止跨层编写代码(如路由层直接编写数据库操作代码、crud层编写参数校验逻辑);

  2. 模块化路由的前缀(prefix)需规范命名,建议按业务模块划分(如/api/books、/api/users),便于接口管理;

  3. 所有数据库操作必须封装在crud层,路由层仅负责调用crud方法,确保业务逻辑的复用性和可维护性;

  4. Pydantic模型需严格校验请求参数,避免无效数据进入业务层,同时规范响应数据格式,提升接口一致性;

  5. 配置文件(config)需集中管理,便于后续切换环境(如开发环境、测试环境、生产环境),无需修改业务代码。

总结

本文详细讲解了FastAPI标准工程结构的设计与各层职责,结合独立构思的代码示例,实现了模块化路由的创建、封装与挂载,完整呈现了企业级FastAPI项目的开发规范。规范的工程结构能解决代码冗余、维护困难、协作不便等问题,而模块化路由则实现了接口的分层管理,提升了代码的可读性和可扩展性。

通过本文的实现方式,可快速搭建一个结构规范、职责清晰的FastAPI后端项目,后续可根据业务需求,新增更多业务模块(如用户模块、订单模块),只需按现有结构新增对应目录和文件,即可实现无缝扩展,完全适配中小型后端项目的开发需求。