FastAPI 框架 - 部署与实战

FastAPI 框架 - 部署与实战

6.1.21 部署与生产环境

简述:FastAPI 应用通常通过 Uvicorn 或 Gunicorn 部署在生产环境,支持多工作进程和 HTTPS。本节介绍常见的部署方案,包括 Docker 容器化、Nginx 反向代理和 HTTPS 配置。

使用 Uvicorn 运行

使用 Uvicorn 作为 ASGI 服务器运行 FastAPI 应用,支持热重载、多工作进程和 UNIX socket 等配置。

bash 复制代码
# 开发环境
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# 生产环境(多工作进程)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

# 使用 UNIX socket
uvicorn main:app --bind /tmp/uvicorn.sock

使用 Gunicorn

使用 Gunicorn 作为进程管理器配合 UvicornWorker,实现多进程部署以提高并发处理能力。

bash 复制代码
pip install gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Gunicorn 配置文件

python 复制代码
# gunicorn.conf.py
import multiprocessing

bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
keepalive = 120
timeout = 120

使用 Docker 部署

编写 Dockerfile 和 docker-compose.yml,实现 FastAPI 应用的容器化部署。

dockerfile 复制代码
# Dockerfile
FROM python:3.11-slim

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"]
dockerfile 复制代码
# 多阶段构建(更小镜像)
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local:$PATH
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
yaml 复制代码
# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=mysql+pymysql://root:password@db:3306/myapp
    depends_on:
      - db
      - redis
    restart: always
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: myapp
    volumes:
      - mysql_data:/var/lib/mysql
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  mysql_data:

Nginx 反向代理配置

配置 Nginx 作为反向代理转发请求到 Uvicorn,包含 WebSocket 支持和 HTTPS 配置。

nginx 复制代码
# /etc/nginx/sites-available/fastapi
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # SSE 支持(禁用缓冲)
        proxy_buffering off;
        proxy_cache off;
    }
}

# HTTPS 配置(使用 Let's Encrypt)
server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

环境变量配置

使用 pydantic-settings 的 BaseSettings 从 .env 文件加载配置,实现不同环境的参数隔离和安全管理。

python 复制代码
# config.py
from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    app_name: str = "FastAPI App"
    debug: bool = False
    database_url: str = "mysql+pymysql://root:password@localhost:3306/mydb"
    redis_url: str = "redis://localhost:6379/0"
    secret_key: str = "change-me-in-production"

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8"
    }

@lru_cache()
def get_settings():
    return Settings()
bash 复制代码
# .env
APP_NAME=My Production App
DEBUG=False
DATABASE_URL=mysql+pymysql://root:password@db:3306/myapp
SECRET_KEY=your-super-secret-key

HTTPS 配置

使用 SSL 上下文为 Uvicorn 启用 HTTPS(生产环境建议通过 Nginx 终止 SSL)。

python 复制代码
import ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain("cert.pem", "key.pem")

uvicorn.run("main:app", host="0.0.0.0", port=8443, ssl=context)

6.1.22 测试

简述:FastAPI 可以使用 pytest 和 TestClient 进行同步测试,或使用 AsyncClient 进行异步测试。良好的测试覆盖是保证 API 质量和稳定性的关键,本节介绍单元测试、集成测试和异步测试的完整方案。

安装测试依赖

bash 复制代码
pip install pytest pytest-asyncio httpx

同步测试(TestClient)

使用 FastAPI 的 TestClient(基于 httpx)编写同步单元测试,验证接口状态码和响应数据。

python 复制代码
# tests/test_main.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root():
    response = client.get("/")
    assert response.status_code == 200
    assert "message" in response.json()

def test_create_user():
    response = client.post(
        "/api/v1/users/",
        json={"username": "testuser", "email": "test@example.com", "password": "test123456"}
    )
    assert response.status_code in [200, 201]

def test_get_user_not_found():
    response = client.get("/api/v1/users/9999")
    assert response.status_code == 404

异步测试(AsyncClient)

