Python之FastAPI 开发框架(第三篇):高级特性与实战

在之前的两篇教程中,我们学习了 FastAPI 的基础知识和核心概念,包括路由、依赖项、Pydantic 模型、表单与文件上传、中间件等。现在,是时候深入探索 FastAPI 的高级特性,这些特性将帮助你构建更复杂、更安全、更易维护的应用程序。本篇教程将涵盖 WebSocket、数据库集成(SQLAlchemy)、安全认证(OAuth2 JWT)、应用结构优化、部署策略等内容,并通过实战示例展示如何将这些特性整合到一个完整的项目中。

1. WebSocket 支持

FastAPI 原生支持 WebSocket,可以轻松实现实时双向通信,例如聊天应用、实时通知、游戏等。

1.1 基础 WebSocket 路由

使用 @app.websocket 装饰器定义一个 WebSocket 端点。在路径操作函数中,你需要接收一个 WebSocket 对象,并手动处理连接、接收和发送消息。

python 复制代码
from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Message text was: {data}")
  • await websocket.accept() 接受连接。
  • websocket.receive_text() 接收文本消息,还有 receive_bytes()receive_json() 等。
  • websocket.send_text() 发送文本消息。

1.2 广播消息

实际应用中,你可能需要将消息广播给多个连接的客户端。可以维护一个连接列表。

python 复制代码
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"You wrote: {data}", websocket)
            await manager.broadcast(f"Client #{client_id} says: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left the chat")

WebSocket 与 HTTP 可以共存于同一个应用,非常灵活。

2. 数据库集成(以 SQLAlchemy 为例)

大多数 Web 应用需要持久化数据。FastAPI 不强制使用特定的数据库库,但与 SQLAlchemy(异步或同步)配合非常顺畅。推荐使用异步 SQLAlchemy(sqlalchemy.ext.asyncio)以获得更好的性能。

2.1 安装依赖

bash 复制代码
pip install sqlalchemy databases asyncpg   # asyncpg 是 PostgreSQL 异步驱动

如果使用同步 SQLAlchemy,可以省略 databases 和异步驱动。

2.2 定义模型和数据库连接

python 复制代码
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "postgresql://user:pass@localhost/dbname"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

class Item(Base):
    __tablename__ = "items"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String)

2.3 创建依赖项获取数据库会话

python 复制代码
from fastapi import Depends
from sqlalchemy.orm import Session

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

然后在路径操作中使用 Depends(get_db) 获取会话。

python 复制代码
@app.post("/items/")
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
    db_item = Item(name=item.name, description=item.description)
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

2.4 异步 SQLAlchemy

对于异步操作,使用 create_async_engineAsyncSession

python 复制代码
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

ASYNC_DB_URL = "postgresql+asyncpg://user:pass@localhost/dbname"
async_engine = create_async_engine(ASYNC_DB_URL)
AsyncSessionLocal = sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)

async def get_async_db():
    async with AsyncSessionLocal() as session:
        yield session

然后在异步路径操作中使用 await

python 复制代码
@app.post("/items/")
async def create_item(item: ItemCreate, db: AsyncSession = Depends(get_async_db)):
    db_item = Item(name=item.name, description=item.description)
    db.add(db_item)
    await db.commit()
    await db.refresh(db_item)
    return db_item

3. 安全性与认证(OAuth2 JWT)

FastAPI 提供了多种安全工具,包括 HTTP Basic、OAuth2 密码流(带 JWT 令牌)等。最常用的是 OAuth2 密码流 + JWT。

3.1 安装 PyJWT 和 Passlib

bash 复制代码
pip install python-jose[cryptography] passlib[bcrypt]
  • python-jose 用于生成和验证 JWT。
  • passlib 用于密码哈希(使用 Bcrypt)。

3.2 密码哈希和验证

python 复制代码
from passlib.context import CryptContext

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)

3.3 创建 JWT 令牌

python 复制代码
from datetime import datetime, timedelta
from jose import JWTError, jwt

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict, expires_delta: timedelta = None):
    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

3.4 OAuth2 密码流依赖项

使用 OAuth2PasswordBearer 获取令牌。

python 复制代码
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token = create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}

3.5 获取当前用户

