FastAPI APIRouter模块化架构完全指南

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

目录

为什么需要APIRouter?

不用Router的问题

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

# ❌ main.py — 几千行,难以维护
from fastapi import FastAPI

app = FastAPI()

@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 个路由

问题显而易见

  • 代码文件过长,难以阅读和维护
  • 修改一处功能可能影响其他模块
  • 团队协作困难,容易产生冲突
  • 路由管理混乱,难以定位问题

用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
├── schemas/
│   ├── user_schemas.py
│   └── post_schemas.py
├── services/
│   ├── user_service.py
│   └── post_service.py
└── utils/
    ├── auth_utils.py
    └── validation_utils.py

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

APIRouter的核心价值

  1. 代码组织:将相关功能聚合到一起
  2. 团队协作:不同开发者负责不同模块
  3. 可维护性:功能修改不影响其他模块
  4. 可扩展性:新增功能不影响现有代码
  5. 文档自动生成:按功能分组展示API文档

APIRouter基础用法

创建Router

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

# 定义响应模型
class UserSchema(BaseModel):
    id: int
    username: str
    email: str

class CreateUserSchema(BaseModel):
    username: str
    email: str
    password: str

class UpdateUserSchema(BaseModel):
    username: str = None
    email: str = None

# 创建路由器实例
router = APIRouter(
    prefix="/users",           # 路径前缀,所有路由自动添加此前缀
    tags=["用户管理"],           # OpenAPI文档中的标签
    responses={404: {"description": "用户未找到"}},  # 公共响应定义
)

# 路由路径会自动加上 prefix = /users
@router.get("/", response_model=List[UserSchema])
async def list_users(skip: int = 0, limit: int = 100):
    """
    获取用户列表
    - **skip**: 跳过的记录数
    - **limit**: 返回的最大记录数
    """
    # 模拟数据库查询
    users = [{"id": 1, "username": "alice", "email": "alice@example.com"}]
    return users

@router.get("/{user_id}", response_model=UserSchema)
async def get_user(user_id: int):
    """根据ID获取用户信息"""
    # 模拟数据库查询
    if user_id != 1:
        raise HTTPException(status_code=404, detail="User not found")
    return {"id": user_id, "username": "alice", "email": "alice@example.com"}

@router.post("/", response_model=UserSchema, status_code=201)
async def create_user(data: CreateUserSchema):
    """创建新用户"""
    # 模拟用户创建
    return {
        "id": 2,
        "username": data.username,
        "email": data.email
    }

@router.put("/{user_id}", response_model=UserSchema)
async def update_user(user_id: int, data: UpdateUserSchema):
    """更新用户信息"""
    # 模拟用户更新
    return {
        "id": user_id,
        "username": data.username or "alice",
        "email": data.email or "alice@example.com"
    }

@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
    """删除用户"""
    # 模拟用户删除
    return None

在主应用中注册Router

# main.py
from fastapi import FastAPI
from routers.users import router as users_router
from routers.posts import router as posts_router
from routers.comments import router as comments_router
from routers.auth import router as auth_router

app = FastAPI(
    title="道满API服务",
    version="1.0.0",
    description="使用FastAPI构建的企业级REST API服务"
)

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

@app.get("/")
async def root():
    return {"message": "欢迎使用道满API服务", "version": "1.0.0"}

@app.get("/health")
async def health_check():
    return {"status": "healthy", "timestamp": "2024-01-15T10:30:00Z"}

效果:自动生成OpenAPI文档

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

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

Router参数详解

router = APIRouter(
    prefix="/users",           # 路径前缀,所有路由自动添加此前缀
    tags=["用户管理"],           # OpenAPI文档标签,用于分组显示
    dependencies=[],           # 该路由组的公共依赖
    default_response_class=None, # 默认响应类
    responses={404: {"description": "资源未找到"}},  # 公共响应定义
    callbacks=None,            # 回调函数
    routes=None,               # 预定义路由
    redirect_slashes=True,     # 是否重定向斜杠
    deprecated=False,          # 是否已弃用
    include_in_schema=True,    # 是否包含在OpenAPI schema中
    openapi_extra=None,        # OpenAPI额外信息
    lifespan=None,             # 生命周期管理
)