使用 httpx 的 AsyncClient 和 pytest-asyncio 编写异步测试,适用于测试异步路由和异步数据库操作。

python 复制代码
# tests/test_async.py
import pytest
from httpx import AsyncClient, ASGITransport
from main import app

@pytest.fixture
async def async_client():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client

@pytest.mark.asyncio
async def test_async_root(async_client):
    response = await async_client.get("/")
    assert response.status_code == 200

@pytest.mark.asyncio
async def test_async_create_user(async_client):
    response = await async_client.post(
        "/api/v1/users/",
        json={"username": "asynctest", "email": "async@example.com", "password": "test123456"}
    )
    assert response.status_code in [200, 201]

测试数据库隔离

使用 SQLite 内存数据库替代生产数据库,通过依赖覆盖和 fixture 实现每个测试用例的数据库隔离与自动清理。

python 复制代码
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from fastapi.testclient import TestClient
from database.base import Base
from database.connection import get_db
from main import app

SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"

engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def override_get_db():
    try:
        db = TestingSessionLocal()
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db

@pytest.fixture(autouse=True)
def setup_database():
    Base.metadata.create_all(bind=engine)
    yield
    Base.metadata.drop_all(bind=engine)

@pytest.fixture
def client():
    return TestClient(app)

@pytest.fixture
def db_session():
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()

认证接口测试

测试登录成功/失败、未认证访问受保护接口、携带 Token 访问等认证场景。

python 复制代码
# tests/test_auth.py
from fastapi.testclient import TestClient

def test_login_success(client: TestClient):
    response = client.post(
        "/api/v1/auth/login",
        json={"username": "admin", "password": "admin123"}
    )
    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data
    assert data["token_type"] == "bearer"

def test_login_wrong_password(client: TestClient):
    response = client.post(
        "/api/v1/auth/login",
        json={"username": "admin", "password": "wrongpassword"}
    )
    assert response.status_code == 401

def test_protected_route_without_token(client: TestClient):
    response = client.get("/api/v1/users/me")
    assert response.status_code == 401

def test_protected_route_with_token(client: TestClient):
    login_resp = client.post(
        "/api/v1/auth/login",
        json={"username": "admin", "password": "admin123"}
    )
    token = login_resp.json()["access_token"]
    response = client.get(
        "/api/v1/users/me",
        headers={"Authorization": f"Bearer {token}"}
    )
    assert response.status_code == 200

pytest 配置

ini 复制代码
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
asyncio_mode = auto

6.1.23 日志与监控

简述:日志是生产环境排查问题的重要手段,监控则帮助实时掌握应用运行状态。本节介绍 FastAPI 中结构化日志配置、请求日志中间件和 Prometheus 指标监控。

结构化日志配置

使用自定义 JSONFormatter 输出结构化 JSON 日志,支持控制台输出和按大小/时间轮转的文件日志。

python 复制代码
# core/logging_config.py
import logging
import sys
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
import json
from datetime import datetime, timezone

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_data = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno
        }
        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_data, ensure_ascii=False)

def setup_logging(log_level: str = "INFO", log_file: str = None):
    root_logger = logging.getLogger()
    root_logger.setLevel(getattr(logging, log_level.upper()))

    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(JSONFormatter())
    root_logger.addHandler(console_handler)

    if log_file:
        file_handler = RotatingFileHandler(
            log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
        )
        file_handler.setFormatter(JSONFormatter())
        root_logger.addHandler(file_handler)

    logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
    logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)

请求日志中间件

通过 BaseHTTPMiddleware 记录每个请求的方法、路径、状态码、耗时和请求 ID,便于问题排查和性能分析。

python 复制代码
# core/middleware.py
import time
import logging
import uuid
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

logger = logging.getLogger("app.request")

class RequestLoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        start_time = time.time()
        request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))

        response = await call_next(request)

        duration = time.time() - start_time
        logger.info(
            f"{request.method} {request.url.path} "
            f"{response.status_code} "
            f"{duration:.3f}s "
            f"request_id={request_id}"
        )

        response.headers["X-Request-ID"] = request_id
        response.headers["X-Process-Time"] = f"{duration:.3f}"
        return response

