第13课:FastAPI|Swagger/ReDoc自动接口文档定制|文档美化与权限配置

在这里插入图片描述


1. 课前导读

本节课学习目标

在前12课中,我们已经能够开发完整的RESTful API,并且具备异常处理、日志记录等企业级能力。但一个优秀的API项目,不仅代码要健壮,文档同样至关重要——它是对外沟通的契约,是前后端协作的桥梁。FastAPI最大的亮点之一就是自动生成交互式API文档(Swagger UI和ReDoc),零配置即可获得在线测试界面。

然而,默认生成的文档只能满足基础需求。在实际项目中,我们往往需要定制文档的标题、描述、接口分组、示例值,甚至在生产环境限制文档的访问权限。学完本节课,你将:

  • 理解自动文档生成原理:FastAPI如何根据代码生成OpenAPI规范
  • 定制文档元信息:修改标题、描述、版本、联系人、许可证等
  • 组织接口分组:使用tags参数对接口进行分类,提升文档可读性
  • 添加请求/响应示例:使用exampleexamples参数,为客户端提供参考
  • 隐藏内部接口或禁用文档:根据环境变量控制文档是否公开
  • 为文档添加权限控制:要求登录后才能访问/docs或/redoc

前置知识

  • 已完成前12课,熟悉FastAPI路由、Pydantic模型、依赖注入
  • 了解OpenAPI(Swagger)规范的基本概念
  • 知道什么是API文档以及为什么需要它

学完能掌握什么

学完本节课后,你将具备以下能力:

  1. 配置文档的基础信息:让文档更符合项目品牌和规范
  2. 使用标签和描述组织接口:将用户管理、商品管理等接口分组展示
  3. 提供丰富的示例:帮助API消费者快速理解请求/响应格式
  4. 生产环境安全控制:禁用文档或要求认证后才能访问
  5. 扩展OpenAPI schema:自定义文档中的额外内容(如服务器列表)

适用人群

  • 希望提升API文档质量,方便团队协作的开发者
  • 准备将API开放给第三方使用的系统设计者
  • 关注生产环境安全的工程师
  • 想深入理解FastAPI文档生成机制的进阶学员

2. 核心理论讲解

2.1 OpenAPI规范与FastAPI的集成

OpenAPI规范(原Swagger规范)是一种用于描述RESTful API的标准化语言,使用JSON或YAML格式定义API的路径、参数、请求体、响应、认证方式等。FastAPI基于Python类型注解和Pydantic模型,自动生成符合OpenAPI 3.0+规范的JSON schema,然后通过Swagger UI和ReDoc渲染成交互式文档。

核心流程

代码(类型注解、Pydantic模型、路由装饰器) → FastAPI解析 → 生成OpenAPI JSON → Swagger UI/ReDoc读取并渲染
  • Swagger UI(/docs):提供交互式测试功能,可以直接发送请求。
  • ReDoc(/redoc):更美观的纯文档界面,适合阅读。

2.2 文档定制点

FastAPI允许在多个层级定制文档:

定制层级方式影响范围
全局文档元信息FastAPI(title="...", description="...", version="...")整个文档的标题、描述等
路径标签@app.get("/items", tags=["Items"])对接口分组
路径摘要和描述@app.get("/", summary="...", description="...")单个接口的简要说明
参数描述Query(..., description="...")Path(...)单个参数的解释
请求体示例Field(..., example=...)Body(examples=...)示例值
响应示例@app.get(..., responses={200: {"description": "...", "content": {...}}})不同状态码的响应结构

2.3 与Flask/Django的文档对比

框架文档生成方式交互式测试定制性学习成本
FastAPI自动,基于代码原生支持极高(OpenAPI完整)
Flask需手动编写或使用flask-swagger需扩展
Django REST自动,基于Serializer可搭配drf-yasgdrf-spectacular中高

FastAPI的文档优势是“零配置即可用”,且与类型系统强绑定,保证了文档与代码的一致性。

2.4 安全考虑:生产环境是否暴露文档?

