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)