FastAPI 进阶篇:从“会写接口”到“生产级架构”的完美蜕变

FastAPI 进阶篇:从“会写接口”到“生产级架构”的完美蜕变

1. 引子:基础篇之后,还缺什么?

基础篇讲完了 FastAPI 的"骨架"——路由、参数、响应、异常处理、依赖注入。你学会了写 API,但离"能上线的 API"还有一段距离。

假设你写了一个博客系统,基础篇教你的东西能让你完成 CRUD,但现实中的问题是:

  • 接口没有认证,任何人都可以删文章——需要 OAuth2 + JWT
  • 每个接口都写一遍请求日志——需要 中间件
  • 用户注册完要等邮件发送完才能响应——需要 后台任务
  • 前端想要实时收到新评论通知——需要 WebSocket
  • 改完代码不敢上线——需要 测试

这篇博客把这些问题一次性解决。

在这里插入图片描述


2. 核心概念解析

高级篇涉及的概念比基础篇多,但彼此关联。先花两分钟建立整体认知:

中间件(Middleware):相当于请求的"海关检查通道"。每个请求进来都要过一遍海关,出去也要再过一遍。海关可以记录日志、检查证件、测量时间。对应到代码,就是 @app.middleware('http') 装饰器。

洋葱模型:中间件的执行顺序像一个洋葱——先注册的中间件在最外层。请求"剥开"洋葱进入核心(你的业务代码),响应再从核心"穿出"洋葱。第一个注册的中间件,它的前置逻辑最先执行,后置逻辑最后执行。

OAuth2 密码流:用户拿用户名+密码换 Token,然后每次请求都带上 Token。FastAPI 的 OAuth2PasswordBearer 自动从请求头中提取 Token,如果 Token 不存在或无效,自动返回 401。

JWT(JSON Web Token):Token 的一种格式,特点是"自包含"——服务器不需要存 Session,因为用户信息和过期时间都编码在 Token 本身里。服务器只需要验证签名(用 SECRET_KEY)就能确认 Token 的真伪。

BackgroundTasks:FastAPI 内置的轻量级异步任务工具。响应优先返回,任务在后台默默执行。适合发邮件、写日志这类操作。如果任务需要可靠投递或跨进程执行,应该换成 Celery。

WebSocket:HTTP 是"你问我答"——客户端请求,服务端响应。WebSocket 是"打电话"——建立连接后,双方随时可以说话。适合聊天、实时通知、协作编辑。

Lifespan:应用的"生命周期钩子"。应用启动时执行一次(连接数据库、加载模型),关闭时执行一次(释放资源)。用 @asynccontextmanager 实现。


3. 核心体系:四个生产级能力拆解

3.1 中间件机制

中间件的本质是一个函数,接收 request 和一个 call_next 参数。

import time
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware('http')
async def add_process_time(request: Request, call_next):
    """计算每个请求的处理时间,添加到响应头"""
    # 请求前:记录开始时间
    start_time = time.perf_counter()

    # 调用下一层(路径操作或其他中间件)
    response = await call_next(request)

    # 响应后:计算耗时,写入响应头
    process_time = time.perf_counter() - start_time
    response.headers['X-Process-Time'] = str(process_time)

    return response

call_next 是理解中间件机制的关键:

  • 调用 call_next(request) 才会继续执行,不调用则请求被拦截
  • call_next 必须 await,因为路径操作可能是异步的
  • call_next 返回 Response,你可以修改响应头或完全替换响应

洋葱模型的执行顺序

# 后注册的中间件,前置逻辑先执行
@app.middleware('http')
async def m1(request, call_next):
    print('m1 前置')            # 第2步
    response = await call_next(request)
    print('m1 后置')            # 第4步
    return response

@app.middleware('http')
async def m2(request, call_next):
    print('m2 前置')            # 第1步(最先执行)
    response = await call_next(request)
    print('m2 后置')            # 第5步(最后执行)
    return response

# 执行顺序:m2前置 → m1前置 → 路径操作 → m1后置 → m2后置

也就是说:后注册的中间件更靠近客户端,先注册的更靠近业务逻辑。

内置中间件通过 app.add_middleware() 注册:

from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.middleware.gzip import GZipMiddleware