Prometheus 指标监控

集成 prometheus-fastapi-instrumentator,自动暴露请求计数、延迟分布等 Prometheus 指标,配合 Grafana 实现可视化监控。

bash 复制代码
pip install prometheus-fastapi-instrumentator
python 复制代码
# main.py
from prometheus_fastapi_instrumentator import Instrumentator

app = FastAPI()

Instrumentator(
    should_group_status_codes=True,
    should_ignore_untemplated=True,
    excluded_handlers=["/health", "/metrics"]
).instrument(app).expose(app, endpoint="/metrics", include_in_schema=False)

Prometheus 配置

yaml 复制代码
# prometheus.yml
scrape_configs:
  - job_name: 'fastapi'
    metrics_path: '/metrics'
    static_configs:
      - targets: ['localhost:8000']

6.1.24 健康检查与优雅关闭

简述:健康检查接口用于监控系统和负载均衡判断服务状态;优雅关闭确保应用在停止时完成正在处理的请求、释放数据库连接等资源,避免数据丢失。

健康检查接口

提供简单健康检查和详细健康检查(检测数据库、Redis 连接状态),供负载均衡和监控系统调用。

python 复制代码
# routers/health.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from sqlalchemy import text
from database.session import get_db
from datetime import datetime, timezone
from database.redis_connection import redis_client

router = APIRouter(tags=["健康检查"])

@router.get("/health")
async def health_check():
    return {"status": "healthy", "timestamp": datetime.now(timezone.utc).isoformat()}

@router.get("/health/detail")
async def detailed_health_check(db: Session = Depends(get_db)):
    checks = {}

    try:
        db.execute(text("SELECT 1"))
        checks["database"] = {"status": "healthy"}
    except Exception as e:
        checks["database"] = {"status": "unhealthy", "error": str(e)}

    try:
        redis_client.ping()
        checks["redis"] = {"status": "healthy"}
    except Exception as e:
        checks["redis"] = {"status": "unhealthy", "error": str(e)}

    all_healthy = all(c["status"] == "healthy" for c in checks.values())
    return {
        "status": "healthy" if all_healthy else "degraded",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "checks": checks
    }

Lifespan 事件(替代 on_event)

使用 asynccontextmanager 实现 lifespan 生命周期管理,在启动时建表和初始化数据,关闭时释放数据库连接池。

python 复制代码
# core/lifespan.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from database.connection import engine, Base
from database.async_connection import async_engine
import logging

logger = logging.getLogger("app")

@asynccontextmanager
async def lifespan(app: FastAPI):
    logger.info("应用启动中...")

    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    logger.info("应用启动完成")
    yield
    logger.info("应用关闭中...")

    await async_engine.dispose()
    engine.dispose()

    logger.info("应用已关闭")
python 复制代码
# main.py
from fastapi import FastAPI
from core.lifespan import lifespan

app = FastAPI(lifespan=lifespan)

提示@app.on_event("startup")@app.on_event("shutdown") 在 FastAPI 新版本中已弃用,推荐使用 lifespan 上下文管理器。lifespan 在 yield 之前的代码在启动时执行,yield 之后的代码在关闭时执行,代码更紧凑且支持异步资源管理。

优雅关闭与信号处理

捕获 SIGTERM/SIGINT 信号,实现优雅关闭:停止接收新请求、等待进行中的请求完成、释放资源。

python 复制代码
import signal
import asyncio
import uvicorn

shutdown_event = asyncio.Event()

def signal_handler(sig, frame):
    shutdown_event.set()

signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)

async def main():
    config = uvicorn.Config("main:app", host="0.0.0.0", port=8000)
    server = uvicorn.Server(config)

    async def watch_shutdown():
        await shutdown_event.wait()
        server.should_exit = True

    await asyncio.gather(server.serve(), watch_shutdown())

