第6步-1-1:FastAPI 框架 - 基础部分
6.1.1 FastAPI 基础入门
简述:FastAPI 是一个现代 Python Web 框架,以高性能、自动文档和类型安全著称。基于 Starlette 和 Uvicorn,支持异步处理,适合快速构建 RESTful API 和微服务。
概念:FastAPI 简介
FastAPI 是一个现代、快速(高性能)的 Python Web 框架,基于 Starlette(路由层)和 Uvicorn(ASGI 服务器)。它支持异步处理、自动生成 OpenAPI 文档、使用 Pydantic 进行数据验证。适合构建微服务、实时应用、高性能 API。
核心特性:
- 快速:基于 Starlette 和 Pydantic,性能与 NodeJS、Go 相当
- 自动文档:自动生成 Swagger UI 和 ReDoc 交互式 API 文档
- 类型安全:基于 Python 类型提示,编辑器支持自动补全和类型检查
- 异步支持:原生支持 async/await,适合高并发场景
- 数据验证:Pydantic 自动验证请求和响应数据
安装 FastAPI
bash
pip install fastapi
pip install uvicorn[standard] # ASGI 服务器
第一个 FastAPI 应用
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello FastAPI"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "name": "Alice"}
# 运行
# uvicorn main:app --reload
FastAPI 项目结构(推荐)
myapi/
├── main.py # 主应用入口
├── routers/ # 路由模块
│ ├── __init__.py
│ ├── users.py
│ └── items.py
├── schemas/ # Pydantic 模型
│ ├── __init__.py
│ └── user.py
├── models/ # 数据库模型
│ └── user.py
├── crud.py # 数据库操作
├── database.py # 数据库配置
└── requirements.txt
启动服务器
bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
访问文档
- Swagger UI:http://127.0.0.1:8000/docs
- ReDoc:http://127.0.0.1:8000/redoc
- OpenAPI JSON:http://127.0.0.1:8000/openapi.json
6.1.2 路由与 HTTP 方法
简述:路由是 URL 路径到处理函数的映射。FastAPI 通过装饰器自动映射 HTTP 方法(GET/POST/PUT/DELETE)到对应的函数,支持路径参数、查询参数和请求体的便捷获取。
概念:路由
路由是 URL 路径到处理函数的映射。FastAPI 通过装饰器自动将函数注册为路由处理器。
GET 请求
python
from fastapi import FastAPI, Query, Path
from typing import Optional
app = FastAPI()
# 基础 GET
@app.get("/users")
async def get_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
# 带路径参数
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
# 带查询参数
@app.get("/search")
async def search_users(name: Optional[str] = None, age: int = 18):
return {"name": name, "age": age}
# Query 参数验证
@app.get("/items")
async def get_items(
skip: int = Query(0, ge=0, description="跳过数量"),
limit: int = Query(10, ge=1, le=100, description="返回数量")
):
return {"skip": skip, "limit": limit}
POST 请求
python
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: str
age: Optional[int] = Field(None, ge=0, le=150)
password: str = Field(..., min_length=6)
class UserResponse(BaseModel):
id: int
username: str
email: str
class Config:
from_attributes = True
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# 实际项目中这里会存入数据库
new_user = {
"id": 1,
"username": user.username,
"email": user.email
}
return new_user
# 使用 Body 接收原始数据
@app.post("/articles")
async def create_article(content: str = Body(..., embed=True)):
return {"content": content}
PUT 请求
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
users_db = {1: {"id": 1, "username": "alice", "email": "alice@example.com"}}
class UserUpdate(BaseModel):
username: str | None = None
email: str | None = None
@app.put("/users/{user_id}")
async def update_user(user_id: int, user: UserUpdate):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="用户不存在")
# 更新非空字段
if user.username is not None:
users_db[user_id]["username"] = user.username
if user.email is not None:
users_db[user_id]["email"] = user.email
return users_db[user_id]
DELETE 请求
python
@app.delete("/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="用户不存在")
del users_db[user_id]
return None
@app.delete("/users")
async def delete_all_users():
users_db.clear()
return {"message": "所有用户已删除"}
请求参数类型汇总
| 参数位置 | 获取方式 | 示例 |
|---|---|---|
| 路径参数 | 函数参数 | @app.get("/users/{id}") |
| 查询参数 | Query() |
name: str = Query(None) |
| 请求体 | Body() / Pydantic |
user: UserCreate |
| 表单数据 | Form() |
username: str = Form(...) |
| 请求头 | Header() |
x_token: str = Header(None) |
| Cookie | Cookie() |
session_id: str = Cookie(None) |
Form Data(表单数据)
使用
Form()接收application/x-www-form-urlencoded或multipart/form-data格式的表单数据,适用于传统 HTML 表单提交和 OAuth2 密码模式登录场景。注意:Form和Body(JSON)不能在同一个路由中同时使用。
python
from fastapi import FastAPI, Form, UploadFile, File
app = FastAPI()
@app.post("/login")
async def login(
username: str = Form(..., min_length=3, max_length=50),
password: str = Form(..., min_length=6),
remember: bool = Form(False)
):
return {"username": username, "remember": remember}
@app.post("/profile")
async def update_profile(
name: str = Form(...),
bio: str = Form(None),
avatar: UploadFile = File(None)
):
return {"name": name, "bio": bio, "avatar": avatar.filename if avatar else None}
提示 :当路由参数中使用了
Form()或File(),FastAPI 会自动将请求的 Content-Type 设为multipart/form-data或application/x-www-form-urlencoded,而不是 JSON。如果需要接收 JSON 请求体,应使用 Pydantic 模型。
Cookie 操作
使用
Cookie()读取请求中的 Cookie,通过Response.set_cookie()写入 Cookie。常用于会话管理、主题偏好、Token 刷新等场景。
python
from fastapi import FastAPI, Cookie, Response
from typing import Optional
app = FastAPI()
@app.get("/set-cookie")
async def set_cookie(response: Response):
response.set_cookie(
key="session_id",
value="abc123",
max_age=3600,
httponly=True,
samesite="lax",
secure=False
)
response.set_cookie(
key="theme",
value="dark",
max_age=86400 * 30
)
return {"message": "Cookie 已设置"}
@app.get("/get-cookie")
async def get_cookie(
session_id: Optional[str] = Cookie(None),
theme: Optional[str] = Cookie(None, alias="theme")
):
return {
"session_id": session_id,
"theme": theme
}
@app.get("/delete-cookie")
async def delete_cookie(response: Response):
response.delete_cookie("session_id")
return {"message": "Cookie 已删除"}
Cookie 安全参数 :
httponly=True防止 JavaScript 访问(防 XSS),secure=True仅 HTTPS 传输,samesite="lax"防止 CSRF 攻击。生产环境建议三个参数都启用。
Header 操作
使用
Header()获取请求头信息。FastAPI 会自动将请求头名称中的连字符-转换为下划线_(如X-Request-ID→x_request_id),并统一转为小写。
python
from fastapi import FastAPI, Header
from typing import Optional
app = FastAPI()
@app.get("/headers")
async def read_headers(
user_agent: Optional[str] = Header(None),
accept_language: Optional[str] = Header(None),
x_request_id: Optional[str] = Header(None, alias="X-Request-ID")
):
return {
"user_agent": user_agent,
"accept_language": accept_language,
"x_request_id": x_request_id
}
@app.get("/client-info")
async def client_info(
x_forwarded_for: Optional[str] = Header(None),
x_real_ip: Optional[str] = Header(None)
):
client_ip = x_real_ip or (x_forwarded_for.split(",")[0] if x_forwarded_for else "unknown")
return {"client_ip": client_ip}
提示 :
Header()参数名中的下划线会自动映射到请求头的连字符。例如x_api_key: str = Header(...)会读取X-Api-Key请求头。也可以用alias参数显式指定请求头名称。
Request 对象
通过
request: Request直接访问底层 Starlette 请求对象,获取请求头、查询参数、Cookie、客户端 IP、请求体等完整信息。适用于需要灵活处理请求的场景。
python
from fastapi import FastAPI, Request
from typing import Optional
app = FastAPI()
@app.get("/request-info")
async def request_info(request: Request):
return {
"method": request.method,
"url": str(request.url),
"path": request.url.path,
"query_params": dict(request.query_params),
"client_host": request.client.host if request.client else None,
"client_port": request.client.port if request.client else None,
"headers": dict(request.headers),
"cookies": request.cookies,
"user_agent": request.headers.get("user-agent"),
}
@app.post("/raw-body")
async def read_raw_body(request: Request):
content_type = request.headers.get("content-type")
if "json" in content_type:
data = await request.json()
elif "form" in content_type:
data = dict(await request.form())
else:
data = (await request.body()).decode()
return {"content_type": content_type, "data": data}
@app.get("/client-ip")
async def get_client_ip(request: Request):
forwarded = request.headers.get("x-forwarded-for")
real_ip = request.headers.get("x-real-ip")
client_ip = real_ip or (forwarded.split(",")[0] if forwarded else request.client.host)
return {"client_ip": client_ip}
提示 :
Request对象是 Starlette 提供的,包含完整的 HTTP 请求信息。在中间件、依赖注入和路由中都可以使用。当 FastAPI 的声明式参数(Query、Header、Cookie等)无法满足需求时,可以直接使用Request对象。
模块化路由:创建独立 Router 文件
概念 :当项目规模增大时,应将不同模块的路由拆分到独立的文件中。使用 APIRouter 可以创建模块化的路由,注册到主应用时通过 app.include_router() 统一管理。
项目目录结构
myapi/
├── main.py # 主应用入口
├── routers/ # 路由模块目录
│ ├── __init__.py # 空文件,使 routers 成为包
│ ├── users.py # 用户相关路由
│ ├── items.py # 商品相关路由
│ └── orders.py # 订单相关路由
├── schemas/ # Pydantic 模型
│ ├── __init__.py
│ ├── user.py
│ └── item.py
└── requirements.txt
创建 users.py 路由文件
python
# routers/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
router = APIRouter(prefix="/users", tags=["用户管理"])
# 模拟数据库
users_db = [
{"id": 1, "username": "alice", "email": "alice@example.com"},
{"id": 2, "username": "bob", "email": "bob@example.com"}
]
# Pydantic 模型
class UserCreate(BaseModel):
username: str
email: str
class UserUpdate(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
class UserResponse(BaseModel):
id: int
username: str
email: str
# GET 查询所有用户
@router.get("/", response_model=List[UserResponse])
async def get_users(skip: int = 0, limit: int = 10):
return users_db[skip:skip + limit]
# GET 查询单个用户
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
user = next((u for u in users_db if u["id"] == user_id), None)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
# POST 创建用户
@router.post("/", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
new_id = max(u["id"] for u in users_db) + 1 if users_db else 1
new_user = {"id": new_id, **user.model_dump()}
users_db.append(new_user)
return new_user
# PUT 更新用户
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user: UserUpdate):
for u in users_db:
if u["id"] == user_id:
if user.username is not None:
u["username"] = user.username
if user.email is not None:
u["email"] = user.email
return u
raise HTTPException(status_code=404, detail="用户不存在")
# DELETE 删除用户
@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
global users_db
for i, u in enumerate(users_db):
if u["id"] == user_id:
users_db.pop(i)
return None
raise HTTPException(status_code=404, detail="用户不存在")
创建 items.py 路由文件
python
# routers/items.py
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
router = APIRouter(prefix="/items", tags=["商品管理"])
items_db = [
{"id": 1, "name": "Laptop", "price": 2999.99, "stock": 50},
{"id": 2, "name": "Phone", "price": 999.99, "stock": 100}
]
class ItemCreate(BaseModel):
name: str
price: float
stock: int = 0
class ItemUpdate(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
stock: Optional[int] = None
class ItemResponse(BaseModel):
id: int
name: str
price: float
stock: int
@router.get("/", response_model=List[ItemResponse])
async def get_items():
return items_db
@router.get("/{item_id}", response_model=ItemResponse)
async def get_item(item_id: int):
item = next((i for i in items_db if i["id"] == item_id), None)
if not item:
raise HTTPException(status_code=404, detail="商品不存在")
return item
@router.post("/", response_model=ItemResponse, status_code=201)
async def create_item(item: ItemCreate):
new_id = max(i["id"] for i in items_db) + 1 if items_db else 1
new_item = {"id": new_id, **item.model_dump()}
items_db.append(new_item)
return new_item
@router.delete("/{item_id}", status_code=204)
async def delete_item(item_id: int):
global items_db
for i, item in enumerate(items_db):
if item["id"] == item_id:
items_db.pop(i)
return None
raise HTTPException(status_code=404, detail="商品不存在")
创建 main.py 主应用
python
# main.py
from fastapi import FastAPI
from routers import users, items
app = FastAPI(
title="MyAPI",
description="模块化 FastAPI 应用示例",
version="1.0.0"
)
# 注册路由
app.include_router(users.router)
app.include_router(items.router)
@app.get("/")
async def root():
return {"message": "欢迎访问 MyAPI", "docs": "/docs"}
@app.get("/health")
async def health_check():
return {"status": "healthy"}
运行和测试
bash
# 启动服务器
uvicorn main:app --reload
# 访问文档
# http://127.0.0.1:8000/docs
# API 路径结构
# GET /users/ - 查询用户列表
# POST /users/ - 创建用户
# GET /users/{user_id} - 查询单个用户
# PUT /users/{user_id} - 更新用户
# DELETE /users/{user_id} - 删除用户
# GET /items/ - 查询商品列表
# POST /items/ - 创建商品
# GET /items/{item_id} - 查询单个商品
# DELETE /items/{item_id} - 删除商品
routers/init.py 文件
python
# routers/__init__.py
from . import users, items
__all__ = ["users", "items"]
使用子路由前缀和标签分组
python
# 如果需要更细粒度的控制,可以单独指定参数
app.include_router(
users.router,
prefix="/api/v1", # 添加路径前缀
tags=["V1 - 用户"], # 自定义标签
dependencies=[] # 全局依赖(如认证)
)
# 访问路径变为:/api/v1/users/
在路由文件中使用依赖注入
python
# routers/users.py
from fastapi import Depends, HTTPException
from typing import Generator
# 模拟数据库会话依赖
def get_db():
db = {"connected": True}
yield db
# 清理资源
# 在路由中使用依赖
@router.get("/{user_id}")
async def get_user(user_id: int, db: dict = Depends(get_db)):
print(f"数据库连接: {db}")
# ... 查询逻辑
pass
# 使用认证依赖
async def verify_token(token: str):
if token != "valid-token":
raise HTTPException(status_code=401, detail="无效的令牌")
return {"user_id": 1}
@router.get("/profile")
async def get_user_profile(token: str = Depends(verify_token)):
return {"user_id": token["user_id"], "username": "alice"}
6.1.3 Pydantic 数据验证
简述:Pydantic 是 Python 数据验证库,FastAPI 使用它进行请求数据的自动验证和响应数据的序列化。通过定义数据模型,可以实现字段类型检查、默认值设置和自定义验证规则。
概念:Pydantic
Pydantic 是 Python 数据验证库,FastAPI 使用它进行请求数据验证和响应数据序列化。定义模型后,FastAPI 自动验证输入数据并生成 JSON Schema。Pydantic 底层调用 json.dumps(),但自动处理了 datetime、UUID、Enum 等特殊类型的转换。
基础模型
python
from pydantic import BaseModel, Field, EmailStr, ConfigDict
from typing import Optional, List
from datetime import datetime
class User(BaseModel):
id: int
username: str
email: str
is_active: bool = True
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr # 自动验证邮箱格式
password: str = Field(..., min_length=6)
class UserUpdate(BaseModel):
username: Optional[str] = Field(None, min_length=3, max_length=50)
email: Optional[EmailStr] = None
password: Optional[str] = Field(None, min_length=6)
嵌套模型
python
class Address(BaseModel):
street: str
city: str
country: str
zip_code: str
class OrderItem(BaseModel):
product_id: int
quantity: int = Field(..., gt=0, description="数量必须大于0")
price: float = Field(..., gt=0)
class Order(BaseModel):
id: int
user_id: int
items: List[OrderItem]
address: Address
total_amount: float
created_at: datetime
status: str = "pending"
class Config:
from_attributes = True
嵌套模型请求示例
python
@app.post("/orders")
async def create_order(order: Order):
# order.items 是 OrderItem 对象列表
# order.address 是 Address 对象
total = sum(item.quantity * item.price for item in order.items)
return {"order_id": 1, "total": total}
Pydantic 字段验证器
python
from pydantic import field_validator
from typing import Annotated
class UserRegister(BaseModel):
username: str
password: str
password_confirm: str
age: int
@field_validator("username")
@classmethod
def username_alphanumeric(cls, v):
if not v.replace("_", "").isalnum():
raise ValueError("用户名只能包含字母、数字、下划线")
return v
@field_validator("password_confirm")
@classmethod
def passwords_match(cls, v, info):
# 注意:需要从 info.fields 获取其他字段值
if 'password' in info.data and v != info.data['password']:
raise ValueError("两次密码输入不一致")
return v
@field_validator("age")
@classmethod
def age_valid(cls, v):
if v < 0 or v > 150:
raise ValueError("年龄必须在 0-150 之间")
return v
高级验证:model_validator(Pydantic V2)
python
from pydantic import model_validator
class UserLogin(BaseModel):
username: str
password: str
code: str
@model_validator(mode='after')
def validate_code(self):
if self.code != "1234":
raise ValueError("验证码错误")
return self
提示 :Pydantic V2 中
root_validator已被model_validator替代。mode='after'表示在所有字段验证完成后执行,此时可通过self.field访问已验证的字段值。
可选字段与默认值
python
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
class Product(BaseModel):
name: str
price: float = 0.0
description: Optional[str] = None
tags: List[str] = []
is_featured: bool = False
model_config = ConfigDict(
str_strip_whitespace=True,
populate_by_name=True
)
提示 :Pydantic V2 中使用
model_config = ConfigDict(...)替代了 V1 的class Config写法。str_strip_whitespace=True会自动去除字符串首尾空格,populate_by_name=True允许通过字段别名填充数据。
字段别名与序列化高级用法
使用
alias实现字段名映射(前后端字段名不一致时)、serialization_alias区分输入输出别名、model_dump()系列方法控制序列化输出,是实际项目中非常实用的功能。
python
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
from datetime import datetime
class UserCreate(BaseModel):
model_config = ConfigDict(populate_by_name=True)
username: str = Field(..., alias="userName", min_length=3, max_length=50)
email: str = Field(..., alias="emailAddress")
phone_number: Optional[str] = Field(None, alias="phone", serialization_alias="phoneNumber")
data = {"userName": "alice", "emailAddress": "alice@example.com", "phone": "13800138000"}
user = UserCreate.model_validate(data)
print(user.username)
print(user.model_dump())
print(user.model_dump(by_alias=True))
print(user.model_dump(by_alias=True, exclude_unset=True))
print(user.model_dump_json(by_alias=True))
输出:
alice
{'username': 'alice', 'email': 'alice@example.com', 'phone_number': '13800138000'}
{'userName': 'alice', 'emailAddress': 'alice@example.com', 'phone': '13800138000'}
{'userName': 'alice', 'emailAddress': 'alice@example.com', 'phoneNumber': '13800138000'}
{"userName":"alice","emailAddress":"alice@example.com","phoneNumber":"13800138000"}
关键方法对比:
| 方法 | 用途 | 返回类型 |
|---|---|---|
model_dump() |
序列化为字典 | dict |
model_dump_json() |
序列化为 JSON 字符串 | str |
model_validate(data) |
从字典/JSON 创建模型 | 模型实例 |
关键参数对比:
| 参数 | 说明 |
|---|---|
by_alias=True |
使用别名而非字段名输出 |
exclude_unset=True |
仅输出用户显式设置的字段(未设置的不输出) |
exclude_none=True |
排除值为 None 的字段 |
exclude={"field"} |
排除指定字段 |
include={"field"} |
仅包含指定字段 |
python
class Article(BaseModel):
title: str
content: str
view_count: int = 0
tags: List[str] = []
is_draft: bool = True
article = Article(title="FastAPI", content="Hello")
article.model_dump()
article.model_dump(exclude_unset=True)
article.model_dump(exclude={"content", "is_draft"})
article.model_dump(include={"title", "content"})
article.model_dump(exclude_none=True)
提示 :
exclude_unset=True在 PATCH 更新接口中特别有用------只更新用户提交的字段,未提交的字段保持原值。serialization_alias是 Pydantic V2 新增功能,允许输入用alias解析,输出用serialization_alias序列化,实现输入输出字段名分离。
6.1.4 响应与状态码
简述:FastAPI 允许为每个端点定义响应模型,控制返回数据的结构和格式。同时支持多种 HTTP 状态码,可为不同场景定义不同的响应类型和内容。
概念:响应模型
FastAPI 允许为每个端点定义响应模型,用于验证和序列化返回数据。可以定义多个响应状态码及其对应的响应模型。
定义响应模型
python
from fastapi import FastAPI, Response, status
from fastapi.responses import JSONResponse, RedirectResponse
from pydantic import BaseModel
from typing import Optional, List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
class ErrorResponse(BaseModel):
error: str
detail: str
# 使用 response_model
@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
return {"id": item_id, "name": "Phone", "price": 999.99}
# 返回列表
@app.get("/items", response_model=List[Item])
async def list_items():
return [
{"id": 1, "name": "Phone", "price": 999.99},
{"id": 2, "name": "Laptop", "price": 2999.99}
]
# 排除字段
class User(BaseModel):
id: int
username: str
email: str
password: str # 不应返回给客户端
class UserPublic(BaseModel):
id: int
username: str
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
return {"id": user_id, "username": "alice", "email": "alice@example.com", "password": "secret"}
状态码
python
from fastapi import status
@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
return {"id": 1, **item.model_dump()}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
return None
@app.get("/error", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
async def error():
return {"error": "服务器错误"}
多种响应
python
from fastapi import FastAPI, HTTPException
from typing import Union
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
200: {"model": Item, "description": "成功"},
404: {"model": ErrorResponse, "description": "未找到"},
500: {"description": "服务器错误"}
}
)
async def get_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=404, detail="Item not found")
if item_id < 0:
return Response(status_code=500)
return {"id": item_id, "name": "Phone", "price": 999.99}
JSONResponse
python
from fastapi.responses import JSONResponse
@app.get("/custom-response")
async def custom_response():
return JSONResponse(
content={"message": "Hello", "data": [1, 2, 3]},
status_code=200,
headers={"X-Custom-Header": "value"}
)
@app.get("/redirect")
async def redirect():
return RedirectResponse(url="/", status_code=302)
响应模型高级用法
使用
response_model_exclude_unset、response_model_include/exclude和response_model_by_alias精细控制响应数据的输出,解决实际项目中字段过滤、部分更新、别名输出等需求。
python
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI()
class UserFull(BaseModel):
id: int
username: str
email: str
hashed_password: str
is_active: bool = True
is_superuser: bool = False
full_name: Optional[str] = None
model_config = {"from_attributes": True}
class UserPublic(BaseModel):
id: int
username: str
email: str
model_config = {"from_attributes": True}
# 方式1:使用不同的响应模型过滤字段
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user_public(user_id: int):
return {"id": user_id, "username": "alice", "email": "a@b.com", "hashed_password": "xxx"}
# 方式2:response_model_exclude_unset - 仅返回用户显式设置的字段
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def get_item(item_id: int):
return {"id": item_id, "name": "Phone", "price": 999.99}
# 方式3:response_model_exclude - 排除敏感字段
@app.get("/users-full/{user_id}", response_model=UserFull, response_model_exclude={"hashed_password"})
async def get_user_safe(user_id: int):
return {
"id": user_id, "username": "alice", "email": "a@b.com",
"hashed_password": "secret", "is_active": True
}
# 方式4:response_model_include - 仅包含指定字段
@app.get("/users-brief/{user_id}", response_model=UserFull, response_model_include={"id", "username"})
async def get_user_brief(user_id: int):
return {
"id": user_id, "username": "alice", "email": "a@b.com",
"hashed_password": "secret"
}
# 方式5:response_model_by_alias - 使用别名输出
@app.get("/users-alias/{user_id}", response_model=UserFull, response_model_by_alias=True)
async def get_user_alias(user_id: int):
return {"id": user_id, "username": "alice", "email": "a@b.com", "hashed_password": "xxx"}
响应模型参数对比:
| 参数 | 说明 | 典型场景 |
|---|---|---|
response_model |
指定响应模型类 | 所有接口 |
response_model_exclude_unset=True |
仅输出已设置的字段 | GET 接口,避免输出默认值 |
response_model_exclude={"field"} |
排除指定字段 | 排除密码等敏感字段 |
response_model_include={"field"} |
仅包含指定字段 | 简要信息接口 |
response_model_by_alias=True |
使用别名输出 | 前端要求驼峰命名 |
提示 :推荐优先使用不同的响应模型(如
UserPublicvsUserFull),比exclude/include更清晰、更易维护。exclude_unset在 PATCH 接口中配合model_dump(exclude_unset=True)使用效果最佳。
6.1.5 依赖注入系统
简述:依赖注入是一种设计模式,FastAPI 提供了强大的声明式依赖注入系统。可以将数据库连接、认证、配置等逻辑提取为可复用的依赖项,在路由函数中按需使用。
概念:依赖注入
FastAPI 的依赖注入系统允许声明式地共享逻辑、数据连接、认证等。依赖项可以是函数、类或可调用对象,FastAPI 自动解析参数并调用。
简单依赖
python
from fastapi import Depends, FastAPI
app = FastAPI()
# 定义依赖
def get_query_param(q: str = "default"):
return q
@app.get("/items")
async def read_items(q: str = Depends(get_query_param)):
return {"query": q}
# 类形式的依赖
class QueryParams:
def __init__(self, q: str = "default", skip: int = 0, limit: int = 10):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/search")
async def search(params: QueryParams = Depends()):
return {"q": params.q, "skip": params.skip, "limit": params.limit}
数据库依赖
下面案例中说明的 FastAPI 的依赖在数据库操作的用法,包括连接数据库、查询数据、创建数据等。注意执行顺序:
python
# 模拟数据库连接
def connect_to_database():
print("1、连接数据库...")
return {"conn": "mysql_connection", "status": "connected"}
def get_db():
db = connect_to_database()
try:
yield db # 将 db 注入到路由函数
finally:
print("4、关闭数据库连接...") # 请求结束后自动执行
# ========== 在路由中使用 ==========
@app.get("/users")
def get_users(db: dict = Depends(get_db)):
print(f"查询用户,使用: {db['conn']}")
return [{"id": 1, "name": "Alice"}]
@app.post("/users")
def create_user(db: dict = Depends(get_db)):
print(f"2、创建用户,使用: {db['conn']}")
print(f"3、创建用户完成")
return {"id": 2, "name": "Bob"}
配置依赖
python
from pydantic import BaseModel
class Settings(BaseModel):
app_name: str = "FastAPI App"
debug: bool = False
database_url: str
settings = Settings(app_name="My App", debug=True, database_url="sqlite:///./db.sqlite")
@app.get("/settings")
async def get_settings(current_settings: Settings = Depends(lambda: settings)):
return current_settings.model_dump()
依赖链
python
# 依赖链示例
def get_db():
return {"connection": "db_connection"}
def get_current_user(db: dict = Depends(get_db)):
return {"user_id": 1, "username": "alice"}
def verify_admin(user: dict = Depends(get_current_user)):
if user.get("role") != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@app.get("/admin/dashboard")
async def admin_dashboard(admin: dict = Depends(verify_admin)):
return {"message": "欢迎管理员"}
可选依赖
python
from typing import Optional
def get_optional_db():
return {"db": "connected"}
@app.get("/optional")
async def optional_dep(db: Optional[dict] = Depends(get_optional_db)):
if db:
return {"status": "connected"}
return {"status": "not_connected"}
循环依赖问题
FastAPI 循环依赖问题:当依赖项之间存在循环引用时,FastAPI 会报错。解决方法是避免循环依赖,或者使用 Depends 注解指定依赖项的顺序。
6.1.6 中间件
简述:中间件是在请求处理前后执行的函数,可以修改请求或响应、添加日志、处理 CORS、计算响应时间等。FastAPI 支持内置中间件和自定义中间件,按添加顺序依次执行。
概念:中间件
中间件是在请求处理前后执行的函数,可以修改请求或响应、记录日志、添加性能计时等。
内置中间件
python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
import time
app = FastAPI()
# CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 允许的源
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# GZip 压缩中间件
app.add_middleware(GZipMiddleware, minimum_size=1000)
# 自定义中间件
@app.middleware("http")
async def add_process_time_header(request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
@app.middleware("http")
async def log_requests(request, call_next):
print(f"请求: {request.method} {request.url.path}")
response = await call_next(request)
print(f"响应: {response.status_code}")
return response
CORS 详解
python
# 详细 CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"http://localhost:8080",
"https://myapp.com"
],
allow_origin_regex="https://.*\.myapp\.com", # 正则匹配
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
expose_headers=["X-Custom-Header"],
max_age=600 # 预检请求缓存时间(秒)
)
中间件顺序
FastAPI 的中间件执行顺序是后进先出(LIFO),也就是后定义的中间件先执行请求,后定义的后响应。
python
app = FastAPI()
# 中间件按添加顺序执行
# 顺序: TrustedHost -> GZip -> CORS -> 自定义 -> 路由
app.add_middleware(GZipMiddleware, minimum_size=500)
app.add_middleware(CORSMiddleware, allow_origins=["*"])
@app.middleware("http")
async def middleware1(request, call_next):
print("中间件执行 1. 请求前")
response = await call_next(request)
print("中间件执行 1. 响应后 - 在这里执行后续操作")
return response
@app.middleware("http")
async def middleware2(request, call_next):
print("中间件执行 2. 请求前")
response = await call_next(request)
print("中间件执行 2. 响应后 - 在这里执行后续操作")
return response
@app.get("/")
async def root():
print("业务逻辑在中间执行")
return {"message": "Hello"}
调用 root 接口最后输出:
中间件执行 2. 请求前
中间件执行 1. 请求前
业务逻辑在中间执行
中间件执行 1. 响应后 - 在这里执行后续操作
中间件执行 2. 响应后 - 在这里执行后续操作
6.1.7 错误处理与异常
简述:FastAPI 提供了 HTTPException 用于抛出 HTTP 错误响应,同时支持自定义异常处理器和全局异常处理。可以统一处理业务异常、验证错误和服务器错误,提升 API 的健壮性。
概念:HTTPException
HTTPException 用于抛出 HTTP 错误响应,可以指定状态码和错误信息。
HTTPException
python
from fastapi import HTTPException, status
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id < 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户ID不能为负数",
headers={"X-Error": "invalid_id"}
)
if user_id > 1000:
raise HTTPException(status_code=404, detail="用户不存在")
return {"user_id": user_id}
自定义异常处理器
概念 :自定义异常处理器用于捕获和处理项目中定义的业务异常。通过 @app.exception_handler() 装饰器注册,将特定异常类型映射到统一的响应格式,适合统一错误码和错误结构的场景。
python
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
app = FastAPI()
class CustomException(Exception):
def __init__(self, name: str, message: str):
self.name = name
self.message = message
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={
"error": exc.name,
"message": exc.message,
"path": str(request.url)
}
)
@app.get("/custom-error")
async def raise_custom_error():
raise CustomException("BusinessError", "业务逻辑错误")
全局异常处理器
概念 :全局异常处理器使用 Exception 作为异常类型,捕获所有未被特定处理器处理的异常。通常放在最后注册,作为"兜底"处理器,用于记录日志、返回友好的错误信息。生产环境中不应暴露详细的错误堆栈。
python
from fastapi.responses import JSONResponse
import traceback
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
# 记录日志
print(f"全局异常: {exc}")
traceback.print_exc()
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"message": "服务器内部错误",
"detail": str(exc) if app.debug else None
}
)
ValidationError 处理
概念 :RequestValidationError 是 FastAPI 自动抛出的验证异常,当请求数据不符合 Pydantic 模型定义时触发。自定义处理器可以将验证错误格式化为更友好的响应,指明哪个字段出错及错误原因。
python
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = []
for error in exc.errors():
errors.append({
"field": ".".join(str(loc) for loc in error["loc"]),
"message": error["msg"],
"type": error["type"]
})
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"message": "数据验证失败",
"errors": errors
}
)
6.1.8 swagger文档配置
简述:FastAPI 允许为每个路由配置额外的元数据,如标签、摘要、描述、状态码等,这些信息会显示在自动生成的 API 文档中。使用 APIRouter 可以模块化管理大量路由。
概念:路径操作配置
FastAPI 允许为每个路由配置额外的元数据,如标签、摘要、描述、状态码等,这些信息会显示在自动生成的 API 文档中。
路径操作装饰器参数
python
from fastapi import FastAPI, status
from typing import List
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tags: List[str] = []
@app.post(
"/items",
response_model=Item,
status_code=status.HTTP_201_CREATED,
tags=["items"],
summary="创建商品",
description="创建一个新的商品,支持多标签",
responses={
201: {"description": "商品创建成功"},
400: {"description": "请求参数错误"},
401: {"description": "未授权"}
},
deprecated=False
)
async def create_item(item: Item):
return item
@app.get(
"/items/{item_id}",
tags=["items"],
summary="获取商品详情",
description="根据ID获取商品详细信息",
response_description="商品详情信息"
)
async def get_item(item_id: int):
return {"name": "Phone", "price": 999.99}
# 使用 deprecated 标记
@app.get("/old-items", tags=["items"], deprecated=True)
async def get_old_items():
return [{"id": 1}]
APIRouter 模块化
python
from fastapi import APIRouter, Depends, HTTPException
from typing import List
router = APIRouter(prefix="/users", tags=["用户管理"])
admin_router = APIRouter(prefix="/admin", tags=["管理后台"])
# 用户相关路由
@router.get("/", response_model=List[dict])
async def list_users(skip: int = 0, limit: int = 10):
return [{"id": i} for i in range(skip, skip + limit)]
@router.get("/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "Alice"}
@router.post("/", status_code=201)
async def create_user(name: str):
return {"id": 1, "name": name}
# 管理员路由
@admin_router.get("/stats")
async def get_stats():
return {"total_users": 100}
# 注册路由
app = FastAPI()
app.include_router(router)
app.include_router(admin_router)
# 访问: GET /users/
# 访问: GET /users/{user_id}
# 访问: POST /users/
# 访问: GET /admin/stats
6.1.9 Background Tasks(后台任务)
简述 :Background Tasks 允许在响应返回后执行耗时操作(如发送邮件、日志记录、数据处理),不阻塞 API 响应。FastAPI 内置
BackgroundTasks类,无需额外依赖即可使用。
概念:后台任务
后台任务是在 HTTP 响应返回给客户端之后才执行的函数。适用于不需要即时返回结果的操作,如发送通知邮件、写入日志、更新缓存等。与 Celery 等分布式任务队列相比,Background Tasks 更轻量,适合简单场景。
基础用法
在路由函数中注入
BackgroundTasks,通过add_task()添加后台任务,支持传参。
python
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel, EmailStr
app = FastAPI()
class MessageCreate(BaseModel):
email: EmailStr
subject: str
content: str
def send_email(email: str, subject: str, content: str):
import time
time.sleep(3)
print(f"邮件已发送: {email}, 主题: {subject}")
def write_log(message: str):
with open("app.log", "a", encoding="utf-8") as f:
f.write(f"{message}\n")
@app.post("/messages")
async def create_message(
message: MessageCreate,
background_tasks: BackgroundTasks
):
background_tasks.add_task(send_email, message.email, message.subject, message.content)
background_tasks.add_task(write_log, f"新消息: {message.subject}")
return {"message": "消息已接收,邮件将在后台发送"}
依赖注入中使用后台任务
在依赖函数中添加后台任务,路由函数无需直接操作
BackgroundTasks。
python
from fastapi import Depends, BackgroundTasks
def log_request(background_tasks: BackgroundTasks, request_id: str = "default"):
background_tasks.add_task(write_log, f"请求处理完成: {request_id}")
return {"logged": True}
@app.get("/items/{item_id}")
async def get_item(item_id: int, logger=Depends(log_request)):
return {"item_id": item_id, "logged": logger["logged"]}
带状态的后台任务
使用类封装后台任务逻辑,支持状态跟踪和错误处理。
python
import logging
from typing import Optional
logger = logging.getLogger("app.tasks")
class EmailTask:
def __init__(self, email: str, subject: str, content: str):
self.email = email
self.subject = subject
self.content = content
self.status: str = "pending"
def __call__(self):
try:
self.status = "running"
import time
time.sleep(2)
print(f"发送邮件到 {self.email}: {self.subject}")
self.status = "completed"
except Exception as e:
self.status = "failed"
logger.error(f"邮件发送失败: {e}")
@app.post("/send-email")
async def send_email_api(
message: MessageCreate,
background_tasks: BackgroundTasks
):
task = EmailTask(message.email, message.subject, message.content)
background_tasks.add_task(task)
return {
"message": "邮件任务已提交",
"email": message.email
}
Background Tasks vs Celery
| 对比项 | Background Tasks | Celery |
|---|---|---|
| 依赖 | FastAPI 内置 | 需安装 Celery + Redis/RabbitMQ |
| 持久化 | 无(进程重启丢失) | 有(任务持久化到队列) |
| 重试 | 不支持自动重试 | 支持自动重试 |
| 分布式 | 单进程 | 多 worker 分布式执行 |
| 监控 | 无 | Flower 等监控工具 |
| 定时任务 | 不支持 | 支持 |
| 适用场景 | 简单、轻量任务 | 复杂、可靠性要求高的任务 |
提示:Background Tasks 适合发邮件、写日志等简单场景。如果需要任务持久化、重试、定时或分布式执行,应使用 Celery(见第6步-1-3-部署与实战(file:///d:/kaicofile/trae/study_python/第6步-1-3-FastAPI框架-部署与实战.md))。
6.1.10 Static Files(静态文件托管)
简述 :FastAPI 通过
StaticFiles中间件托管静态资源文件(CSS、JS、图片等),适用于前后端不分离项目或需要提供文件下载服务的场景。
概念:静态文件
在 Web 应用中,CSS、JavaScript、图片等静态资源需要通过 HTTP 服务提供给客户端。FastAPI 使用 Starlette 的 StaticFiles 组件挂载静态文件目录。
基础用法
使用
app.mount()挂载静态文件目录,指定 URL 路径前缀和文件系统路径。
bash
pip install aiofiles
python
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def root():
return {
"css": "/static/css/style.css",
"js": "/static/js/app.js",
"image": "/static/images/logo.png"
}
目录结构:
myproject/
├── static/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
├── main.py
多个静态目录与媒体文件
分别挂载静态资源和用户上传的媒体文件,使用不同的 URL 路径前缀区分。
python
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import os
app = FastAPI()
os.makedirs("static", exist_ok=True)
os.makedirs("media", exist_ok=True)
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/media", StaticFiles(directory="media"), name="media")
返回 HTML 页面
使用
HTMLResponse返回 HTML 页面,结合静态文件实现简单的页面渲染。
python
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/", response_class=HTMLResponse)
async def index():
return """
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<h1>FastAPI 静态文件示例</h1>
<img src="/static/images/logo.png" alt="Logo">
<script src="/static/js/app.js"></script>
</body>
</html>
"""
提示 :前后端分离项目中,前端通常由 Nginx 或 CDN 托管,不需要 FastAPI 的
StaticFiles。此功能主要用于:1)提供上传文件的访问服务;2)小型项目或管理后台的简单页面渲染。
📖 下一步:FastAPI框架-高级部分 数据库集成、认证与安全、WebSocket、SSE、限流、Alembic 迁移等