
文章目录
1. 课前导读
本节课学习目标
在前12课中,我们已经能够开发完整的RESTful API,并且具备异常处理、日志记录等企业级能力。但一个优秀的API项目,不仅代码要健壮,文档同样至关重要——它是对外沟通的契约,是前后端协作的桥梁。FastAPI最大的亮点之一就是自动生成交互式API文档(Swagger UI和ReDoc),零配置即可获得在线测试界面。
然而,默认生成的文档只能满足基础需求。在实际项目中,我们往往需要定制文档的标题、描述、接口分组、示例值,甚至在生产环境限制文档的访问权限。学完本节课,你将:
- 理解自动文档生成原理:FastAPI如何根据代码生成OpenAPI规范
- 定制文档元信息:修改标题、描述、版本、联系人、许可证等
- 组织接口分组:使用
tags参数对接口进行分类,提升文档可读性 - 添加请求/响应示例:使用
example和examples参数,为客户端提供参考 - 隐藏内部接口或禁用文档:根据环境变量控制文档是否公开
- 为文档添加权限控制:要求登录后才能访问/docs或/redoc
前置知识
- 已完成前12课,熟悉FastAPI路由、Pydantic模型、依赖注入
- 了解OpenAPI(Swagger)规范的基本概念
- 知道什么是API文档以及为什么需要它
学完能掌握什么
学完本节课后,你将具备以下能力:
- 配置文档的基础信息:让文档更符合项目品牌和规范
- 使用标签和描述组织接口:将用户管理、商品管理等接口分组展示
- 提供丰富的示例:帮助API消费者快速理解请求/响应格式
- 生产环境安全控制:禁用文档或要求认证后才能访问
- 扩展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-yasg或drf-spectacular | 高 | 中高 |
FastAPI的文档优势是“零配置即可用”,且与类型系统强绑定,保证了文档与代码的一致性。
2.4 安全考虑:生产环境是否暴露文档?
API文档提供了详细的接口信息,包括参数、请求体格式、响应结构等,在生产环境暴露可能带来安全风险(例如攻击者可以了解系统接口)。因此,通常需要:
- 内网部署文档,或
- 要求访问认证(如HTTP Basic Auth或使用API Key),或
- 完全禁用文档(设置
docs_url=None、redoc_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 为参数添加描述和示例
在路径参数、查询参数、请求体字段中添加description和example。
@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_parameters和redoc_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 |
易错点与避坑指南
- ❌ 忘记为Field提供example,导致文档中没有示例值。尽量为所有字段添加example。
- ❌ 在responses中使用example但content的media_type不匹配。必须指定
"application/json"。 - ❌ 同时使用了
response_model和responses中的示例,后者可能不会在自动响应模型中体现。两者可以共存,但示例仅用于文档展示。 - ❌ 生产环境未禁用文档,暴露了接口细节。务必根据环境变量控制
docs_url。 - ❌ 自定义文档端点时忘记设置
include_in_schema=False,导致/docs出现在文档自身中形成循环。 - ❌ 在Router中设置
tags,但在主应用中也设置openapi_tags未匹配。标签名称必须完全一致。
最佳实践
- 为所有公共接口添加描述和摘要,提升文档可读性。
- 为重要字段提供示例值,降低API使用者的理解成本。
- 使用
tags合理分组,按资源或功能划分(用户、商品、订单等)。 - 生产环境使用环境变量控制文档可见性,可选择内网开放或加认证。
- 在根描述中加入认证说明,让用户知道如何调用需要登录的接口。
- 利用OpenAPI的扩展字段添加服务器列表,方便客户端切换环境。
- 保持文档与代码同步,修改接口后检查文档是否准确。
6. 课后作业 & 思考题
实操练习题(必做)
-
基础练习:为第5课的商品创建接口添加完善的文档
- 添加
tags、summary、description - 为请求体每个字段添加
description和example - 为响应200和422添加示例
- 添加
-
进阶练习:自定义文档标签和分组
- 在
openapi_tags中定义两个分组:“用户认证”、“数据统计” - 为已有的登录接口和统计接口分配相应标签
- 为每个标签添加外部文档链接
- 在
-
挑战练习:实现文档的Bearer Token认证
- 在Swagger UI中添加“Authorize”按钮,允许输入JWT token
- 配置
swagger_ui_parameters中的oauth2RedirectUrl或使用OpenAPI的security scheme - 提示:使用
app = FastAPI(swagger_ui_oauth2_redirect_url="/oauth2-redirect")和安全方案
理论思考题
- FastAPI如何将Pydantic模型自动转换成OpenAPI的schema? 底层使用了哪个库?
include_in_schema=False和设置docs_url=None的区别是什么?- 为什么OpenAPI规范推荐使用
example而不是default来提供示例值? - 如何实现条件化显示某些接口(如仅管理员可见)在文档中?(提示:动态生成OpenAPI)
拓展学习方向
- OpenAPI规范官方文档:深入了解3.0.0版本的各个字段
- Swagger UI配置选项:调整UI的外观和行为
- 生成OpenAPI JSON文件:用于导入Postman或生成客户端SDK
- 使用
fastapi-code-generator:根据OpenAPI生成前端代码
7. 本节干货总结
核心考点(面试/自测)
-
FastAPI自动文档生成的原理是什么?
利用OpenAPI规范,从类型注解、Pydantic模型和路由装饰器中提取元数据生成JSON。 -
如何为分组接口添加描述?
在创建FastAPI时传入openapi_tags参数,为每个标签定义description。 -
如何隐藏一个接口不在文档中显示?
在装饰器中设置include_in_schema=False。 -
生产环境如何禁用文档?
设置docs_url=None, redoc_url=None, openapi_url=None。 -
如何为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集成:定义模型、创建数据库连接
- 异步数据库操作:使用
databases或async SQLAlchemy - 依赖注入管理会话:结合第10课,自动关闭数据库连接
- 数据库迁移工具Alembic:管理表结构变更
让API真正与数据库交互,实现数据的增删改查!
🔗《20节课 FastAPI 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

348

被折叠的 条评论
为什么被折叠?