# CORS 跨域(最常用的内置中间件)
app.add_middleware(
    CORSMiddleware,
    allow_origins=['http://localhost:3000', 'https://example.com'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

# 限制 Host 头,防止 Host 头注入攻击
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=['example.com', '*.example.com', 'localhost'],
)

# 响应压缩(大 JSON 响应效果明显)
app.add_middleware(GZipMiddleware, minimum_size=1000)

CORS 的一个常见坑allow_credentials=True 时,allow_origins 不能为 ["*"],必须明确指定源列表。这是浏览器安全规范,不是 FastAPI 的限制。

3.2 安全认证体系

安全认证是高级篇最重的部分,分三层:密码存储 → Token 签发 → 依赖封装。

第一层:密码加密

任何时候都不要明文存密码。bcrypt 是目前最常用的密码哈希库,它自动生成随机盐(salt),防止彩虹表攻击:

import bcrypt

class PwdContext:
    """密码加密和验证的工具类"""

    def hash(self, password: str) -> str:
        """加密密码"""
        # gensalt() 生成随机盐,轮数越高越安全(也越慢)
        salt = bcrypt.gensalt()
        hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
        return hashed.decode('utf-8')

    def verify(self, plain_password: str, hashed_password: str) -> bool:
        """验证密码是否匹配"""
        return bcrypt.checkpw(
            plain_password.encode('utf-8'),
            hashed_password.encode('utf-8')
        )

pwd_context = PwdContext()

第二层:OAuth2 密码流 + JWT

OAuth2PasswordBearer 的 tokenUrl 参数告诉客户端去哪换 Token。当客户端访问受保护路由却没有携带有效 Token 时,FastAPI 自动返回 401。

from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from datetime import datetime, timedelta

# ============ 配置 ============
SECRET_KEY = 'your-secret-key-keep-it-secret'  # 生产环境用环境变量
ALGORITHM = 'HS256'                             # 签名算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30                # Token 过期时间

# tokenUrl 指向登录接口的路径
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')

# ============ 签发 Token ============
def create_access_token(data: dict) -> str:
    """生成 JWT Token"""
    # 复制数据,避免修改原始 dict
    to_encode = data.copy()

    # 设置过期时间(exp 是 JWT 标准字段)
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({'exp': expire})

    # 用密钥签名,生成 JWT 字符串
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

# ============ 登录接口 ============
@app.post('/token')
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    """用户登录,返回 Token"""
    # 验证用户名密码(authenticate_user 需要自己实现)
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=401, detail='用户名或密码错误')

    # 签发 Token,sub 字段存储用户标识
    access_token = create_access_token({'sub': user.username})
    return {'access_token': access_token, 'token_type': 'bearer'}

第三层:封装为可复用的依赖

把 Token 解析逻辑封装成一个依赖函数,任何受保护路由都可以通过 Depends(get_current_user) 注入当前用户:

async def get_current_user(token: str = Depends(oauth2_scheme)):
    """从 Token 中解析当前登录用户"""
    try:
        # 解码 Token,验证签名和过期时间
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get('sub')
        if username is None:
            raise HTTPException(status_code=401, detail='无效 Token')
    except JWTError:
        # Token 过期、签名错误等情况
        raise HTTPException(status_code=401, detail='无效 Token')

    # 从数据库查询用户(get_user_by_username 需要自己实现)
    user = await get_user_by_username(username)
    if user is None:
        raise HTTPException(status_code=401, detail='用户不存在')
    return user

# 使用方式:任何需要登录的路由直接注入
@app.get('/users/me')
async def read_users_me(current_user: User = Depends(get_current_user)):
    return {'username': current_user.username, 'email': current_user.email}

这样设计的优势是:认证逻辑只写一次,所有受保护路由只需要加一行 Depends(get_current_user)

3.3 后台任务与实时通信

BackgroundTasks:轻量级异步任务

适用于"响应不需要等它完成"的操作。关键点是:BackgroundTasks.add_task() 会把任务添加到队列中,响应返回后再顺序执行这些任务。注意这里说的"后置"是指响应已经发送给客户端之后。

from fastapi import BackgroundTasks

def send_welcome_email(email: str, username: str):
    """模拟发送欢迎邮件(同步函数即可)"""
    print(f'发送欢迎邮件到 {email}: 你好 {username}!')
    # 实际项目中这里调用邮件服务 API

def write_audit_log(action: str, user_id: int):
    """写入审计日志"""
    print(f'[AUDIT] 用户 {user_id} 执行了: {action}')