python 复制代码
async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        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_db, username)
    if user is None:
        raise credentials_exception
    return user

然后在需要保护的路由中依赖 get_current_user

python 复制代码
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

4. 应用结构优化(APIRouter)

随着项目增长,将所有路由放在一个文件中会变得混乱。FastAPI 提供了 APIRouter 来模块化路由。

4.1 使用 APIRouter

routers/items.py 中:

python 复制代码
from fastapi import APIRouter, Depends

router = APIRouter(prefix="/items", tags=["items"])

@router.get("/")
async def read_items():
    return [{"name": "Foo"}]

@router.get("/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

4.2 包含路由

在主 app 中:

python 复制代码
from fastapi import FastAPI
from routers import items, users

app = FastAPI()
app.include_router(items.router)
app.include_router(users.router)

你还可以为每个路由器设置前缀、标签、依赖项等,提高代码组织性。

5. 部署策略

FastAPI 应用可以部署在各种环境中,常见的方式是使用 Uvicorn 配合 Gunicorn(作为进程管理器),或者使用 Docker。

5.1 使用 Gunicorn + Uvicorn 工作进程

安装 gunicornuvicorn

bash 复制代码
pip install gunicorn uvicorn

启动命令:

bash 复制代码
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
  • -w 4:启动 4 个工作进程。
  • -k uvicorn.workers.UvicornWorker:指定使用 Uvicorn 工作进程。

5.2 Docker 部署

编写 Dockerfile

dockerfile 复制代码
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

构建并运行:

bash 复制代码
docker build -t myapp .
docker run -p 80:80 myapp

在生产环境中,通常还会使用 Nginx 作为反向代理,以及使用环境变量管理配置。

6. 实战:整合所有特性的小项目

下面我们通过一个简单的"实时聊天 + 用户认证"项目来演示如何将上述高级特性整合在一起。

6.1 项目结构

复制代码
chat_app/
├── main.py
├── routers/
│   ├── auth.py
│   ├── chat.py
│   └── items.py
├── models.py
├── schemas.py
├── database.py
├── dependencies.py
├── security.py
└── requirements.txt

6.2 主要文件内容

  • database.py:创建数据库引擎和会话。
  • models.py:定义 SQLAlchemy 模型。
  • schemas.py:定义 Pydantic 模型(用于请求/响应)。
  • security.py:密码哈希、JWT 处理。
  • dependencies.py:公共依赖项(如 get_db, get_current_user)。
  • routers/auth.py:注册、登录、令牌获取。
  • routers/chat.py:WebSocket 聊天室。
  • routers/items.py:示例 CRUD 操作(受保护)。
  • main.py:创建应用,包含所有路由器。

6.3 代码片段示例

security.py
python 复制代码
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def verify_password(plain, hashed):
    return pwd_context.verify(plain, hashed)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
dependencies.py
python 复制代码
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from . import models, database, security

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")

def get_db():
    db = database.SessionLocal()
    try:
        yield db
    finally:
        db.close()

async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, security.SECRET_KEY, algorithms=[security.ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = db.query(models.User).filter(models.User.username == username).first()
    if user is None:
        raise credentials_exception
    return user
routers/auth.py
python 复制代码
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from .. import schemas, models, dependencies, security

router = APIRouter(prefix="/auth", tags=["authentication"])

@router.post("/register", response_model=schemas.User)
def register(user: schemas.UserCreate, db: Session = Depends(dependencies.get_db)):
    db_user = db.query(models.User).filter(models.User.username == user.username).first()
    if db_user:
        raise HTTPException(status_code=400, detail="Username already registered")
    hashed = security.get_password_hash(user.password)
    new_user = models.User(username=user.username, hashed_password=hashed)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

@router.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(dependencies.get_db)):
    user = db.query(models.User).filter(models.User.username == form_data.username).first()
    if not user or not security.verify_password(form_data.password, user.hashed_password):
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token = security.create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}
routers/chat.py
python 复制代码
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from ..dependencies import get_current_user  # 注意:WebSocket 不能轻易使用依赖项,需手动验证
from ..security import decode_token

router = APIRouter(prefix="/chat", tags=["chat"])