API文档提供了详细的接口信息,包括参数、请求体格式、响应结构等,在生产环境暴露可能带来安全风险(例如攻击者可以了解系统接口)。因此,通常需要:

  • 内网部署文档,或
  • 要求访问认证(如HTTP Basic Auth或使用API Key),或
  • 完全禁用文档(设置docs_url=Noneredoc_url=None

本节课将演示如何根据环境变量动态控制文档的开启和权限。


3. 环境搭建 & 实操准备

3.1 复用项目环境

继续使用第12课的项目。确保虚拟环境已激活。

cd ~/fastapi_course/lesson03_first_project
source venv/bin/activate

3.2 安装依赖

无需额外依赖。如果需要生成自定义OpenAPI JSON,无需安装。

3.3 新增代码文件

创建app/api/v1/document_demo.py,并在main.py中注册。

touch app/api/v1/document_demo.py

main.py中注册:

from app.api.v1 import document_demo
app.include_router(document_demo.router, prefix="/api/v1/docs", tags=["文档示例"])

同时,我们将在app/main.py中修改FastAPI实例的创建参数,添加文档定制。


4. 手把手代码实战

4.1 定制全局文档元信息

app/main.py中创建FastAPI实例时,传入丰富的元数据:

from fastapi import FastAPI

app = FastAPI(
    title="FastAPI 从入门到精通教程 API",
    description="""
    本API是《FastAPI从入门到精通》专栏的示例项目。
    
    ## 功能特性
    - 用户注册、登录、个人信息管理
    - 商品增删改查
    - 订单管理
    - 文件上传
    
    ## 认证方式
    使用 JWT Bearer Token,请先调用 `/api/v1/auth/login` 获取 token,然后在请求头中添加 `Authorization: Bearer <token>`。
    """,
    version="1.0.0",
    terms_of_service="http://example.com/terms/",
    contact={
        "name": "作者",
        "url": "http://example.com/contact/",
        "email": "author@example.com",
    },
    license_info={
        "name": "MIT",
        "url": "https://opensource.org/licenses/MIT",
    },
    # 文档URL定制(可修改路径)
    docs_url="/docs",           # 默认就是 /docs
    redoc_url="/redoc",         # 默认 /redoc
    # 如果禁用文档,设置 docs_url=None, redoc_url=None
    # openapi_url="/openapi.json"  # 默认 /openapi.json
)

# 其他中间件、路由等...

访问/docs,可以看到文档标题、描述、联系人等已经更新。

4.2 使用tags对接口分组

在路由装饰器中添加tags参数,可以将多个接口归入同一个分组。

# app/api/v1/document_demo.py
from fastapi import APIRouter, Query, Path
from pydantic import BaseModel, Field

router = APIRouter()

class ItemCreate(BaseModel):
    name: str = Field(..., example="iPhone 15")
    price: float = Field(..., example=5999.0)

@router.post("/items", tags=["商品管理"])
async def create_item(item: ItemCreate):
    """创建商品"""
    return {"id": 1, **item.model_dump()}

@router.get("/items/{item_id}", tags=["商品管理"])
async def get_item(item_id: int = Path(..., description="商品ID", example=1)):
    """获取商品详情"""
    return {"id": item_id, "name": "示例商品"}

@router.post("/users", tags=["用户管理"])
async def create_user(username: str = Query(..., description="用户名")):
    """注册用户"""
    return {"username": username}

在文档中,“商品管理”和“用户管理”会显示为两个独立的分组。

4.3 为接口添加摘要和详细描述

@router.get(
    "/example",
    summary="示例接口的简短摘要",
    description="这是接口的详细描述,可以包含`Markdown`格式。",
    response_description="成功时返回的消息",
)
async def example_endpoint():
    return {"message": "ok"}

4.4 为参数添加描述和示例

在路径参数、查询参数、请求体字段中添加descriptionexample

@router.get("/search")
async def search_items(
    q: str = Query(..., description="搜索关键词", example="手机"),
    page: int = Query(1, description="页码,从1开始", ge=1),
    size: int = Query(10, description="每页数量", le=100)
):
    return {"query": q, "page": page, "size": size}

对于Pydantic模型字段:

class UserCreate(BaseModel):
    username: str = Field(..., description="用户名,3-20个字符", min_length=3, max_length=20, example="alice")
    email: str = Field(..., description="邮箱地址", example="alice@example.com")
    age: int = Field(..., description="年龄", ge=18, le=120, example=25)

4.5 添加多个请求示例

使用examples参数可以为同一个字段提供多个示例(OpenAPI 3.0.0+)。

from fastapi import Body

@router.post("/users/multi-example")
async def create_user_with_examples(
    user: UserCreate = Body(
        ...,
        examples={
            "普通用户": {
                "summary": "普通用户注册",
                "description": "使用用户名和邮箱注册",
                "value": {"username": "bob", "email": "bob@example.com", "age": 30}
            },
            "VIP用户": {
                "summary": "VIP用户",
                "value": {"username": "vip_user", "email": "vip@example.com", "age": 25}
            }
        }
    )
):
    return user

4.6 为响应添加示例

使用responses参数自定义不同状态码的响应描述和示例。

@router.get(
    "/items/{item_id}",
    responses={
        200: {
            "description": "成功返回商品",
            "content": {
                "application/json": {
                    "example": {"id": 1, "name": "示例商品", "price": 99.9}
                }
            }
        },
        404: {
            "description": "商品不存在",
            "content": {
                "application/json": {
                    "example": {"detail": "Item not found"}
                }
            }
        }
    }
)
async def get_item_with_responses(item_id: int):
    if item_id != 1:
        raise HTTPException(404, detail="Item not found")
    return {"id": 1, "name": "示例商品", "price": 99.9}

4.7 隐藏特定端点

有时有些内部接口不希望出现在文档中。可以通过include_in_schema=False隐藏。

@router.get("/internal/health", include_in_schema=False)
async def health_check():
    return {"status": "ok"}

该端点不会出现在/docs/redoc中,但仍然可以访问。

4.8 条件性启用/禁用文档

根据环境变量决定是否公开文档。

from app.config import settings

docs_enabled = settings.debug  # 开发环境为True,生产为False

app = FastAPI(
    title="My API",
    docs_url="/docs" if docs_enabled else None,
    redoc_url="/redoc" if docs_enabled else None,
    openapi_url="/openapi.json" if docs_enabled else None,
)

在生产环境中设置DEBUG=false,文档将完全禁用,访问返回404。

4.9 为文档添加认证保护

有时需要用户登录后才能查看文档。可以通过中间件或依赖实现。下面演示使用简单的HTTP Basic Auth保护/docs/redoc

首先,安装python-multipart(用于表单解析),然后编写一个依赖检查认证:

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import secrets

security = HTTPBasic()

def verify_docs_access(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(credentials.password, "docs123")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    return True

然后在main.py中,由于/docs/redoc是静态路由,不能直接加依赖,但可以借助中间件或自定义端点。另一种简单方法:挂载文档到需要认证的子路径。但更常用的做法是使用反向代理(如Nginx)配置认证,或者将文档单独部署在内网。

如果需要精确控制,可以自定义文档端点:

from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
from fastapi.openapi.utils import get_openapi

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(authenticated: bool = Depends(verify_docs_access)):
    return get_swagger_ui_html(
        openapi_url="/openapi.json",
        title="API Docs",
    )

@app.get("/redoc", include_in_schema=False)
async def custom_redoc_html(authenticated: bool = Depends(verify_docs_access)):
    return get_redoc_html(
        openapi_url="/openapi.json",
        title="ReDoc",
    )

@app.get("/openapi.json", include_in_schema=False)
async def get_open_api_endpoint(authenticated: bool = Depends(verify_docs_access)):
    return JSONResponse(get_openapi(title="My API", version="1.0", routes=app.routes))

这样,访问/docs会先触发认证依赖,认证成功后才显示文档。

4.10 自定义Swagger UI和ReDoc的参数

可以通过swagger_ui_parametersredoc_ui_parameters调整界面行为。

app = FastAPI(
    swagger_ui_parameters={
        "syntaxHighlight.theme": "obsidian",  # 代码高亮主题
        "docExpansion": "list",                # 文档展开方式: none, list, full
        "defaultModelsExpandDepth": -1,        # 不展开模型
    }
)

4.11 添加OpenAPI扩展字段

可以自定义OpenAPI JSON中的额外内容,例如服务器列表。

app = FastAPI(
    servers=[
        {"url": "https://api.example.com", "description": "生产环境"},
        {"url": "https://staging-api.example.com", "description": "预发布环境"},
        {"url": "http://localhost:8000", "description": "本地开发"},
    ]
)

4.12 完整实战:构建文档完善的商品API

我们结合之前课程的商品管理模块,完善其文档。

# app/api/v1/products_docs.py
from fastapi import APIRouter, HTTPException, Query, Path
from pydantic import BaseModel, Field
from typing import List, Optional

router = APIRouter(tags=["商品文档演示"])

class ProductCreate(BaseModel):
    name: str = Field(..., description="商品名称", min_length=1, max_length=100, example="智能手表")
    price: float = Field(..., description="价格(元)", gt=0, example=199.0)
    stock: int = Field(0, description="库存数量", ge=0, example=50)

class ProductOut(ProductCreate):
    id: int = Field(..., description="商品ID", example=1)

@router.post(
    "/products",
    response_model=ProductOut,
    summary="创建商品",
    description="创建新商品,返回生成的商品信息。",
    status_code=201,
    responses={
        201: {"description": "商品创建成功"},
        422: {"description": "参数校验失败"}
    }
)
async def create_product(product: ProductCreate):
    # 模拟保存
    return ProductOut(id=1, **product.model_dump())

@router.get(
    "/products/{product_id}",
    response_model=ProductOut,
    summary="获取商品详情",
    responses={
        200: {"description": "成功返回商品"},
        404: {"description": "商品不存在"}
    }
)
async def get_product(
    product_id: int = Path(..., description="商品ID", ge=1, example=1)
):
    if product_id != 1:
        raise HTTPException(404, detail="商品不存在")
    return ProductOut(id=1, name="智能手表", price=199.0, stock=50)

@router.get(
    "/products",
    response_model=List[ProductOut],
    summary="商品列表",
    description="支持分页和按价格区间筛选"
)
async def list_products(
    min_price: Optional[float] = Query(None, description="最低价格", ge=0),
    max_price: Optional[float] = Query(None, description="最高价格", gt=0),
    page: int = Query(1, description="页码", ge=1),
    size: int = Query(10, description="每页数量", ge=1, le=100)
):
    # 模拟数据
    return []

访问/docs可以看到每个参数和模型都有清晰的说明和示例值。

4.13 使用openapi_tags全局配置标签

如果希望更精细地控制标签的描述、外部文档等,可以使用openapi_tags参数。

tags_metadata = [
    {
        "name": "商品管理",
        "description": "商品的增删改查操作",
        "externalDocs": {
            "description": "了解更多",
            "url": "https://example.com/docs/products",
        },
    },
    {
        "name": "用户管理",
        "description": "用户注册、登录、资料管理",
    },
]

app = FastAPI(openapi_tags=tags_metadata)

然后在路由中使用tags=["商品管理"]即可关联。


5. 重点知识点总结

文档定制核心语法速查表

目标代码
全局标题/描述FastAPI(title="...", description="...")
接口分组@router.get("/", tags=["TagName"])
接口摘要@router.get("/", summary="...")
参数描述q: str = Query(..., description="...")
字段示例Field(..., example=1)
请求体多个示例Body(..., examples={...})
响应示例responses={200: {"description": "...", "content": {...}}}
隐藏端点include_in_schema=False
禁用文档docs_url=None, redoc_url=None
自定义文档端点@app.get("/docs") + get_swagger_ui_html

易错点与避坑指南

  1. ❌ 忘记为Field提供example,导致文档中没有示例值。尽量为所有字段添加example。
  2. ❌ 在responses中使用example但content的media_type不匹配。必须指定"application/json"
  3. ❌ 同时使用了response_modelresponses中的示例,后者可能不会在自动响应模型中体现。两者可以共存,但示例仅用于文档展示。
  4. ❌ 生产环境未禁用文档,暴露了接口细节。务必根据环境变量控制docs_url
  5. ❌ 自定义文档端点时忘记设置include_in_schema=False,导致/docs出现在文档自身中形成循环。
  6. ❌ 在Router中设置tags,但在主应用中也设置openapi_tags未匹配。标签名称必须完全一致。

最佳实践

  1. 为所有公共接口添加描述和摘要,提升文档可读性。
  2. 为重要字段提供示例值,降低API使用者的理解成本。
  3. 使用tags合理分组,按资源或功能划分(用户、商品、订单等)。
  4. 生产环境使用环境变量控制文档可见性,可选择内网开放或加认证。
  5. 在根描述中加入认证说明,让用户知道如何调用需要登录的接口。
  6. 利用OpenAPI的扩展字段添加服务器列表,方便客户端切换环境。
  7. 保持文档与代码同步,修改接口后检查文档是否准确。

6. 课后作业 & 思考题

实操练习题(必做)

  1. 基础练习:为第5课的商品创建接口添加完善的文档

    • 添加tagssummarydescription
    • 为请求体每个字段添加descriptionexample
    • 为响应200和422添加示例
  2. 进阶练习:自定义文档标签和分组

    • openapi_tags中定义两个分组:“用户认证”、“数据统计”
    • 为已有的登录接口和统计接口分配相应标签
    • 为每个标签添加外部文档链接
  3. 挑战练习:实现文档的Bearer Token认证

    • 在Swagger UI中添加“Authorize”按钮,允许输入JWT token
    • 配置swagger_ui_parameters中的oauth2RedirectUrl或使用OpenAPI的security scheme
    • 提示:使用app = FastAPI(swagger_ui_oauth2_redirect_url="/oauth2-redirect")和安全方案

理论思考题

  1. FastAPI如何将Pydantic模型自动转换成OpenAPI的schema? 底层使用了哪个库?
  2. include_in_schema=False和设置docs_url=None的区别是什么?
  3. 为什么OpenAPI规范推荐使用example而不是default来提供示例值?
  4. 如何实现条件化显示某些接口(如仅管理员可见)在文档中?(提示:动态生成OpenAPI)

拓展学习方向

  • OpenAPI规范官方文档:深入了解3.0.0版本的各个字段
  • Swagger UI配置选项:调整UI的外观和行为
  • 生成OpenAPI JSON文件:用于导入Postman或生成客户端SDK
  • 使用fastapi-code-generator:根据OpenAPI生成前端代码

7. 本节干货总结

核心考点(面试/自测)

  1. FastAPI自动文档生成的原理是什么?
    利用OpenAPI规范,从类型注解、Pydantic模型和路由装饰器中提取元数据生成JSON。

  2. 如何为分组接口添加描述?
    在创建FastAPI时传入openapi_tags参数,为每个标签定义description

  3. 如何隐藏一个接口不在文档中显示?
    在装饰器中设置include_in_schema=False

  4. 生产环境如何禁用文档?
    设置docs_url=None, redoc_url=None, openapi_url=None

  5. 如何为Swagger UI添加认证(如Bearer Token)?
    配置安全方案(security)并在swagger_ui_parameters中启用授权按钮。

实际工作应用场景

  • 场景1:API开放平台
    提供详细的文档和在线测试功能,让第三方开发者快速集成。

  • 场景2:内部前后端协作
    后端开发完成后,前端直接使用/docs查看接口并调试,减少沟通成本。

  • 场景3:自动化测试
    从OpenAPI JSON生成测试代码或断言。

  • 场景4:API网关集成
    将OpenAPI导入网关进行路由校验或生成SDK。

  • 场景5:版本管理
    为不同API版本生成不同文档,例如/docs/v1/docs/v2


下节课预告

本节课我们让API文档变得专业、美观、易用。现在,我们的FastAPI项目已经具备了完整的接口层、中间件、异常处理、文档等。接下来,我们需要将数据持久化——集成数据库。

下节课(第14课),我们将学习:

  • SQLAlchemy ORM集成:定义模型、创建数据库连接
  • 异步数据库操作:使用databasesasync SQLAlchemy
  • 依赖注入管理会话:结合第10课,自动关闭数据库连接
  • 数据库迁移工具Alembic:管理表结构变更

让API真正与数据库交互,实现数据的增删改查!


🔗《20节课 FastAPI 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值