@app.post('/users/register')
async def register(
    username: str,
    email: str,
    background_tasks: BackgroundTasks,
):
    """用户注册:响应优先返回,后台发邮件 + 写日志"""
    # 注册逻辑(创建用户等)
    user_id = 123  # 假设创建成功返回的 ID

    # 添加后台任务(可以添加多个)
    background_tasks.add_task(send_welcome_email, email, username)
    background_tasks.add_task(write_audit_log, '用户注册', user_id)

    # 响应立即返回,不用等邮件发送
    return {'message': '注册成功', 'user_id': user_id}

BackgroundTasks 的局限性:任务存储在内存中,进程崩溃后任务丢失。不适合需要可靠投递的场景(支付回调等),这类场景应使用 Celery + Redis/RabbitMQ。

WebSocket 实时通信

WebSocket 与 HTTP 共享同一个端口,FastAPI 可以同时处理两种协议。基本通信模式是:accept → 循环 receive/send → 处理断连。

from fastapi import WebSocket, WebSocketDisconnect

# 维护所有活跃连接的列表
active_connections: list[WebSocket] = []

@app.websocket('/ws/notifications')
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket 通知推送端点"""
    # 接受 WebSocket 连接
    await websocket.accept()
    # 加入连接池
    active_connections.append(websocket)
    print(f'新连接加入,当前连接数: {len(active_connections)}')

    try:
        # 循环接收消息(保持连接活跃)
        while True:
            data = await websocket.receive_text()
            print(f'收到客户端消息: {data}')

            # 广播给所有连接的客户端
            for conn in active_connections:
                await conn.send_text(f'服务端广播: {data}')

    except WebSocketDisconnect:
        # 客户端断开时,从连接池移除
        active_connections.remove(websocket)
        print(f'连接断开,当前连接数: {len(active_connections)}')

WebSocket 实战要点

  1. 必须在 try/except 中捕获 WebSocketDisconnect,否则断连后下次广播会报错
  2. 连接池建议用 set 代替 list(避免重复添加,移除效率更高)
  3. 实际项目中需要做心跳检测(ping/pong),清理僵尸连接

3.4 生命周期与工程化

Lifespan 事件

@asynccontextmanager 装饰一个异步生成器函数,yield 之前的代码在应用启动时执行,之后的在关闭时执行:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    """应用生命周期管理"""
    # === 启动时执行 ===
    print('应用启动中...')
    await init_database()     # 连接数据库
    await load_ml_model()     # 加载机器学习模型
    print('应用启动完成')

    yield  # 应用运行期间

    # === 关闭时执行 ===
    print('应用关闭中...')
    await close_database()    # 关闭数据库连接
    print('应用已关闭')

app = FastAPI(lifespan=lifespan)

通用响应封装

让所有接口返回统一的 JSON 格式,前端处理起来更方便:

from typing import TypeVar, Generic
from pydantic import BaseModel

# 泛型类型变量,让返回的数据类型可以动态指定
T = TypeVar('T')

class Result(BaseModel, Generic[T]):
    """统一响应格式"""
    code: int = 200
    message: str = 'success'
    data: T | None = None

    @classmethod
    def success(cls, data: T | None = None):
        """成功响应"""
        return cls(code=200, message='success', data=data)

    @classmethod
    def error(cls, code: int, message: str):
        """失败响应"""
        return cls(code=code, message=message, data=None)

# 使用示例
@app.get('/users/{user_id}', response_model=Result[UserOut])
async def get_user(user_id: int):
    user = await find_user(user_id)
    if not user:
        return Result.error(404, '用户不存在')
    return Result.success(user)

测试编写

TestClient 基于 httpx,同步调用 FastAPI 应用,无需启动服务器:

from fastapi.testclient import TestClient
from app.main import app

# 创建测试客户端,全程不需要启动 Uvicorn
client = TestClient(app)

def test_create_book():
    """测试创建图书接口"""
    response = client.post('/books/', json={
        'title': 'Python 从入门到实践',
        'author': 'Eric Matthes',
        'price': 89.0,
    })
    # 验证状态码
    assert response.status_code == 201
    # 验证返回数据
    data = response.json()
    assert data['title'] == 'Python 从入门到实践'
    assert 'id' in data

def test_get_nonexistent_book():
    """测试获取不存在的图书"""
    response = client.get('/books/99999')
    assert response.status_code == 404
    assert response.json()['detail'] == '书籍不存在'

依赖覆盖是 FastAPI 测试的最大优势——用 Mock 数据库替换真实数据库,不污染数据:

# 生产环境的依赖
async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

# 测试环境的 Mock 依赖
async def override_get_db():
    """测试环境使用内存数据库"""
    async with MemoryAsyncSession() as session:
        yield session

# 替换依赖
app.dependency_overrides[get_db] = override_get_db

# 现在 TestClient 的所有请求都使用内存数据库
def test_create_book():
    response = client.post('/books/', json={'title': '测试图书', 'price': 99.0})
    assert response.status_code == 201

4. 实战代码演示:带认证的实时通知系统

下面用一个完整的"用户通知系统"串联所有高级篇知识点。这个系统做的事情很简单:

  1. 用户注册 / 登录(JWT 认证)
  2. 用户创建一条通知
  3. 系统通过 WebSocket 实时推送给所有在线用户
  4. 后台任务记录审计日志
  5. 中间件统计请求耗时
  6. 测试覆盖核心接口

4.1 完整代码

"""
FastAPI 高级特性实战:用户通知系统
演示:中间件 + JWT 认证 + WebSocket + 后台任务 + 测试
"""
import time
import bcrypt
from datetime import datetime, timedelta
from typing import Annotated

from fastapi import (
    FastAPI, Depends, HTTPException, WebSocket,
    WebSocketDisconnect, BackgroundTasks, Request, status,
)
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.responses import JSONResponse
from jose import JWTError, jwt
from pydantic import BaseModel

# ============ 配置 ============
SECRET_KEY = 'dev-secret-key-change-in-production'
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# ============ 数据模型 ============
class UserCreate(BaseModel):
    username: str
    password: str
    email: str

class UserOut(BaseModel):
    """响应模型:不暴露密码"""
    username: str
    email: str

class Notification(BaseModel):
    title: str
    content: str

# ============ 密码工具 ============
class PwdContext:
    def hash(self, password: str) -> str:
        salt = bcrypt.gensalt()
        return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')

    def verify(self, plain: str, hashed: str) -> bool:
        return bcrypt.checkpw(plain.encode('utf-8'), hashed.encode('utf-8'))

pwd_context = PwdContext()

# ============ 模拟数据库 ============
fake_users_db: dict[str, dict] = {}
fake_notifications_db: list[dict] = []
next_user_id = 1

# ============ 应用初始化 ============
app = FastAPI()

# OAuth2 方案:tokenUrl 指向登录接口
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/token')

# ============ ③ 中间件:请求耗时统计 ============
@app.middleware('http')
async def add_process_time(request: Request, call_next):
    """每个请求都会经过这里,记录处理时间"""
    start = time.perf_counter()
    response = await call_next(request)
    elapsed = time.perf_counter() - start
    response.headers['X-Process-Time'] = f'{elapsed:.4f}s'
    return response

# ============ 认证工具函数 ============
def create_access_token(data: dict) -> str:
    """签发 JWT Token"""
    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)

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    """从 Token 解析当前用户(可复用的依赖)"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get('sub')
        if username is None or username not in fake_users_db:
            raise HTTPException(status_code=401, detail='无效 Token')
    except JWTError:
        raise HTTPException(status_code=401, detail='Token 已过期或无效')
    return fake_users_db[username]