APIRouter高级特性

多层嵌套Router

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

# 内容管理路由器,组合帖子和评论路由
content_router = APIRouter(prefix="/content", tags=["内容管理"])

# 将子路由注册到内容路由器
content_router.include_router(posts_router)
content_router.include_router(comments_router)

# 导出路由器
router = content_router
# routers/content/posts.py
from fastapi import APIRouter

router = APIRouter(prefix="/posts", tags=["内容 - 帖子"])

@router.get("/")
async def list_posts():
    return {"posts": []}

@router.post("/")
async def create_post():
    return {"message": "帖子创建成功"}
# routers/content/comments.py
from fastapi import APIRouter

router = APIRouter(prefix="/comments", tags=["内容 - 评论"])

@router.get("/")
async def list_comments():
    return {"comments": []}

@router.post("/")
async def create_comment():
    return {"message": "评论创建成功"}
# main.py - 使用多层嵌套路由
from fastapi import FastAPI
from routers.content import router as content_router
from routers.users import router as users_router

app = FastAPI()

# 注册嵌套路由
app.include_router(content_router, prefix="/api/v1")  # 最终路径: /api/v1/content/posts/
app.include_router(users_router, prefix="/api/v1")    # 最终路径: /api/v1/users/

Router级依赖注入

# dependencies.py - 全局依赖
from fastapi import Depends, HTTPException, Header
from typing import Optional

async def get_token_header(authorization: str = Header(...)):
    """从Header获取Token"""
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid token")
    return authorization.replace("Bearer ", "")

async def get_query_token(token: str = Header(...)):
    """从Query获取Token"""
    if token != "secret-token":
        raise HTTPException(status_code=400, detail="No secret token provided")
    return token

# routers/users.py - 使用路由级依赖
from fastapi import APIRouter, Depends
from dependencies import get_token_header

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

@router.get("/")      # ✅ 自动需要认证
async def list_users():
    return {"users": []}

@router.get("/{user_id}")  # ✅ 自动需要认证
async def get_user(user_id: int):
    return {"user_id": user_id}

# routers/auth.py - 不需要认证的路由
from fastapi import APIRouter

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

@router.post("/login")    # ✅ 不需要认证
async def login(username: str, password: str):
    return {"message": "登录成功", "token": "fake-jwt-token"}

@router.post("/register")  # ✅ 不需要认证
async def register(email: str, password: str):
    return {"message": "注册成功"}

路由排序与优先级

# ⚠️ 注意路由顺序!FastAPI 按定义顺序匹配
from fastapi import APIRouter

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

@router.get("/special")           # 精确匹配优先
async def special_user():
    return {"type": "special"}

@router.get("/{user_id}")         # 通配符匹配,放在后面
async def get_user(user_id: int):
    return {"user_id": user_id}

@router.get("/")                  # 最后匹配根路径
async def list_users():
    return {"users": []}

# 匹配优先级:
# 1. /users/special → special_user()
# 2. /users/123 → get_user(user_id=123)
# 3. /users/ → list_users()

动态路由注册

# 动态注册路由的工厂函数
from fastapi import APIRouter

def create_crud_router(model_name: str, model_schema, service_class):
    """创建CRUD操作的通用路由工厂"""
    router = APIRouter(prefix=f"/{model_name}s", tags=[f"{model_name.capitalize()}管理"])
    
    @router.get("/")
    async def list_items():
        # 使用服务类进行业务逻辑处理
        service = service_class()
        return await service.list_all()
    
    @router.get("/{item_id}")
    async def get_item(item_id: int):
        service = service_class()
        return await service.get_by_id(item_id)
    
    @router.post("/", status_code=201)
    async def create_item(data: model_schema):
        service = service_class()
        return await service.create(data)
    
    @router.put("/{item_id}")
    async def update_item(item_id: int, data: model_schema):
        service = service_class()
        return await service.update(item_id, data)
    
    @router.delete("/{item_id}", status_code=204)
    async def delete_item(item_id: int):
        service = service_class()
        await service.delete(item_id)
        return None
    
    return router

