APIRouter 模块化:像拼积木一样组织你的大型项目架构

📂 所属阶段:第二阶段 — 进阶黑科技(核心篇)
🔗 相关章节:依赖注入系统 · 异常处理


1. 为什么需要 APIRouter?

1.1 不用 Router 的问题

想象一个社交应用,有用户、帖子、评论、点赞等功能,全部写在 main.py 里:

# ❌ main.py — 几千行,难以维护
@app.get("/users")
def list_users(): ...
@app.post("/users")
def create_user(): ...
@app.get("/users/{id}")
def get_user(id): ...
@app.put("/users/{id}")
def update_user(id): ...
@app.delete("/users/{id}")
def delete_user(id): ...

@app.get("/posts")
def list_posts(): ...
@app.post("/posts")
def create_post(): ...

# ... 再来 50 个路由

1.2 用 Router 拆分的优势

app/
├── main.py              ← 主入口,组装所有路由
├── dependencies.py      ← 全局依赖(认证、数据库)
├── routers/
│   ├── __init__.py
│   ├── users.py         ← 用户相关路由(/users/...)
│   ├── posts.py         ← 帖子相关路由(/posts/...)
│   ├── comments.py      ← 评论相关路由(/comments/...)
│   └── auth.py          ← 认证相关路由(/auth/...)
└── models/
    ├── user.py
    └── post.py

每个文件只管自己的功能,改一处不影响其他模块。


2. APIRouter 基础用法

2.1 创建 Router

# routers/users.py
from fastapi import APIRouter, Depends, HTTPException
from typing import List

router = APIRouter(prefix="/users", tags=["用户管理"])

# 路由路径会自动加上 prefix = /users
@router.get("/", response_model=List[UserSchema])
async def list_users(skip: int = 0, limit: int = 100):
    return await db.get_users(skip=skip, limit=limit)

@router.get("/{user_id}", response_model=UserSchema)
async def get_user(user_id: int):
    user = await db.get_user(user_id)
    if not user:
        raise HTTPException(404, "User not found")
    return user

@router.post("/", response_model=UserSchema, status_code=201)
async def create_user(data: CreateUserSchema):
    return await db.create_user(data)

@router.put("/{user_id}", response_model=UserSchema)
async def update_user(user_id: int, data: UpdateUserSchema):
    return await db.update_user(user_id, data)

@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
    await db.delete_user(user_id)
    return None

2.2 在主应用中注册 Router

# main.py
from fastapi import FastAPI
from routers import users, posts, comments, auth

app = FastAPI(title="我的 API", version="1.0.0")

# 注册所有子路由
app.include_router(users.router)
app.include_router(posts.router)
app.include_router(comments.router)
app.include_router(auth.router)

@app.get("/")
async def root():
    return {"message": "Welcome to my API"}

2.3 效果:自动生成 OpenAPI 文档

路由结构                        OpenAPI 显示
/users/...                    ← 标签:用户管理
  GET /                        ← 列出用户
  GET /{id}                    ← 获取用户详情
  POST /                       ← 创建用户
  PUT /{id}                    ← 更新用户
  DELETE /{id}                ← 删除用户

/posts/...                    ← 标签:帖子管理
  GET /                        ← 列出帖子
  POST /                       ← 创建帖子
  ...

3. Router 的高级特性

3.1 多层嵌套 Router

# routers/content/
# __init__.py
from .posts import router as posts_router
from .comments import router as comments_router

# routers/content/posts.py
router = APIRouter(prefix="/posts", tags=["内容 - 帖子"])

# routers/content/comments.py
router = APIRouter(prefix="/comments", tags=["内容 - 评论"])

# routers/content/__init__.py
from .posts import router as posts_router
from .comments import router as comments_router
# routers/__init__.py — 一级路由
from .content.posts import router as posts_router
from .content.comments import router as comments_router
from .users import router as users_router
from .auth import router as auth_router

# main.py
from routers import (
    posts_router, comments_router,
    users_router, auth_router
)