# ============ ② WebSocket 连接管理 ============
active_ws_connections: set[WebSocket] = set()

@app.websocket('/ws/notifications')
async def websocket_notifications(websocket: WebSocket):
    """WebSocket 端点:连接后实时接收通知推送"""
    await websocket.accept()
    active_ws_connections.add(websocket)
    try:
        # 保持连接,等待客户端断开
        while True:
            await websocket.receive_text()
    except WebSocketDisconnect:
        active_ws_connections.discard(websocket)

async def broadcast_notification(title: str, content: str):
    """向所有 WebSocket 客户端广播通知"""
    message = f'新通知: {title} - {content}'
    # 遍历连接池,断连的会自动捕获 WebSocketDisconnect
    for conn in list(active_ws_connections):
        try:
            await conn.send_text(message)
        except WebSocketDisconnect:
            active_ws_connections.discard(conn)

# ============ ① 后台任务函数 ============
def write_audit_log(action: str, username: str):
    """后台任务:写审计日志(响应返回后执行)"""
    print(f'[AUDIT] {datetime.now()}: {username} 执行了 {action}')

# ============ API 路由 ============
@app.post('/register', response_model=UserOut)
async def register(user_in: UserCreate, background_tasks: BackgroundTasks):
    """用户注册(带后台任务)"""
    global next_user_id
    if user_in.username in fake_users_db:
        raise HTTPException(status_code=400, detail='用户名已存在')

    # 创建用户,密码加密存储
    fake_users_db[user_in.username] = {
        'id': next_user_id,
        'username': user_in.username,
        'email': user_in.email,
        'hashed_password': pwd_context.hash(user_in.password),
    }
    next_user_id += 1

    # 添加后台任务:响应返回后写审计日志
    background_tasks.add_task(write_audit_log, '用户注册', user_in.username)

    return {'username': user_in.username, 'email': user_in.email}