if __name__ == "__main__":
    asyncio.run(main())

6.1.25 Celery 异步任务队列

简述:Celery 是 Python 最流行的分布式任务队列,配合 Redis/RabbitMQ 作为消息代理,支持任务持久化、自动重试、定时调度和分布式执行。适用于耗时操作(邮件发送、数据处理、报表生成)和定时任务场景。

概念:Celery 核心组件

组件 说明
Worker 执行任务的进程,可部署多个实现分布式
Broker 消息代理(Redis/RabbitMQ),负责任务分发
Backend 结果存储(Redis/数据库),保存任务执行结果
Task 任务函数,由 Worker 执行
Beat 定时调度器,按计划触发任务

安装与配置

bash 复制代码
pip install celery[redis] redis
python 复制代码
# core/celery_app.py
from celery import Celery
from config import get_settings

settings = get_settings()

celery_app = Celery(
    "fastapi_worker",
    broker=settings.redis_url,
    backend=settings.redis_url,
    include=["core.tasks"]
)

celery_app.conf.update(
    task_serializer="json",
    accept_content=["json"],
    result_serializer="json",
    timezone="Asia/Shanghai",
    enable_utc=True,
    task_track_started=True,
    task_time_limit=300,
    task_soft_time_limit=270,
    worker_prefetch_multiplier=1,
    worker_max_tasks_per_child=1000,
)

定义任务

使用 @celery_app.task 装饰器定义异步任务,支持重试、超时和结果追踪。

python 复制代码
# core/tasks.py
from core.celery_app import celery_app
import time
import logging

logger = logging.getLogger("app.tasks")

@celery_app.task(bind=True, max_retries=3, default_retry_delay=60)
def send_email_task(self, email: str, subject: str, content: str):
    try:
        time.sleep(2)
        logger.info(f"邮件已发送: {email}, 主题: {subject}")
        return {"status": "success", "email": email}
    except Exception as exc:
        logger.error(f"邮件发送失败: {exc}")
        self.retry(exc=exc)

@celery_app.task(bind=True)
def generate_report_task(self, report_type: str, params: dict):
    total_steps = 10
    for i in range(total_steps):
        time.sleep(1)
        self.update_state(
            state="PROGRESS",
            meta={"current": i + 1, "total": total_steps}
        )
    return {"status": "completed", "report_type": report_type}

@celery_app.task
def cleanup_expired_sessions():
    logger.info("清理过期会话...")
    return {"status": "completed", "cleaned": 42}

在 FastAPI 中调用 Celery 任务

在路由中调用 Celery 任务,返回任务 ID,提供查询任务状态的接口。

python 复制代码
# routers/tasks.py
from fastapi import APIRouter, HTTPException
from core.tasks import send_email_task, generate_report_task
from celery.result import AsyncResult
from core.celery_app import celery_app

router = APIRouter(prefix="/tasks", tags=["异步任务"])

@router.post("/send-email")
async def trigger_email(email: str, subject: str, content: str):
    task = send_email_task.delay(email, subject, content)
    return {"task_id": task.id, "status": "submitted"}

@router.post("/generate-report")
async def trigger_report(report_type: str):
    task = generate_report_task.delay(report_type, {"format": "pdf"})
    return {"task_id": task.id, "status": "submitted"}

@router.get("/status/{task_id}")
async def get_task_status(task_id: str):
    result = AsyncResult(task_id, app=celery_app)
    response = {
        "task_id": task_id,
        "status": result.status,
    }
    if result.ready():
        if result.successful():
            response["result"] = result.result
        else:
            response["error"] = str(result.result)
    elif result.status == "PROGRESS":
        response["progress"] = result.info
    return response

定时任务(Celery Beat)

使用 Celery Beat 配置定时调度,支持 crontab 和 interval 两种调度方式。

python 复制代码
# core/celery_app.py - 追加定时任务配置
from celery.schedules import crontab