app = FastAPI()
app.include_router(auth_router, prefix="/api/v1")
app.include_router(users_router, prefix="/api/v1")
app.include_router(posts_router, prefix="/api/v1")
app.include_router(comments_router, prefix="/api/v1")

3.2 Router 级依赖注入

# 路由公共依赖
async def get_current_user(token: str = Header(...)):
    return verify_token(token)

# users.py
router = APIRouter(
    prefix="/users",
    dependencies=[Depends(get_current_user)],  # 本文件所有路由都需认证
    tags=["用户管理"]
)

@router.get("/")      # ✅ 自动需要认证
async def list_users():
    ...

# auth.py(不需要认证的路由)
router = APIRouter(prefix="/auth", tags=["认证"])

@router.post("/login")    # ✅ 不需要认证
async def login():
    ...

3.3 路由排序与优先级

# ⚠️ 注意路由顺序!FastAPI 按定义顺序匹配
@router.get("/users")           # 匹配 /users
async def list_users(): ...

@router.get("/users/{user_id}") # 这个会匹配 /users/123
async def get_user(user_id): ...

# 但 /users/new 不匹配 user_id,
# 如果你想单独处理 /users/new,必须放在 /users/{user_id} 前面:
@router.get("/users")
@router.get("/users/new")       # 精确匹配优先
@router.get("/users/{user_id}")

4. 完整的模块化项目结构

4.1 目录树

pyweb/
├── main.py                      # 应用入口
├── config.py                    # 配置文件
├── database.py                  # 数据库连接
├── models/
│   ├── __init__.py
│   ├── user.py
│   ├── post.py
│   └── schemas.py               # Pydantic 模型
├── routers/
│   ├── __init__.py              # 导出所有 router
│   ├── users.py
│   ├── posts.py
│   ├── comments.py
│   └── auth.py
├── services/
│   ├── __init__.py
│   ├── user_service.py
│   └── post_service.py          # 业务逻辑层
├── dependencies.py              # 全局依赖
├── exceptions.py               # 自定义异常
└── middleware/
    ├── __init__.py
    ├── cors.py
    ├── logging.py
    └── gzip.py

4.2 config.py — 集中配置

from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    app_name: str = "DaomanAPI"
    version: str = "1.0.0"
    debug: bool = False

    database_url: str = "sqlite+aiosqlite:///./app.db"
    jwt_secret: str = "change-me-in-production"
    jwt_algorithm: str = "HS256"
    jwt_expire_minutes: int = 30

    cors_origins: list[str] = ["https://daomanpy.com"]

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

@lru_cache()
def get_settings() -> Settings:
    return Settings()

4.3 dependencies.py — 全局依赖

from fastapi import Depends, Header, HTTPException
from fastapi.security import HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from services.auth import verify_token
from config import get_settings

security = HTTPBearer(auto_error=False)

async def get_current_user_optional(
    token: str = Depends(security),
    db: AsyncSession = Depends(get_db)
):
    if not token:
        return None
    return await verify_token(db, token.credentials)

async def get_current_user(
    token: str = Depends(security),
    db: AsyncSession = Depends(get_db)
) -> dict:
    if not token:
        raise HTTPException(401, "请先登录")
    user = await verify_token(db, token.credentials)
    if not user:
        raise HTTPException(401, "Token 无效")
    return user

4.4 services/user_service.py — 业务逻辑层

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from models.user import User
from exceptions import NotFoundException, ConflictException

class UserService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_by_id(self, user_id: int) -> User:
        result = await self.db.execute(
            select(User).where(User.id == user_id)
        )
        user = result.scalar_one_or_none()
        if not user:
            raise NotFoundException("用户", str(user_id))
        return user

    async def get_by_email(self, email: str) -> User | None:
        result = await self.db.execute(
            select(User).where(User.email == email)
        )
        return result.scalar_one_or_none()

    async def create(self, data: dict) -> User:
        # 检查邮箱冲突
        existing = await self.get_by_email(data["email"])
        if existing:
            raise ConflictException("邮箱已被注册")
        user = User(**data)
        self.db.add(user)
        await self.db.commit()
        await self.db.refresh(user)
        return user