@app.post('/token')
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    """用户登录:验证身份 → 签发 Token"""
    user = fake_users_db.get(form_data.username)
    if not user or not pwd_context.verify(form_data.password, user['hashed_password']):
        raise HTTPException(status_code=401, detail='用户名或密码错误')

    token = create_access_token({'sub': form_data.username})
    return {'access_token': token, 'token_type': 'bearer'}

@app.post('/notifications')
async def create_notification(
    notif: Notification,
    background_tasks: BackgroundTasks,
    current_user: Annotated[dict, Depends(get_current_user)],
):
    """创建通知(需要登录)→ 写入数据库 → WebSocket 广播"""
    # 保存通知
    notification_record = {
        'id': len(fake_notifications_db) + 1,
        'title': notif.title,
        'content': notif.content,
        'created_by': current_user['username'],
        'created_at': datetime.now().isoformat(),
    }
    fake_notifications_db.append(notification_record)

    # 后台任务:审计日志
    background_tasks.add_task(
        write_audit_log, '创建通知', current_user['username']
    )

    # WebSocket 广播(异步执行,不阻塞响应)
    await broadcast_notification(notif.title, notif.content)

    return notification_record

@app.get('/notifications')
async def list_notifications(
    current_user: Annotated[dict, Depends(get_current_user)],
):
    """获取通知列表(需要登录)"""
    return fake_notifications_db

# ============ 测试代码 ============
from fastapi.testclient import TestClient

client = TestClient(app)

def test_full_flow():
    """测试完整流程:注册 → 登录 → 创建通知 → 获取通知"""
    # 1. 注册
    resp = client.post('/register', json={
        'username': 'testuser',
        'password': 'pass123',
        'email': 'test@example.com',
    })
    assert resp.status_code == 200
    assert resp.json()['username'] == 'testuser'
    # 验证响应模型中不包含 password
    assert 'password' not in resp.json()

    # 2. 登录获取 Token
    resp = client.post('/token', data={
        'username': 'testuser',
        'password': 'pass123',
    })
    assert resp.status_code == 200
    token = resp.json()['access_token']
    assert token is not None

    # 3. 用 Token 创建通知(放在 Authorization header 中)
    resp = client.post(
        '/notifications',
        json={'title': '欢迎', 'content': '欢迎使用通知系统'},
        headers={'Authorization': f'Bearer {token}'},
    )
    assert resp.status_code == 200
    assert resp.json()['title'] == '欢迎'

    # 4. 获取通知列表
    resp = client.get(
        '/notifications',
        headers={'Authorization': f'Bearer {token}'},
    )
    assert resp.status_code == 200
    assert len(resp.json()) == 1

    # 5. 验证未携带 Token 的请求被拒绝
    resp = client.get('/notifications')
    assert resp.status_code == 401

    # 6. 验证响应头中有处理时间(中间件注入的)
    assert 'X-Process-Time' in resp.headers

4.2 运行结果说明

启动服务后:

# 注册用户
curl -X POST http://localhost:8000/register \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "secret123", "email": "alice@test.com"}'
# 返回: {"username": "alice", "email": "alice@test.com"}

# 登录获取 Token
curl -X POST http://localhost:8000/token \
  -d "username=alice&password=secret123"
# 返回: {"access_token": "eyJ...", "token_type": "bearer"}

# 用 Token 创建通知
curl -X POST http://localhost:8000/notifications \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"title": "系统通知", "content": "欢迎回来"}'