# 使用工厂函数创建路由
from routers.user_service import UserService
from schemas.user_schema import UserSchema

users_router = create_crud_router("user", UserSchema, UserService)

完整模块化项目结构

推荐目录结构

my_fastapi_app/
├── main.py                      # 应用入口
├── config.py                    # 配置管理
├── database.py                  # 数据库连接
├── models/                      # 数据库模型
│   ├── __init__.py
│   ├── user.py
│   ├── post.py
│   └── base.py                  # 基础模型
├── schemas/                     # Pydantic模型
│   ├── __init__.py
│   ├── user_schema.py
│   ├── post_schema.py
│   └── common.py                # 通用模型
├── routers/                     # 路由模块
│   ├── __init__.py
│   ├── users.py
│   ├── posts.py
│   ├── comments.py
│   └── auth.py
├── services/                    # 业务逻辑层
│   ├── __init__.py
│   ├── user_service.py
│   ├── post_service.py
│   └── auth_service.py
├── dependencies.py              # 依赖注入
├── exceptions.py                # 自定义异常
├── middleware/                  # 中间件
│   ├── __init__.py
│   ├── auth_middleware.py
│   └── logging_middleware.py
├── utils/                       # 工具函数
│   ├── __init__.py
│   ├── auth_utils.py
│   ├── validation_utils.py
│   └── crypto_utils.py
├── core/                        # 核心配置
│   ├── __init__.py
│   ├── security.py
│   └── events.py
└── tests/                       # 测试文件
    ├── __init__.py
    ├── test_users.py
    └── conftest.py

config.py — 集中配置管理

from pydantic import BaseSettings
from functools import lru_cache
from typing import List

class Settings(BaseSettings):
    # 应用配置
    app_name: str = "道满FastAPI应用"
    version: str = "1.0.0"
    debug: bool = False
    api_prefix: str = "/api/v1"
    
    # 数据库配置
    database_url: str = "sqlite+aiosqlite:///./app.db"
    database_pool_size: int = 20
    database_pool_timeout: int = 30
    
    # 认证配置
    jwt_secret: str = "your-super-secret-jwt-secret-change-it-in-production"
    jwt_algorithm: str = "HS256"
    jwt_expire_minutes: int = 30
    access_token_expire_minutes: int = 30
    refresh_token_expire_days: int = 7
    
    # CORS配置
    cors_origins: List[str] = [
        "http://localhost:3000",
        "http://localhost:8080",
        "https://yourdomain.com"
    ]
    
    # Redis配置
    redis_host: str = "localhost"
    redis_port: int = 6379
    redis_db: int = 0
    
    # 日志配置
    log_level: str = "INFO"
    log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    
    class Config:
        env_file = ".env"  # 从.env文件加载配置
        env_file_encoding = "utf-8"

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

# 全局配置实例
settings = get_settings()

dependencies.py — 全局依赖管理

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional
from database import get_db
from core.security import verify_token
from config import settings

security = HTTPBearer(auto_error=False)

# 全局数据库依赖
async def get_database_session() -> AsyncSession:
    async with get_db() as session:
        try:
            yield session
        finally:
            await session.close()

# 可选的当前用户依赖
async def get_current_user_optional(
    token: str = Depends(security),
    db: AsyncSession = Depends(get_database_session)
) -> Optional[dict]:
    if not token:
        return None
    try:
        payload = verify_token(token.credentials)
        user_id: int = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Could not validate credentials"
            )
        # 从数据库获取用户信息
        # user = await db.get_user_by_id(user_id)
        return {"id": user_id, "username": "test_user"}
    except Exception:
        return None

# 必需的当前用户依赖
async def get_current_user(
    token: str = Depends(security),
    db: AsyncSession = Depends(get_database_session)
) -> dict:
    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated"
        )
    try:
        payload = verify_token(token.credentials)
        user_id: int = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Could not validate credentials"
            )
        # 从数据库获取用户信息
        # user = await db.get_user_by_id(user_id)
        return {"id": user_id, "username": "test_user", "role": "user"}
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials"
        )