celery_app.conf.beat_schedule = {
    "cleanup-every-hour": {
        "task": "core.tasks.cleanup_expired_sessions",
        "schedule": crontab(minute=0),
    },
    "daily-report": {
        "task": "core.tasks.generate_report_task",
        "schedule": crontab(hour=8, minute=0),
        "args": ("daily_summary",),
    },
    "health-check-every-30s": {
        "task": "core.tasks.health_check",
        "schedule": 30.0,
    },
}

启动 Celery Worker

bash 复制代码
# 启动 Worker
celery -A core.celery_app worker --loglevel=info --concurrency=4

# 启动 Beat(定时调度)
celery -A core.celery_app beat --loglevel=info

# 同时启动 Worker 和 Beat
celery -A core.celery_app worker --beat --loglevel=info

# 使用 Docker 启动
# docker-compose.yml 中添加:
# celery-worker:
#   build: .
#   command: celery -A core.celery_app worker --loglevel=info
# celery-beat:
#   build: .
#   command: celery -A core.celery_app beat --loglevel=info

Celery vs Background Tasks 对比

对比项 Background Tasks Celery
依赖 FastAPI 内置 需安装 Celery + Redis/RabbitMQ
持久化 无(进程重启丢失) 有(任务持久化到队列)
重试 不支持自动重试 支持自动重试
分布式 单进程 多 worker 分布式执行
监控 Flower 等监控工具
定时任务 不支持 支持
适用场景 简单、轻量任务 复杂、可靠性要求高的任务

6.1.26 OpenAPI 文档自定义

简述:FastAPI 自动生成的 Swagger UI 和 ReDoc 文档非常实用,但生产环境通常需要定制文档标题、描述、分组、安全方案等。本节介绍如何深度定制 OpenAPI 文档。

基础文档配置

配置 FastAPI 应用的标题、描述、版本号、联系方式等元信息。

python 复制代码
from fastapi import FastAPI

app = FastAPI(
    title="My API",
    description="这是一个示例 API 文档",
    version="1.0.0",
    terms_of_service="https://example.com/terms/",
    contact={
        "name": "API Support",
        "url": "https://example.com/support",
        "email": "support@example.com",
    },
    license_info={
        "name": "MIT License",
        "url": "https://opensource.org/licenses/MIT",
    },
)

自定义文档 URL 和禁用

修改 Swagger UI 和 ReDoc 的访问路径,或在生产环境禁用文档。

python 复制代码
app = FastAPI(
    docs_url="/documentation",     # 默认 /docs → 改为 /documentation
    redoc_url="/api-docs",         # 默认 /redoc → 改为 /api-docs
    openapi_url="/openapi.json",   # 默认 /openapi.json
)

# 生产环境禁用文档
app = FastAPI(
    docs_url=None,
    redoc_url=None,
    openapi_url=None,
)

自定义 OpenAPI Schema

通过 custom_openapi() 函数深度定制 OpenAPI Schema,添加安全方案、服务器列表、标签分组等。

python 复制代码
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI()

def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title="My Custom API",
        version="2.0.0",
        description="自定义 OpenAPI 文档",
        routes=app.routes,
    )

    # 添加安全方案
    openapi_schema["components"]["securitySchemes"] = {
        "BearerAuth": {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT",
        },
        "ApiKeyAuth": {
            "type": "apiKey",
            "in": "header",
            "name": "X-API-Key",
        },
    }

    # 全局安全要求
    openapi_schema["security"] = [{"BearerAuth": []}]

    # 添加服务器列表
    openapi_schema["servers"] = [
        {"url": "https://api.example.com", "description": "生产环境"},
        {"url": "https://staging.example.com", "description": "预发布环境"},
        {"url": "http://localhost:8000", "description": "开发环境"},
    ]

    # 标签分组
    openapi_schema["tags"] = [
        {"name": "认证", "description": "用户认证与授权"},
        {"name": "用户管理", "description": "用户 CRUD 操作"},
        {"name": "订单管理", "description": "订单相关操作"},
    ]

    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