# 未携带 Token 的请求
curl http://localhost:8000/notifications
# 返回: 401 Unauthorized

运行测试:

# 直接运行测试函数
python -c "test_full_flow()"
# 看到 [AUDIT] 日志输出在后台任务中执行

5. 避坑指南 / 最佳实践

5.1 JWT 的常见安全陷阱

# ❌ 错误:密钥硬编码在代码中
SECRET_KEY = 'hardcoded-secret'

# ✓ 正确:从环境变量读取
import os
SECRET_KEY = os.getenv('JWT_SECRET_KEY')
if not SECRET_KEY:
    raise RuntimeError('JWT_SECRET_KEY 环境变量未设置')

另外注意:JWT 的 payload 是 Base64 编码而非加密的,任何拿到 Token 的人都可以解码查看 payload 中的内容。不要在 payload 中存放敏感信息(密码、身份证号等)。

5.2 中间件注册顺序

CORS 中间件应该注册在最外层(最靠近客户端),这样跨域检查在所有其他逻辑之前执行:

# ✓ 正确顺序:CORS 最先注册
app.add_middleware(CORSMiddleware, allow_origins=['*'], ...)

@app.middleware('http')
async def logging_middleware(request, call_next):
    ...

@app.middleware('http')
async def auth_middleware(request, call_next):
    ...

如果 CORS 中间件在内层,跨域请求的 OPTIONS 预检请求可能会被内层中间件拦截,导致跨域失败。

5.3 BackgroundTasks 的适用边界

场景用 BackgroundTasks用 Celery
发邮件✓ 可以✓ 也可以
写日志✓ 推荐杀鸡用牛刀
清理临时文件✓ 可以没必要
支付回调通知❌ 进程崩溃会丢失✓ 必须
定时任务(每天凌晨跑报表)❌ 不支持✓ 必须
CPU 密集型计算(图像处理)❌ 阻塞事件循环✓ 独立 Worker

一句话原则:BackgroundTasks 适合"丢了也不心疼"的轻量任务。重要任务交给 Celery。

5.4 WebSocket 连接管理

# ✓ 正确:使用 set 去重,discard 安全移除
active_connections: set[WebSocket] = set()
active_connections.add(websocket)
active_connections.discard(websocket)  # 不存在也不会报错

# ❌ 错误:广播时遍历的同时修改集合
for conn in active_connections:
    if broken(conn):
        active_connections.remove(conn)  # RuntimeError: Set changed size during iteration

# ✓ 正确:遍历副本
for conn in list(active_connections):
    if broken(conn):
        active_connections.discard(conn)

5.5 测试的依赖覆盖

app.dependency_overrides 是一个字典,调用 TestClient 前设置好覆盖。不同测试之间要清理状态:

def test_something():
    # 设置覆盖
    app.dependency_overrides[get_db] = override_get_db

    # 执行测试
    response = client.get('/items/')

    # 清理覆盖(防止影响其他测试)
    app.dependency_overrides.clear()

    # 断言
    assert response.status_code == 200

也可以用 pytest.fixture 实现更优雅的生命周期管理。

5.6 官方文档


6. 总结

下表总结了"生产环境刚需"与"FastAPI 对应方案"的映射关系:

生产需求FastAPI 方案一句话要点
请求日志 / 耗时统计@app.middleware('http')不调用 call_next 则请求被拦截
跨域访问CORSMiddlewareallow_credentials=True 时 origins 不能为 ["*"]
密码安全存储bcrypt自动加盐,永远不要存明文
用户认证OAuth2PasswordBearer + JWTToken 自包含,服务端无状态
轻量后台任务BackgroundTasks响应返回后执行,丢失不心疼
实时消息推送WebSocket维护连接池,处理断连
应用启动/关闭Lifespanyield 前启动,yield 后关闭
自动化测试TestClient + 依赖覆盖不需要启动服务器
统一响应格式Result[T] 泛型封装前端不用猜响应格式

基础篇教你写 API,高级篇教你写"能上线的 API"。两者结合,再加上后面会讲的数据库操作,一个完整的 FastAPI 后端服务的拼图就齐了。


下篇预告:FastAPI 数据库篇——SQLAlchemy 2.0 异步 ORM 实战。讲清楚 Engine、Session、模型定义、CRUD 操作、事务管理和 Alembic 迁移,以及如何在 FastAPI 中优雅地管理数据库连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值