class ConnectionManager:
    def __init__(self):
        self.active_connections = {}

    async def connect(self, websocket: WebSocket, user_id: int):
        await websocket.accept()
        self.active_connections[user_id] = websocket

    def disconnect(self, user_id: int):
        self.active_connections.pop(user_id, None)

    async def send_personal(self, message: str, user_id: int):
        if user_id in self.active_connections:
            await self.active_connections[user_id].send_text(message)

    async def broadcast(self, message: str, exclude_user: int = None):
        for uid, conn in self.active_connections.items():
            if uid != exclude_user:
                await conn.send_text(message)

manager = ConnectionManager()

@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    # 手动从查询参数获取 token 并验证
    token = websocket.query_params.get("token")
    if not token:
        await websocket.close(code=1008)
        return
    try:
        payload = decode_token(token)  # 假设 decode_token 会抛出异常如果无效
        user_id = payload.get("sub")
    except:
        await websocket.close(code=1008)
        return

    await manager.connect(websocket, user_id)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"User {user_id}: {data}", exclude_user=user_id)
    except WebSocketDisconnect:
        manager.disconnect(user_id)
        await manager.broadcast(f"User {user_id} left the chat")
main.py
python 复制代码
from fastapi import FastAPI
from .routers import auth, chat, items

app = FastAPI()

app.include_router(auth.router)
app.include_router(chat.router)
app.include_router(items.router)

@app.get("/")
def root():
    return {"message": "Chat App API"}

6.4 运行与测试

  1. 启动数据库(如 PostgreSQL)。
  2. 运行 uvicorn chat_app.main:app --reload
  3. 访问 /docs 查看自动生成的 API 文档。
  4. 通过 /auth/register 创建用户。
  5. 通过 /auth/token 获取令牌。
  6. 使用 WebSocket 客户端(如浏览器 JavaScript 或 Postman)连接到 /chat/ws?token=<your_token>,并测试聊天功能。

7. 性能优化建议

  • 使用异步数据库驱动 :如 asyncpg 配合 SQLAlchemy 异步。
  • 缓存:使用 Redis 缓存频繁访问的数据。
  • Gzip 压缩 :启用 GZipMiddleware 减少响应大小。
  • 连接池:合理配置数据库连接池大小。
  • 限制请求体大小 :使用 max_request_size 等参数防止恶意大请求。
  • 后台任务:将耗时操作放入后台任务。

8. 总结

通过本教程,我们深入探索了 FastAPI 的高级特性:

  • WebSocket 实现实时通信。
  • 数据库集成(SQLAlchemy)进行持久化。
  • 安全认证(OAuth2 JWT)保护端点。
  • 应用结构优化(APIRouter)使代码可维护。
  • 部署策略(Gunicorn、Docker)将应用投入生产。
  • 实战项目整合了所有特性,展示了如何构建一个功能完整的聊天应用。

FastAPI 的强大之处不仅在于其性能,还在于其清晰的设计和丰富的功能。掌握这些高级特性后,你已经具备了构建复杂、安全、可扩展的现代 Web 应用的能力。希望这三篇教程能成为你探索 FastAPI 世界的坚实基础。Happy coding!

相关推荐
天远Date Lab1 小时前
Python实战:基于天远二手车估值API构建企业车队资产数字化管理方案
大数据·人工智能·python
BestOrNothing_20151 小时前
Ubuntu 22.04 下使用 VS Code 搭建 ROS 2 Humble 集成开发环境
c++·vscode·python·ros2·ubuntu22.04
2301_776508721 小时前
分布式系统监控工具
开发语言·c++·算法
Irissgwe2 小时前
Linux进程信号
linux·服务器·开发语言·c++·linux进程信号
暮冬-  Gentle°2 小时前
C++与区块链智能合约
开发语言·c++·算法
JobDocLS2 小时前
Bash调试方法
开发语言·bash
Oueii2 小时前
C++中的代理模式实现
开发语言·c++·算法
艾莉丝努力练剑2 小时前
【Linux:文件 + 进程】理解IPC通信
linux·运维·服务器·开发语言·网络·c++·ide
洋不写bug2 小时前
Java线程(二):线程特点、状态、终止开始控制(
java·开发语言