接口标签与分组

使用 tags 参数对接口进行分组,在 Swagger UI 中按标签折叠展示。

python 复制代码
@app.get("/users/", tags=["用户管理"])
async def list_users():
    return []

@app.post("/users/", tags=["用户管理"])
async def create_user():
    return {}

@app.post("/auth/login", tags=["认证"])
async def login():
    return {}

@app.get("/orders/", tags=["订单管理"])
async def list_orders():
    return []

隐藏接口

使用 include_in_schema=False 将接口从文档中隐藏,但仍可正常访问。

python 复制代码
@app.get("/internal/metrics", include_in_schema=False)
async def internal_metrics():
    return {"cpu": 45, "memory": 72}

@app.post("/webhook/stripe", include_in_schema=False)
async def stripe_webhook():
    return {"status": "ok"}

自定义 Swagger UI 样式

通过覆盖静态资源或注入自定义 CSS/JS 修改 Swagger UI 的外观。

python 复制代码
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html

app = FastAPI(docs_url=None, redoc_url=None)

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - API 文档",
        swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
        swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
    )

@app.get("/redoc", include_in_schema=False)
async def redoc_html():
    return get_redoc_html(
        openapi_url=app.openapi_url,
        title=app.title + " - ReDoc 文档",
        redoc_js_url="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
    )

6.1.27 最佳实践与项目模板

简述:本节总结 FastAPI 项目开发中的最佳实践,涵盖项目结构、配置管理、安全防护、性能优化和代码规范等方面,并提供可直接使用的项目模板。

项目结构规范

推荐的 FastAPI 项目目录结构,按职责分层,便于团队协作和代码维护。

复制代码
myproject/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── config.py
│   ├── database/
│   │   ├── __init__.py
│   │   ├── connection.py
│   │   ├── async_connection.py
│   │   ├── base.py
│   │   └── session.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── order.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── pagination.py
│   ├── crud/
│   │   ├── __init__.py
│   │   └── user.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── security.py
│   │   ├── rbac.py
│   │   ├── dependencies.py
│   │   ├── exceptions.py
│   │   ├── transaction.py
│   │   ├── lifespan.py
│   │   ├── logging_config.py
│   │   ├── middleware.py
│   │   ├── celery_app.py
│   │   └── tasks.py
│   └── routers/
│       ├── __init__.py
│       ├── auth.py
│       ├── users.py
│       ├── orders.py
│       ├── health.py
│       └── tasks.py
├── tests/
│   ├── conftest.py
│   ├── test_auth.py
│   └── test_users.py
├── alembic/
│   ├── versions/
│   └── env.py
├── alembic.ini
├── .env
├── .env.example
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── gunicorn.conf.py
└── pytest.ini

配置管理

使用 pydantic-settings 集中管理所有配置项,通过 .env 文件实现环境隔离。

python 复制代码
# config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
from typing import Optional

class Settings(BaseSettings):
    app_name: str = "FastAPI App"
    debug: bool = False
    api_prefix: str = "/api/v1"

    database_url: str = "mysql+pymysql://root:password@localhost:3306/mydb"
    database_pool_size: int = 10
    database_max_overflow: int = 20
    database_pool_recycle: int = 1800

    redis_url: str = "redis://localhost:6379/0"

    secret_key: str = "change-me-in-production"
    algorithm: str = "HS256"
    access_token_expire_minutes: int = 1440

    log_level: str = "INFO"
    log_file: Optional[str] = None

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
        "case_sensitive": False
    }

@lru_cache()
def get_settings() -> Settings:
    return Settings()

统一响应格式

封装统一的 API 响应格式,包含 code、message、data 字段,便于前端统一处理。

python 复制代码
# schemas/response.py
from typing import TypeVar, Generic, Optional
from pydantic import BaseModel

T = TypeVar("T")

class ResponseBase(BaseModel, Generic[T]):
    code: int = 200
    message: str = "success"
    data: Optional[T] = None