# 管理员权限依赖
async def require_admin(current_user: dict = Depends(get_current_user)):
    if current_user.get("role") != "admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin privileges required"
        )
    return current_user

services/user_service.py — 业务逻辑层

from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete
from models.user import User
from schemas.user_schema import UserCreate, UserUpdate
from exceptions import (
    NotFoundException, 
    ConflictException, 
    ValidationException
)
import logging

logger = logging.getLogger(__name__)

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

    async def get_by_id(self, user_id: int) -> User:
        """根据ID获取用户"""
        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) -> Optional[User]:
        """根据邮箱获取用户"""
        result = await self.db.execute(
            select(User).where(User.email == email)
        )
        return result.scalar_one_or_none()

    async def get_by_username(self, username: str) -> Optional[User]:
        """根据用户名获取用户"""
        result = await self.db.execute(
            select(User).where(User.username == username)
        )
        return result.scalar_one_or_none()

    async def list_users(self, skip: int = 0, limit: int = 100) -> List[User]:
        """获取用户列表"""
        result = await self.db.execute(
            select(User).offset(skip).limit(limit)
        )
        return result.scalars().all()

    async def create(self, user_data: UserCreate) -> User:
        """创建新用户"""
        # 检查邮箱是否已存在
        existing_user = await self.get_by_email(user_data.email)
        if existing_user:
            raise ConflictException("邮箱已被注册")
        
        # 检查用户名是否已存在
        existing_user = await self.get_by_username(user_data.username)
        if existing_user:
            raise ConflictException("用户名已被使用")
        
        # 创建用户
        user = User(**user_data.model_dump())
        self.db.add(user)
        await self.db.commit()
        await self.db.refresh(user)
        
        logger.info(f"用户创建成功: {user.id}")
        return user

    async def update(self, user_id: int, user_data: UserUpdate) -> User:
        """更新用户信息"""
        # 检查用户是否存在
        await self.get_by_id(user_id)
        
        # 检查邮箱冲突
        if user_data.email:
            existing_user = await self.get_by_email(user_data.email)
            if existing_user and existing_user.id != user_id:
                raise ConflictException("邮箱已被其他用户使用")
        
        # 更新用户
        stmt = (
            update(User)
            .where(User.id == user_id)
            .values(**user_data.model_dump(exclude_unset=True))
        )
        await self.db.execute(stmt)
        await self.db.commit()
        
        # 获取更新后的用户
        return await self.get_by_id(user_id)

    async def delete(self, user_id: int) -> None:
        """删除用户"""
        # 检查用户是否存在
        await self.get_by_id(user_id)
        
        stmt = delete(User).where(User.id == user_id)
        await self.db.execute(stmt)
        await self.db.commit()
        
        logger.info(f"用户删除成功: {user_id}")

routers/users.py — 用户路由模块

from fastapi import APIRouter, Depends, status
from typing import List
from sqlalchemy.ext.asyncio import AsyncSession
from dependencies import get_current_user, require_admin
from database import get_database_session
from schemas.user_schema import UserOut, UserCreate, UserUpdate
from services.user_service import UserService
from exceptions import ValidationException

router = APIRouter(
    prefix="/users",
    tags=["用户管理"],
    responses={404: {"description": "用户未找到"}}
)

def get_user_service(
    db: AsyncSession = Depends(get_database_session)
) -> UserService:
    """获取用户服务实例"""
    return UserService(db)

@router.get("/", response_model=List[UserOut])
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 [UserOut.from_orm(user) for user in users]

@router.get("/me", response_model=UserOut)
async def get_current_user_profile(
    current_user: dict = Depends(get_current_user),
    service: UserService = Depends(get_user_service)
):
    """获取当前用户信息"""
    user = await service.get_by_id(current_user["id"])
    return UserOut.from_orm(user)

@router.get("/{user_id}", response_model=UserOut)
async def get_user(
    user_id: int,
    service: UserService = Depends(get_user_service)
):
    """根据ID获取用户信息"""
    user = await service.get_by_id(user_id)
    return UserOut.from_orm(user)

