FastAPI深度解析:从入门到精通
目录
- 技术亮点
- 第一章:FastAPI概述
- 第二章:快速入门
- 第三章:核心概念深度解析
- 第四章:高级特性深度解析
- 第五章:数据库集成和ORM
- 第六章:安全认证和授权
- 第七章:测试和部署
- 第八章:最佳实践和性能优化
- 第九章:实际项目案例:博客API系统
- 第十章:总结和进阶学习
【技术亮点】:🚀 现代Python Web开发的终极选择 - FastAPI以其卓越的性能、直观的API设计和强大的类型提示,正在重新定义Python后端开发的标准。本文将从零开始,深入剖析FastAPI的核心原理、最佳实践和高级特性。
一、FastAPI概述:为什么选择FastAPI?
1.1 FastAPI的核心优势
FastAPI是一个现代、快速(高性能)的Web框架,用于构建API,基于Python 3.6+的标准类型提示。
主要特性:
- ⚡ 极高性能:与NodeJS和Go相当,是现有Python框架中最快的之一
- 🎯 快速开发:开发功能的速度提高约200%到300%
- 🔒 减少错误:减少约40%的人为(开发者)错误
- 💡 直观易懂:强大的编辑器支持,自动补全无处不在
- 📚 标准化:基于(并完全兼容)API的开放标准:OpenAPI和JSON Schema
1.2 性能对比:FastAPI vs 其他框架
# 性能基准测试对比(请求/秒)
框架对比 = {
"FastAPI": 50000, # 基于Starlette和Pydantic
"Flask": 12000, # 传统WSGI框架
"Django": 8000, # 全功能Web框架
"Node.js": 45000, # JavaScript运行时
"Go": 60000 # 编译型语言
}
1.3 技术栈架构
FastAPI架构层次:
┌─────────────────┐
│ FastAPI │ ← 高级API框架
├─────────────────┤
│ Starlette │ ← 异步Web框架
├─────────────────┤
│ Pydantic │ ← 数据验证和序列化
├─────────────────┤
│ Uvicorn │ ← ASGI服务器
└─────────────────┘
二、快速入门:第一个FastAPI应用
2.1 环境准备和安装
# 创建虚拟环境
python -m venv fastapi-env
fastapi-env\Scripts\activate # Windows
# 安装FastAPI和相关依赖
pip install fastapi uvicorn
pip install "uvicorn[standard]" # 包含额外性能优化
2.2 基础应用示例
# main.py
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
# 创建FastAPI实例
app = FastAPI(
title="我的第一个FastAPI应用",
description="这是一个演示FastAPI基础功能的示例",
version="1.0.0"
)
# 定义数据模型
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
# 根路径路由
@app.get("/")
async def read_root():
return {"message": "欢迎使用FastAPI!"}
# 带路径参数的路由
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
# POST请求处理
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
# 启动应用
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
2.3 运行和测试
# 启动服务器
uvicorn main:app --reload --host 0.0.0.0 --port 8000
# 测试API
curl http://localhost:8000/
curl http://localhost:8000/items/42?q=test
curl -X POST "http://localhost:8000/items/" -H "Content-Type: application/json" -d '{"name":"Foo","price":45.2}'
三、核心概念深度解析
3.1 路由系统详解
3.1.1 路径操作装饰器
from fastapi import FastAPI, Path, Query
app = FastAPI()
# 各种HTTP方法
@app.get("/items/{item_id}")
async def get_item(item_id: int):
return {"method": "GET", "item_id": item_id}
@app.post("/items/")
async def create_item():
return {"method": "POST"}
@app.put("/items/{item_id}")
async def update_item(item_id: int):
return {"method": "PUT", "item_id": item_id}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"method": "DELETE", "item_id": item_id}
@app.patch("/items/{item_id}")
async def partial_update(item_id: int):
return {"method": "PATCH", "item_id": item_id}
3.1.2 路径参数和验证
from fastapi import Path
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., title="商品ID", description="商品的唯一标识符", ge=1, le=1000),
q: str = Query(None, min_length=3, max_length=50)
):
"""
获取商品信息
- **item_id**: 商品ID,必须是1-1000之间的整数
- **q**: 可选查询参数,长度3-50字符
"""
return {"item_id": item_id, "q": q}
3.2 请求数据处理
3.2.1 查询参数
from fastapi import Query
from typing import List, Optional
@app.get("/items/")
async def read_items(
skip: int = Query(0, description="跳过的记录数"),
limit: int = Query(100, le=1000, description="返回的记录数"),
tags: List[str] = Query([], description="标签过滤")
):
return {"skip": skip, "limit": limit, "tags": tags}
3.2.2 请求体
from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=50, example="john_doe")
email: str = Field(..., regex=r"^[\w\.-]+@[\w\.-]+\.\w+$", example="john@example.com")
age: int = Field(..., ge=0, le=150, example=25)
is_active: bool = Field(True, description="用户是否激活")
@app.post("/users/")
async def create_user(user: User):
return {"user": user.dict(), "message": "用户创建成功"}
3.3 响应模型和状态码
from fastapi import status
from pydantic import BaseModel
class UserResponse(BaseModel):
id: int
username: str
email: str
is_active: bool
@app.post(
"/users/",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
summary="创建用户",
description="创建一个新用户并返回用户信息"
)
async def create_user(user: User):
# 模拟创建用户
user_data = user.dict()
user_data["id"] = 1 # 模拟ID生成
return UserResponse(**user_data)
四、高级特性深度解析
4.1 依赖注入系统
依赖注入是FastAPI框架的核心特性之一,它允许您以声明式的方式管理和使用组件依赖关系,使代码更加模块化、可测试和可维护。
4.1.1 基础依赖注入
from fastapi import Depends, FastAPI
app = FastAPI()
# 简单的依赖函数
def get_db():
# 模拟数据库连接
db = {"connected": True}
try:
yield db # 使用yield而不是return,支持资源清理
finally:
db["connected"] = False # 清理资源
# 使用依赖
@app.get("/items/")
async def read_items(db: dict = Depends(get_db)):
return {"database_status": db["connected"]}
在这个例子中:
get_db()是一个依赖函数,它创建并返回一个数据库连接read_items函数通过Depends(get_db)声明它需要get_db函数提供的依赖- FastAPI会在处理请求时自动调用
get_db()并将其返回值传递给read_items
4.1.2 类依赖和参数化依赖
from fastapi import Depends
class Pagination:
def __init__(self, skip: int = 0, limit: int = 100):
self.skip = skip
self.limit = limit
def get_pagination(skip: int = 0, limit: int = 100) -> Pagination:
return Pagination(skip=skip, limit=limit)
@app.get("/items/")
async def read_items(pagination: Pagination = Depends(get_pagination)):
return {
"skip": pagination.skip,
"limit": pagination.limit,
"message": f"查询第{pagination.skip}到{pagination.skip + pagination.limit}条记录"
}
4.1.3 依赖项的嵌套使用
依赖项可以依赖于其他依赖项,形成依赖链:
from fastapi import Depends, Header, HTTPException
def get_token(token: str = Header(...)):
if not token:
raise HTTPException(status_code=401, detail="缺少认证令牌")
return token
def get_current_user(token: str = Depends(get_token)):
# 使用token获取用户信息
# 这里模拟从数据库获取用户
if token == "valid-token":
return {"username": "john", "token": token}
raise HTTPException(status_code=401, detail="无效的认证令牌")
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
4.1.4 带有子依赖的类
from typing import Optional
from fastapi import Depends, Query
class CommonQueryParams:
def __init__(
self,
q: Optional[str] = None,
skip: int = 0,
limit: int = 100
):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/users/")
async def read_users(commons: CommonQueryParams = Depends()):
response = {}
if commons.q:
response.update({"q": commons.q})
response.update(
{"skip": commons.skip, "limit": commons.limit}
)
return response
4.1.5 在路径装饰器中使用依赖
有时候,我们不需要在函数内部使用依赖项返回值,但需要执行依赖项逻辑(如认证、权限验证):
from fastapi import Depends, HTTPException, status
async def verify_token(token: str = Header(...)):
if token != "valid-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的认证令牌"
)
async def verify_permissions(current_user: dict = Depends(get_current_user)):
if current_user.get("role") != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="权限不足"
)
@app.get("/admin/dashboard", dependencies=[Depends(verify_token), Depends(verify_permissions)])
async def read_admin_dashboard():
return {"message": "欢迎访问管理员面板"}
4.1.6 依赖项的高级用法
使用yield进行资源管理
当依赖项需要资源清理时(如关闭数据库连接),可以使用 yield 而不是 return:
from sqlalchemy.orm import Session
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db # 在yield之前的部分在请求处理前执行
finally:
db.close() # 在yield之后的部分在请求处理后执行
依赖项的缓存机制
FastAPI默认在单个请求中缓存依赖项的返回值,即使同一个依赖项被多次使用也只会调用一次:
async def get_db():
print("创建数据库连接") # 这个打印只会在一次请求中出现一次
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/items/")
async def read_items(db: Session = Depends(get_db), another_db: Session = Depends(get_db)):
# 尽管使用了两次get_db依赖,但"创建数据库连接"只会打印一次
return {"message": "获取数据成功"}
禁用依赖项缓存
如果需要每次都重新计算依赖项,可以使用 use_cache=False 参数:
@app.get("/items/")
async def read_items(
db: Session = Depends(get_db, use_cache=False),
another_db: Session = Depends(get_db, use_cache=False)
):
# 现在每次使用get_db都会重新调用
return {"message": "获取数据成功"}
4.1.7 实际应用场景
数据库会话管理
from sqlalchemy.orm import Session
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/")
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = User(**user.dict())
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
用户认证
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# 验证token并获取用户信息
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
权限控制
async def get_current_active_user(
current_user: User = Depends(get_current_user)
):
if current_user.disabled:
raise HTTPException(status_code=400, detail="用户已被禁用")
return current_user
@app.get("/users/me/items")
async def read_own_items(
current_user: User = Depends(get_current_active_user)
):
return [{"item_id": "Foo", "owner": current_user.username}]
4.1.8 依赖注入的优势
- 代码复用:将常用功能(如数据库连接、认证)定义为依赖项,可在多个路径操作中重复使用
- 减少代码重复:避免在每个路径操作函数中重复相同的逻辑
- 简化测试:可以轻松替换依赖项进行单元测试
- 提高可维护性:依赖项的修改只需要在一处进行
- 自动集成:FastAPI自动处理依赖项的解析和注入
4.1.9 最佳实践
- 使用yield进行资源管理:对于需要清理的资源(如数据库连接),使用yield而不是return
- 合理拆分依赖项:将复杂的依赖项拆分为多个小依赖项,提高复用性
- 类型提示:为依赖项提供准确的类型提示,提高代码可读性和IDE支持
- 错误处理:在依赖项中适当处理错误情况,抛出HTTPException
- 依赖项命名:使用清晰的命名约定,使依赖项的用途一目了然
通过合理使用FastAPI的依赖注入系统,您可以构建出结构清晰、易于扩展的Web应用程序。依赖注入不仅是一种技术实现,更是一种设计思想,它帮助您编写更加模块化、可测试和可维护的代码。
4.2 中间件和异常处理
4.2.1 自定义中间件
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: 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: Request, call_next):
print(f"收到请求: {request.method} {request.url}")
response = await call_next(request)
print(f"响应状态: {response.status_code}")
return response
4.2.2 异常处理
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI()
class ItemNotFound(Exception):
def __init__(self, item_id: int):
self.item_id = item_id
@app.exception_handler(ItemNotFound)
async def item_not_found_handler(request: Request, exc: ItemNotFound):
return JSONResponse(
status_code=404,
content={"message": f"商品 {exc.item_id} 未找到"}
)
items = {1: {"name": "Foo"}, 2: {"name": "Bar"}}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items:
raise ItemNotFound(item_id)
return {"item": items[item_id]}
4.3 后台任务和WebSocket
4.3.1 后台任务
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(f"{message}\n")
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks
):
background_tasks.add_task(write_log, f"通知发送到 {email}")
return {"message": "通知已在后台发送"}
4.3.2 WebSocket实时通信
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI()
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"你说: {data}", websocket)
await manager.broadcast(f"客户端 #{client_id} 说: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"客户端 #{client_id} 离开了聊天室")
五、数据库集成和ORM
5.1 SQLAlchemy集成
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 数据库配置
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# 定义模型
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
# 创建表
Base.metadata.create_all(bind=engine)
# 依赖注入数据库会话
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
5.2 完整的CRUD操作
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from passlib.context import CryptContext
# 密码哈希上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_password_hash(password):
"""将明文密码转换为哈希密码,用于安全存储"""
return pwd_context.hash(password)
def verify_password(plain_password, hashed_password):
"""验证明文密码与哈希密码是否匹配"""
return pwd_context.verify(plain_password, hashed_password)
class UserCreate(BaseModel):
username: str
email: str
password: str
class UserResponse(BaseModel):
id: int
username: str
email: str
@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
# 检查用户名是否已存在
db_user = db.query(User).filter(User.username == user.username).first()
if db_user:
raise HTTPException(status_code=400, detail="用户名已存在")
# 检查邮箱是否已被使用
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="邮箱已被使用")
# 创建用户,使用密码哈希存储密码
hashed_password = get_password_hash(user.password)
db_user = User(
username=user.username,
email=user.email,
hashed_password=hashed_password # 安全地哈希密码
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return UserResponse(**db_user.__dict__)
@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="用户未找到")
return UserResponse(**user.__dict__)
@app.put("/users/{user_id}", response_model=UserResponse)
async def update_user(
user_id: int,
user_update: UserCreate,
db: Session = Depends(get_db)
):
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="用户未找到")
# 检查新用户名是否已被其他用户使用
if user.username != user_update.username:
existing_user = db.query(User).filter(User.username == user_update.username).first()
if existing_user:
raise HTTPException(status_code=400, detail="用户名已存在")
# 检查新邮箱是否已被其他用户使用
if user.email != user_update.email:
existing_user = db.query(User).filter(User.email == user_update.email).first()
if existing_user:
raise HTTPException(status_code=400, detail="邮箱已被使用")
# 更新用户信息
user.username = user_update.username
user.email = user_update.email
# 只有当提供了新密码时才更新密码
if user_update.password:
user.hashed_password = get_password_hash(user_update.password)
db.commit()
db.refresh(user)
return UserResponse(**user.__dict__)
@app.delete("/users/{user_id}")
async def delete_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="用户未找到")
db.delete(user)
db.commit()
return {"message": "用户已成功删除"}
# 用户登录示例
class UserLogin(BaseModel):
username: str
password: str
@app.post("/users/login")
async def login(user_login: UserLogin, db: Session = Depends(get_db)):
# 查找用户
user = db.query(User).filter(User.username == user_login.username).first()
if not user:
raise HTTPException(status_code=401, detail="用户名或密码错误")
# 验证密码
if not verify_password(user_login.password, user.hashed_password):
raise HTTPException(status_code=401, detail="用户名或密码错误")
# 返回用户信息(不包含密码)
return {"message": "登录成功", "user_id": user.id, "username": user.username}
重要提示:在实际应用中,永远不要以明文形式存储用户密码。上面的示例展示了如何使用
get_password_hash函数安全地哈希密码后再存储到数据库中。这样即使数据库被泄露,攻击者也无法直接获取用户的明文密码。此外,在更新用户信息时,只有当提供了新密码时才更新密码哈希,避免不必要的密码重新哈希操作。
六、安全认证和授权
6.1 JWT认证
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
# 安全配置
SECRET_KEY = "your-secret-key" # 在生产环境中应使用环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 密码哈希上下文,使用bcrypt算法
# bcrypt是当前最安全的密码哈希算法之一,它包含盐值并具有可调整的计算成本
# bcrypt算法会自动为每个密码生成唯一的盐值,有效抵御彩虹表攻击
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
"""验证明文密码与哈希密码是否匹配"""
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
"""将明文密码转换为哈希密码,用于安全存储"""
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: timedelta = None):
"""创建JWT访问令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# 完整的用户认证流程示例
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None
class UserInDB(User):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
# 模拟用户数据库
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": get_password_hash("secret"), # 使用哈希密码
"disabled": False,
}
}
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user
@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
# 完整的用户认证流程示例
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None
class UserInDB(User):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
# 模拟用户数据库
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": get_password_hash("secret"), # 使用哈希密码
"disabled": False,
}
}
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user
@app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
安全性提示:在实际应用中,永远不要以明文形式存储用户密码。应始终使用像bcrypt这样的强密码哈希算法对密码进行哈希处理。bcrypt算法会自动为每个密码生成唯一的盐值,并且具有可调整的计算成本,可以有效抵御彩虹表攻击和暴力破解。即使数据库被泄露,攻击者也无法直接获取用户的明文密码。
6.2 OAuth2密码流
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
import os
# 从环境变量获取密钥,而不是硬编码在代码中
SECRET_KEY = os.environ.get("SECRET_KEY") or "your-secret-key"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# 这里应该从数据库获取用户信息
user = {"username": username}
if user is None:
raise credentials_exception
return user
@app.get("/users/me/")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
生产环境安全建议:
- 永远不要将密钥硬编码在代码中,应使用环境变量或密钥管理服务
- 使用强密钥,建议使用随机生成的长字符串
- 定期轮换密钥以增强安全性
- 在JWT负载中只存储必要的用户标识信息,不要存储敏感数据
七、测试和部署
7.1 单元测试
# test_main.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "欢迎使用FastAPI!"}
def test_create_item():
response = client.post(
"/items/",
json={"name": "测试商品", "price": 9.99}
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "测试商品"
assert data["price"] == 9.99
def test_read_item_not_found():
response = client.get("/items/999")
assert response.status_code == 404
7.2 Docker部署
# Dockerfile
FROM python:3.9
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/app
depends_on:
- db
db:
image: postgres:13
environment:
- POSTGRES_DB=app
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
八、最佳实践和性能优化
8.1 项目结构最佳实践
my_fastapi_app/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI应用实例
│ ├── api/ # API路由
│ │ ├── __init__.py
│ │ ├── endpoints/ # 端点模块
│ │ │ ├── items.py
│ │ │ ├── users.py
│ │ │ └── auth.py
│ │ └── dependencies.py # 依赖注入
│ ├── core/ # 核心配置
│ │ ├── config.py
│ │ └── security.py
│ ├── db/ # 数据库相关
│ │ ├── session.py
│ │ └── models.py
│ ├── schemas/ # Pydantic模型
│ │ ├── user.py
│ │ └── item.py
│ └── services/ # 业务逻辑
│ ├── user_service.py
│ └── item_service.py
├── tests/ # 测试代码
├── requirements.txt
└── Dockerfile
8.2 性能优化技巧
# 1. 使用异步操作
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/slow-operation")
async def slow_operation():
# 使用异步等待而不是同步阻塞
await asyncio.sleep(1) # 模拟IO操作
return {"message": "操作完成"}
# 2. 响应缓存
from fastapi import Response
from datetime import datetime
@app.get("/cached-data")
async def cached_data(response: Response):
# 设置缓存头
response.headers["Cache-Control"] = "public, max-age=3600"
return {"data": "缓存的数据", "timestamp": datetime.now().isoformat()}
# 3. 数据库连接池优化
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine(
"postgresql://user:pass@localhost/db",
poolclass=QueuePool,
pool_size=10,
max_overflow=20,
pool_pre_ping=True
)
8.3 API版本控制
在实际项目中,随着业务发展,API可能需要进行不兼容的更新。通过版本控制,可以确保现有客户端不受影响。
8.3.1 URL路径版本控制
from fastapi import FastAPI
# 创建不同版本的应用实例
app_v1 = FastAPI(title="API v1")
app_v2 = FastAPI(title="API v2")
# v1版本的端点
@app_v1.get("/users/{user_id}")
async def read_user_v1(user_id: int):
return {"user_id": user_id, "version": "v1", "name": "User Name"}
# v2版本的端点
@app_v2.get("/users/{user_id}")
async def read_user_v2(user_id: int):
return {
"id": user_id,
"version": "v2",
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
# 主应用,根据路径前缀分发到不同版本
app = FastAPI()
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)
8.3.2 请求头版本控制
from fastapi import FastAPI, Header, HTTPException
from typing import Optional
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
api_version: Optional[str] = Header(None)
):
if api_version == "v2":
return {
"id": user_id,
"version": "v2",
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
elif api_version == "v1" or api_version is None:
return {"user_id": user_id, "version": "v1", "name": "User Name"}
else:
raise HTTPException(status_code=400, detail="Unsupported API version")
8.3.3 版本迁移策略和向后兼容性
版本迁移最佳实践:
- 渐进式迁移:
- 同时维护多个版本的API
- 为旧版本提供弃用通知
- 设置合理的版本支持周期
from fastapi import FastAPI, Header, HTTPException, Response
from typing import Optional
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
api_version: Optional[str] = Header(None),
response: Response = None
):
if api_version == "v2":
return {
"id": user_id,
"version": "v2",
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
elif api_version == "v1" or api_version is None:
# 为v1版本添加弃用通知
if response:
response.headers["X-API-Deprecation"] = "true"
response.headers["X-API-Deprecation-Date"] = "2023-12-31"
response.headers["Link"] = '</api-docs>; rel="latest-version"'
return {"user_id": user_id, "version": "v1", "name": "User Name"}
else:
raise HTTPException(status_code=400, detail="Unsupported API version")
- 向后兼容性设计:
- 新版本API应尽量保持向后兼容
- 只添加新字段,不删除或修改现有字段
- 使用可选字段扩展功能
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
# v1版本的用户模型
class UserV1(BaseModel):
id: int
username: str
email: str
# v2版本的用户模型,向后兼容v1
class UserV2(BaseModel):
id: int
username: str
email: str
phone: Optional[str] = None # 新增可选字段
created_at: Optional[str] = None # 新增可选字段
# API端点,根据版本返回不同格式的数据
@app.get("/users/{user_id}")
async def read_user(user_id: int, version: str = "v1"):
user_data = {"id": user_id, "username": "User Name", "email": "user@example.com"}
if version == "v2":
# 添加v2特有字段
user_data.update({
"phone": "123-456-7890",
"created_at": "2023-01-01T00:00:00Z"
})
return UserV2(**user_data)
else:
return UserV1(**user_data)
- API版本生命周期管理:
- 明确定义每个版本的支持周期
- 提前通知版本弃用计划
- 提供迁移指南和工具
from fastapi import FastAPI, HTTPException
from datetime import datetime
app = FastAPI()
# API版本信息
API_VERSIONS = {
"v1": {
"status": "deprecated",
"deprecation_date": "2023-06-01",
"end_of_life_date": "2023-12-31",
"migration_guide": "https://example.com/api/v1-to-v2-migration"
},
"v2": {
"status": "current",
"release_date": "2023-01-15"
},
"v3": {
"status": "beta",
"release_date": "2023-11-01"
}
}
@app.get("/api/versions")
async def get_api_versions():
"""获取所有API版本信息"""
return API_VERSIONS
@app.get("/api/versions/{version}")
async def get_api_version_info(version: str):
"""获取特定API版本信息"""
if version not in API_VERSIONS:
raise HTTPException(status_code=404, detail="API version not found")
version_info = API_VERSIONS[version]
# 检查版本是否已过期
if "end_of_life_date" in version_info:
eol_date = datetime.strptime(version_info["end_of_life_date"], "%Y-%m-%d")
if datetime.now() > eol_date:
raise HTTPException(
status_code=410,
detail=f"API version {version} has reached end of life"
)
return version_info
from fastapi import FastAPI, Header, HTTPException
from typing import Optional
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
api_version: Optional[str] = Header(None)
):
if api_version == "v2":
return {
"id": user_id,
"version": "v2",
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
elif api_version == "v1" or api_version is None:
return {"user_id": user_id, "version": "v1", "name": "User Name"}
else:
raise HTTPException(status_code=400, detail="Unsupported API version")
8.3.3 版本迁移策略和向后兼容性
版本迁移最佳实践:
- 渐进式迁移:
- 同时维护多个版本的API
- 为旧版本提供弃用通知
- 设置合理的版本支持周期
from fastapi import FastAPI, Header, HTTPException, Response
from typing import Optional
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(
user_id: int,
api_version: Optional[str] = Header(None),
response: Response = None
):
if api_version == "v2":
return {
"id": user_id,
"version": "v2",
"name": "User Name",
"email": "user@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
elif api_version == "v1" or api_version is None:
# 为v1版本添加弃用通知
if response:
response.headers["X-API-Deprecation"] = "true"
response.headers["X-API-Deprecation-Date"] = "2023-12-31"
response.headers["Link"] = '</api-docs>; rel="latest-version"'
return {"user_id": user_id, "version": "v1", "name": "User Name"}
else:
raise HTTPException(status_code=400, detail="Unsupported API version")
- 向后兼容性设计:
- 新版本API应尽量保持向后兼容
- 只添加新字段,不删除或修改现有字段
- 使用可选字段扩展功能
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
# v1版本的用户模型
class UserV1(BaseModel):
id: int
username: str
email: str
# v2版本的用户模型,向后兼容v1
class UserV2(BaseModel):
id: int
username: str
email: str
phone: Optional[str] = None # 新增可选字段
created_at: Optional[str] = None # 新增可选字段
# API端点,根据版本返回不同格式的数据
@app.get("/users/{user_id}")
async def read_user(user_id: int, version: str = "v1"):
user_data = {"id": user_id, "username": "User Name", "email": "user@example.com"}
if version == "v2":
# 添加v2特有字段
user_data.update({
"phone": "123-456-7890",
"created_at": "2023-01-01T00:00:00Z"
})
return UserV2(**user_data)
else:
return UserV1(**user_data)
- API版本生命周期管理:
- 明确定义每个版本的支持周期
- 提前通知版本弃用计划
- 提供迁移指南和工具
from fastapi import FastAPI, HTTPException
from datetime import datetime
app = FastAPI()
# API版本信息
API_VERSIONS = {
"v1": {
"status": "deprecated",
"deprecation_date": "2023-06-01",
"end_of_life_date": "2023-12-31",
"migration_guide": "https://example.com/api/v1-to-v2-migration"
},
"v2": {
"status": "current",
"release_date": "2023-01-15"
},
"v3": {
"status": "beta",
"release_date": "2023-11-01"
}
}
@app.get("/api/versions")
async def get_api_versions():
"""获取所有API版本信息"""
return API_VERSIONS
@app.get("/api/versions/{version}")
async def get_api_version_info(version: str):
"""获取特定API版本信息"""
if version not in API_VERSIONS:
raise HTTPException(status_code=404, detail="API version not found")
version_info = API_VERSIONS[version]
# 检查版本是否已过期
if "end_of_life_date" in version_info:
eol_date = datetime.strptime(version_info["end_of_life_date"], "%Y-%m-%d")
if datetime.now() > eol_date:
raise HTTPException(
status_code=410,
detail=f"API version {version} has reached end of life"
)
return version_info
九、实际项目案例:博客API系统
9.1 完整项目结构
# app/main.py
from fastapi import FastAPI
from app.api import posts, users, auth
from app.db import engine, Base
# 创建数据库表
Base.metadata.create_all(bind=engine)
app = FastAPI(title="博客API", version="1.0.0")
# 注册路由
app.include_router(auth.router, prefix="/auth", tags=["认证"])
app.include_router(users.router, prefix="/users", tags=["用户"])
app.include_router(posts.router, prefix="/posts", tags=["文章"])
@app.get("/")
async def root():
return {"message": "欢迎使用博客API系统"}
9.2 文章管理API
# app/api/endpoints/posts.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from app.db import get_db
from app.schemas.post import PostCreate, PostResponse
from app.services.post_service import PostService
# 假设get_current_user依赖已在auth模块中定义
# 这里提供一个简单的实现示例,实际项目中应该使用JWT验证
from app.api.auth import get_current_user
# get_current_user依赖的简单实现说明:
# 在auth模块中,get_current_user函数通常是这样实现的:
#
# async def get_current_user(token: str = Depends(oauth2_scheme)):
# """
# 获取当前用户的依赖函数
#
# 通过验证JWT token来识别和返回当前用户
# """
# try:
# # 解码JWT token
# payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# username: str = payload.get("sub")
# if username is None:
# raise HTTPException(status_code=401, detail="无效的认证凭据")
# except JWTError:
# raise HTTPException(status_code=401, detail="无效的认证凭据")
#
# # 从数据库获取用户信息
# user = get_user_by_username(db, username=username)
# if user is None:
# raise HTTPException(status_code=401, detail="用户不存在")
#
# return user
router = APIRouter()
@router.post("/", response_model=PostResponse)
async def create_post(
post: PostCreate,
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
return PostService.create_post(db, post, current_user["id"])
@router.get("/", response_model=List[PostResponse])
async def read_posts(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
posts = PostService.get_posts(db, skip=skip, limit=limit)
return posts
@router.get("/{post_id}", response_model=PostResponse)
async def read_post(post_id: int, db: Session = Depends(get_db)):
post = PostService.get_post(db, post_id)
if post is None:
raise HTTPException(status_code=404, detail="文章未找到")
return post
十、总结和进阶学习
10.1 FastAPI核心优势总结
- 性能卓越:基于Starlette和Pydantic,性能接近Node.js和Go
- 开发效率高:自动API文档、类型提示、编辑器支持
- 学习曲线平缓:Pythonic语法,易于理解和上手
- 生态系统完善:与SQLAlchemy、Pydantic等优秀库完美集成
- 生产就绪:支持Docker、测试、监控等生产环境需求
10.2 进阶学习路径
下一步学习建议:
- 🔗 GraphQL集成:学习Strawberry或Ariadne与FastAPI结合
- 📊 监控和日志:集成Prometheus、Grafana进行应用监控
- 🔐 高级安全:学习OAuth2、OpenID Connect等认证协议
- 🚀 微服务架构:使用FastAPI构建微服务系统
- ☁️ 云原生部署:学习Kubernetes、云服务部署
10.3 社区资源和工具
官方资源:
推荐工具:
- 🛠️ FastAPI CLI:项目脚手架工具
- 📋 Pydantic:数据验证和设置管理
- 🗄️ SQLModel:SQLAlchemy和Pydantic的结合
- 🧪 Pytest:测试框架
- 🐳 Docker:容器化部署
【实践建议】:最好的学习方式是通过实际项目。建议从一个小型API项目开始,逐步添加复杂功能,在实践中掌握FastAPI的各项特性。