class ResponseList(BaseModel, Generic[T]):
    code: int = 200
    message: str = "success"
    data: list[T] = []
    total: int = 0
    page: int = 1
    page_size: int = 10

def success(data=None, message="success"):
    return ResponseBase(code=200, message=message, data=data)

def error(code=400, message="error"):
    return ResponseBase(code=code, message=message, data=None)

安全最佳实践

生产环境必须遵循的安全规范清单。

安全项 说明 实现方式
HTTPS 所有通信加密 Nginx + Let's Encrypt
密码哈希 永不存储明文密码 bcrypt / argon2
JWT 安全 密钥足够长、设置过期时间 HS256 + 短过期
CORS 限制允许的域名 CORSMiddleware 白名单
SQL 注入 使用 ORM 参数化查询 SQLAlchemy ORM
XSS 输入验证 + 输出转义 Pydantic 验证
CSRF Token 验证 SameSite Cookie
限流 防止暴力破解 slowapi / Redis
敏感信息 不在日志/响应中暴露 过滤字段 + 日志脱敏
环境变量 密钥不硬编码 .env + pydantic-settings

性能优化清单

优化项 说明 实现方式
异步 I/O 使用 async/await async def + AsyncSession
连接池 复用数据库连接 SQLAlchemy pool_size
缓存 减少重复查询 Redis 缓存
分页 避免一次加载全部数据 PaginationParams
懒加载 只查询需要的字段 select 指定列
索引 加速查询 数据库索引优化
Gzip 压缩响应体 GZipMiddleware
多进程 充分利用 CPU Gunicorn + UvicornWorker

完整 main.py 模板

一个生产级 FastAPI 应用的 main.py 模板,集成了所有最佳实践。

python 复制代码
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from contextlib import asynccontextmanager

from config import get_settings
from core.lifespan import lifespan
from core.logging_config import setup_logging
from core.middleware import RequestLoggingMiddleware
from routers import auth, users, orders, health, tasks

settings = get_settings()
setup_logging(log_level=settings.log_level, log_file=settings.log_file)

app = FastAPI(
    title=settings.app_name,
    version="1.0.0",
    lifespan=lifespan,
    docs_url="/docs" if settings.debug else None,
    redoc_url="/redoc" if settings.debug else None,
)

# 中间件(顺序重要:最后添加的最先执行)
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://your-domain.com"] if not settings.debug else ["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 路由注册
app.include_router(health.router)
app.include_router(auth.router, prefix=settings.api_prefix)
app.include_router(users.router, prefix=settings.api_prefix)
app.include_router(orders.router, prefix=settings.api_prefix)
app.include_router(tasks.router, prefix=settings.api_prefix)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=settings.debug)
相关推荐
llilay2 小时前
企业级FastAPI后端模板搭建(二)整合路由Router
开发语言·python·fastapi
我叫张小白。2 小时前
Redis的缓存雪崩、击穿、穿透和解决方案
数据结构·redis·fastapi·缓存穿透·缓存击穿·雪崩·热点key问题
还是鼠鼠1 天前
AI掘金头条新闻系统 (Toutiao News)-获取用户信息
后端·python·mysql·fastapi·web
我叫张小白。1 天前
基于Redis与FastAPI的分布式共享会话体系
数据库·redis·分布式·缓存·中间件·fastapi·依赖注入
我叫张小白。2 天前
FastAPI 介绍和入门核心知识点
fastapi
智研数智工坊2 天前
FastAPI+uv+Jinja2+Nuitka 通用Web桌面框架搭建教程|从零搭建可打包迭代的Python开发底座
python·fastapi·uv·nuitka·jinja2·桌面应用开发
alwaysrun2 天前
python之异步高性能Web框架 FastAPI
python·fastapi·web·路由·pydantic
dinl_vin3 天前
FastAPI 系列 ·(十二):生产部署——Docker + 配置管理(系列完结)
docker·容器·fastapi
L_cl3 天前
大模型应用开发 9.FastAPI ① 请求与响应
python·fastapi