@router.post("/", response_model=UserOut, status_code=status.HTTP_201_CREATED)
async def create_user(
    user_data: UserCreate,
    current_user: dict = Depends(require_admin),  # 只有管理员可以创建用户
    service: UserService = Depends(get_user_service)
):
    """创建新用户"""
    user = await service.create(user_data)
    return UserOut.from_orm(user)

@router.put("/{user_id}", response_model=UserOut)
async def update_user(
    user_id: int,
    user_data: UserUpdate,
    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 ValidationException("权限不足", "无权修改他人信息")
    
    user = await service.update(user_id, user_data)
    return UserOut.from_orm(user)

@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
    user_id: int,
    current_user: dict = Depends(require_admin),  # 只有管理员可以删除用户
    service: UserService = Depends(get_user_service)
):
    """删除用户"""
    await service.delete(user_id)
    return None

@router.patch("/{user_id}/activate", response_model=UserOut)
async def activate_user(
    user_id: int,
    current_user: dict = Depends(require_admin),
    service: UserService = Depends(get_user_service)
):
    """激活用户账户"""
    user = await service.get_by_id(user_id)
    # 激活逻辑
    return UserOut.from_orm(user)

main.py — 主应用入口

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from fastapi.staticfiles import StaticFiles
import logging

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

# 配置日志
logging.basicConfig(level=settings.log_level)