4.5 routers/users.py — 用户路由

from fastapi import APIRouter, Depends, status
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
from database import get_db
from dependencies import get_current_user, get_current_user_optional
from models.schemas import (
    UserSchema, CreateUserSchema, UpdateUserSchema, UserListSchema
)
from services.user_service import UserService

router = APIRouter(prefix="/users", tags=["用户管理"])

def get_user_service(db: AsyncSession = Depends(get_db)) -> UserService:
    return UserService(db)

@router.get("/", response_model=List[UserSchema])
async def list_users(
    skip: int = 0,
    limit: int = 20,
    service: UserService = Depends(get_user_service)
):
    users = await service.list_users(skip=skip, limit=limit)
    return users

@router.get("/me", response_model=UserSchema)
async def get_me(
    current_user: dict = Depends(get_current_user),
    service: UserService = Depends(get_user_service)
):
    return await service.get_by_id(current_user["id"])

@router.get("/{user_id}", response_model=UserSchema)
async def get_user(
    user_id: int,
    service: UserService = Depends(get_user_service)
):
    return await service.get_by_id(user_id)

@router.post("/", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
async def create_user(
    data: CreateUserSchema,
    service: UserService = Depends(get_user_service)
):
    return await service.create(data.model_dump())

@router.put("/{user_id}", response_model=UserSchema)
async def update_user(
    user_id: int,
    data: UpdateUserSchema,
    current_user: dict = Depends(get_current_user),
    service: UserService = Depends(get_user_service)
):
    if current_user["id"] != user_id and current_user["role"] != "admin":
        raise HTTPException(403, "无权修改他人信息")
    return await service.update(user_id, data.model_dump(exclude_unset=True))

@router.delete("/{user_id}", status_code=204)
async def delete_user(
    user_id: int,
    current_user: dict = Depends(get_current_user),
    service: UserService = Depends(get_user_service)
):
    if current_user["id"] != user_id and current_user["role"] != "admin":
        raise HTTPException(403, "无权删除")
    await service.delete(user_id)

4.6 main.py — 最终组装

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager

from config import get_settings
from database import engine, Base
from routers import (
    users_router, posts_router,
    comments_router, auth_router
)

settings = get_settings()

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动:创建数据库表
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    # 关闭:释放连接池
    await engine.dispose()

app = FastAPI(
    title=settings.app_name,
    version=settings.version,
    lifespan=lifespan,
)

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 注册路由
app.include_router(auth_router)
app.include_router(users_router)
app.include_router(posts_router)
app.include_router(comments_router)

@app.get("/health")
async def health():
    return {"status": "ok"}

@app.get("/")
async def root():
    return {"message": f"Welcome to {settings.app_name}"}

5. Router 的最佳实践

实践说明
按功能模块拆分每个 router 负责一个实体(如 users、posts)
使用 prefix统一添加路径前缀,避免路径重复
使用 tags自动生成有组织的 OpenAPI 文档
依赖分组dependencies=[] 为一类路由批量添加认证
业务逻辑放 servicerouter 只做路由转发,service 处理业务
Schema 独立文件Pydantic 模型和 SQLAlchemy 模型分开

6. 小结

APIRouter 核心用法速查:

router = APIRouter(
    prefix="/users",           # 路径前缀
    tags=["用户管理"],           # OpenAPI 文档分组
    dependencies=[Depends(...)], # 本模块通用依赖
)

router.include_router(sub_router)  # 嵌套子路由

app.include_router(router)        # 主应用注册

💡 核心思想:Router 就像乐高积木的接口。把每个功能模块做成一个独立 Router,主应用只需要把它们拼起来,就能组成完整的大型应用。


🔗 扩展阅读