@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期管理"""
    # 启动时:创建数据库表
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    print("Database tables created")
    
    yield
    
    # 关闭时:释放连接池
    await engine.dispose()
    print("Database connections disposed")

app = FastAPI(
    title=settings.app_name,
    version=settings.version,
    description=settings.app_description,
    lifespan=lifespan,
    docs_url="/docs",  # Swagger UI
    redoc_url="/redoc",  # ReDoc
    openapi_url="/openapi.json"  # OpenAPI schema
)

# CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    # 额外配置
    allow_origin_regex=None,
    expose_headers=["Access-Control-Allow-Origin"],
    max_age=600,
)

# 注册路由
app.include_router(
    auth_router,
    prefix=settings.api_prefix,
    tags=["认证"]
)

app.include_router(
    users_router,
    prefix=settings.api_prefix,
    tags=["用户管理"]
)

app.include_router(
    posts_router,
    prefix=settings.api_prefix,
    tags=["帖子管理"]
)

app.include_router(
    comments_router,
    prefix=settings.api_prefix,
    tags=["评论管理"]
)

# 健康检查端点
@app.get("/health", tags=["系统"])
async def health_check():
    """健康检查端点"""
    return {
        "status": "healthy",
        "version": settings.version,
        "timestamp": "2024-01-15T10:30:00Z"
    }

# 根路径
@app.get("/", tags=["系统"])
async def root():
    """API根路径"""
    return {
        "message": f"欢迎使用{settings.app_name}",
        "version": settings.version,
        "docs": "/docs",
        "redoc": "/redoc"
    }

# 静态文件服务(可选)
try:
    app.mount("/static", StaticFiles(directory="static"), name="static")
except:
    pass

APIRouter最佳实践

1. 按功能模块拆分

✅ 推荐:
- users.py: 用户管理相关路由
- posts.py: 帖子管理相关路由
- auth.py: 认证授权相关路由
- admin.py: 管理员专用路由

❌ 避免:
- all_routes.py: 将所有路由放在一个文件
- random.py: 功能混杂的路由文件

2. 合理使用prefix和tags

# ✅ 好的命名
router = APIRouter(
    prefix="/users",           # 清晰的路径前缀
    tags=["用户管理"],           # 描述性的标签
)

# ❌ 不好的命名
router = APIRouter(
    prefix="/u",              # 过于简短
    tags=["A"],               # 不够描述性
)

3. 依赖注入的合理使用

# ✅ 按需添加依赖
# 认证相关的路由组
auth_required_router = APIRouter(
    dependencies=[Depends(get_current_user)],
    tags=["认证保护"]
)

# 公开的路由组
public_router = APIRouter(tags=["公开接口"])

# ❌ 避免在不需要的地方添加依赖
# 为不需要认证的路由添加认证依赖
public_router = APIRouter(
    dependencies=[Depends(get_current_user)],  # 不必要的依赖
    tags=["公开接口"]
)

4. 路由分组策略

# 按权限分组
from fastapi import APIRouter

# 公开路由
public_router = APIRouter(tags=["公开接口"])

# 需要认证的路由
protected_router = APIRouter(
    dependencies=[Depends(get_current_user)],
    tags=["受保护接口"]
)

# 管理员路由
admin_router = APIRouter(
    dependencies=[Depends(require_admin)],
    tags=["管理员接口"]
)

# 按功能分组
user_router = APIRouter(prefix="/users", tags=["用户管理"])
post_router = APIRouter(prefix="/posts", tags=["帖子管理"])
comment_router = APIRouter(prefix="/comments", tags=["评论管理"])

5. 响应模型的一致性

from pydantic import BaseModel
from typing import List, Optional

# 统一的响应格式
class ApiResponse(BaseModel):
    code: int = 200
    message: str = "success"
    data: Optional[dict] = None
    timestamp: str

class UserOut(BaseModel):
    id: int
    username: str
    email: str
    created_at: str

# 在路由中使用一致的响应格式
@router.get("/{user_id}", response_model=ApiResponse)
async def get_user(user_id: int):
    user = await service.get_by_id(user_id)
    return ApiResponse(
        code=200,
        message="获取用户成功",
        data=UserOut.from_orm(user).model_dump()
    )

性能优化建议

1. 路由注册优化

# 批量注册路由,避免重复操作
def register_routers(app: FastAPI, prefix: str = ""):
    """批量注册路由的工具函数"""
    from routers import (
        users_router,
        posts_router,
        comments_router,
        auth_router
    )
    
    router_configs = [
        (auth_router, "", ["认证"]),
        (users_router, prefix, ["用户管理"]),
        (posts_router, prefix, ["帖子管理"]),
        (comments_router, prefix, ["评论管理"]),
    ]
    
    for router, route_prefix, tags in router_configs:
        app.include_router(
            router,
            prefix=route_prefix,
            tags=tags
        )

# 在main.py中使用
register_routers(app, settings.api_prefix)

2. 路由缓存策略

from functools import lru_cache

# 缓存路由实例,避免重复创建
@lru_cache(maxsize=128)
def get_router_instance(router_name: str):
    """获取路由实例的缓存工厂"""
    if router_name == "users":
        from routers.users import router
        return router
    elif router_name == "posts":
        from routers.posts import router
        return router
    # ... 其他路由
    return None

3. 中间件与路由的配合

# 为特定路由组添加中间件
from starlette.middleware.base import BaseHTTPMiddleware

class CustomRouteMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        # 只对特定路由路径应用中间件逻辑
        if request.url.path.startswith("/api/v1/users"):
            # 用户相关路由的特殊处理
            request.state.route_group = "users"
        
        response = await call_next(request)
        return response

# 在应用中添加中间件
app.add_middleware(CustomRouteMiddleware)

常见陷阱与避坑指南

陷阱1:路由顺序问题

# ❌ 错误:通配符路由在前
@router.get("/{user_id}")  # 这会匹配 /special
async def get_user(user_id: int):
    return {"user_id": user_id}

@router.get("/special")    # 这个永远不会被匹配到
async def special_user():
    return {"type": "special"}

# ✅ 正确:精确路由在前
@router.get("/special")    # 先匹配精确路径
async def special_user():
    return {"type": "special"}

@router.get("/{user_id}")  # 再匹配通配符路径
async def get_user(user_id: int):
    return {"user_id": user_id}

陷阱2:依赖注入的滥用

# ❌ 错误:在不需要的地方添加依赖
router = APIRouter(
    dependencies=[Depends(get_current_user)],  # 所有路由都需要认证
    tags=["公共接口"]  # 但标签说是公共接口
)

@router.get("/public-info")  # 这个本来应该是公开的
async def get_public_info():
    return {"info": "public"}

# ✅ 正确:按需添加依赖
# 分离公共和私有路由
public_router = APIRouter(tags=["公共接口"])
private_router = APIRouter(
    dependencies=[Depends(get_current_user)],
    tags=["私有接口"]
)

陷阱3:前缀叠加问题

# ❌ 错误:前缀叠加导致路径过长
main_router = APIRouter(prefix="/api/v1")
sub_router = APIRouter(prefix="/users")  # 前缀会叠加

main_router.include_router(sub_router)  # 最终路径: /api/v1/users/users

# ✅ 正确:合理设计前缀层级
api_router = APIRouter(prefix="/api/v1")
users_router = APIRouter(prefix="/users")  # 顶层已经包含版本信息

app.include_router(users_router, prefix="/api/v1")  # 最终路径: /api/v1/users

陷阱4:循环依赖问题

# ❌ 错误:路由间的循环依赖
# routers/a.py
from routers.b import b_router

a_router = APIRouter()
a_router.include_router(b_router)  # A包含B

# routers/b.py  
from routers.a import a_router

b_router = APIRouter()
b_router.include_router(a_router)  # B包含A → 循环依赖!

# ✅ 正确:避免循环依赖
# 将共同依赖提取到第三方
# routers/common.py
common_router = APIRouter()

# 在main.py中统一注册
app.include_router(common_router)
app.include_router(a_router)
app.include_router(b_router)

陷阱5:错误处理分散

# ❌ 错误:每个路由都有自己的错误处理
@router.get("/{user_id}")
async def get_user(user_id: int):
    try:
        user = await service.get_by_id(user_id)
        return user
    except Exception as e:
        return {"error": str(e)}

@router.post("/")
async def create_user(data: UserCreate):
    try:
        user = await service.create(data)
        return user
    except Exception as e:
        return {"error": str(e)}

# ✅ 正确:统一的异常处理
# 在main.py中注册全局异常处理器
from exceptions import register_exception_handlers
register_exception_handlers(app)

与其他架构模式对比

APIRouter vs Flask Blueprints

特性FastAPI APIRouterFlask Blueprints
类型提示支持✅ 完整支持❌ 无
自动文档生成✅ OpenAPI/Swagger❌ 需要额外扩展
异步支持✅ 原生支持❌ 需要额外配置
依赖注入✅ 内置支持❌ 需要手动实现
性能✅ 高性能标准性能

APIRouter vs Django Apps

特性FastAPI APIRouterDjango Apps
配置复杂度✅ 简单❌ 复杂
学习曲线✅ 平缓❌ 陡峭
项目结构✅ 灵活❌ 固定
启动速度✅ 快速❌ 较慢
ORM集成✅ 灵活选择❌ 内置ORM

APIRouter vs Express Routers

特性FastAPI APIRouterExpress Routers
类型安全✅ 静态类型检查❌ 运行时检查
文档自动生成✅ 自动生成❌ 需要手动编写
验证库集成✅ Pydantic内置❌ 需要额外库
性能✅ 高性能标准性能

相关教程

APIRouter是构建大型FastAPI应用的关键。建议按功能模块拆分路由,使用一致的前缀和标签,并将业务逻辑与路由逻辑分离到不同的层中。 良好的项目结构是成功的一半。建议在项目初期就规划好目录结构,使用APIRouter将功能模块化,并保持各层职责清晰。

总结

特性说明最佳实践
prefix路径前缀按版本和功能分组 /api/v1/users
tags文档分组使用描述性标签便于文档浏览
dependencies公共依赖按权限和功能分组应用依赖
嵌套路由模块化组织避免过深层级,保持清晰结构
路由顺序匹配优先级精确路径在前,通配符路径在后
项目结构代码组织按MVC模式分离模型、视图、控制器

💡 核心思想:APIRouter就像乐高积木的接口。把每个功能模块做成一个独立Router,主应用只需要把它们拼起来,就能组成完整的大型应用。通过合理的模块化设计,可以让团队协作更高效,代码维护更容易。

FastAPI的APIRouter为企业级应用提供了强大的模块化能力,通过合理的路由组织、清晰的依赖管理和规范的项目结构,可以构建出易于维护和扩展的高质量API服务。


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,主应用只需要把它们拼起来,就能组成完整的大型应用。


🔗 扩展阅读