FastAPI 系列 · 第 12 篇:生产部署------Docker + 配置管理(系列完结)
🎯 适合人群 :已完成 shop-api 功能开发,希望将 FastAPI 应用部署到生产环境的工程师
⏱️ 阅读时间 :约 35 分钟
💬 一句话定位 :从
pydantic-settings多环境配置管理到多阶段 Dockerfile 构建,从 docker-compose 全栈编排到 Nginx 反向代理,系列最终篇完整还原一个 FastAPI 应用的生产部署全流程。
一、配置管理:pydantic-settings
Spring Boot 用 application.yml + @ConfigurationProperties 管理配置,FastAPI 对应方案是 pydantic-settings + .env 文件。
bash
pip install pydantic-settings python-dotenv
1.1 BaseSettings 配置类
python
# app/config.py
from functools import lru_cache
from typing import Literal
from pydantic import field_validator, AnyHttpUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""
全局配置类。
对标 Spring Boot @ConfigurationProperties + application.yml。
优先级(高到低):
1. 环境变量
2. .env 文件
3. 字段默认值
"""
model_config = SettingsConfigDict(
env_file=".env", # 默认读取 .env 文件
env_file_encoding="utf-8",
case_sensitive=False, # 环境变量大小写不敏感
extra="ignore", # 忽略 .env 中多余的键
)
# ---- 应用基础 ----
APP_NAME: str = "shop-api"
APP_VERSION: str = "1.0.0"
DEBUG: bool = False
ENVIRONMENT: Literal["development", "test", "production"] = "development"
# ---- 安全 ----
SECRET_KEY: str # 无默认值,必须由环境变量提供
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
ALLOWED_ORIGINS: list[str] = ["http://localhost:3000"]
# ---- 数据库 ----
DATABASE_URL: str # 必填
DB_POOL_SIZE: int = 10
DB_MAX_OVERFLOW: int = 20
DB_ECHO: bool = False # 生产环境务必关闭 SQL 日志
# ---- Redis ----
REDIS_URL: str = "redis://localhost:6379/0"
REDIS_MAX_CONNECTIONS: int = 20
# ---- Celery ----
CELERY_BROKER_URL: str = "redis://localhost:6379/1"
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/2"
# ---- ClickHouse ----
CLICKHOUSE_HOST: str = "localhost"
CLICKHOUSE_PORT: int = 9000
CLICKHOUSE_DATABASE: str = "shop_analytics"
CLICKHOUSE_USER: str = "default"
CLICKHOUSE_PASSWORD: str = ""
# ---- 邮件 ----
SMTP_HOST: str = "smtp.example.com"
SMTP_PORT: int = 465
SMTP_USER: str = ""
SMTP_PASSWORD: str = ""
@field_validator("SECRET_KEY")
@classmethod
def validate_secret_key(cls, v: str) -> str:
if len(v) < 32:
raise ValueError("SECRET_KEY 长度至少 32 位,生产环境请使用随机生成值")
return v
@field_validator("ALLOWED_ORIGINS", mode="before")
@classmethod
def parse_origins(cls, v):
"""支持逗号分隔的字符串:ALLOWED_ORIGINS=http://a.com,http://b.com"""
if isinstance(v, str):
return [origin.strip() for origin in v.split(",")]
return v
@property
def is_production(self) -> bool:
return self.ENVIRONMENT == "production"
@property
def is_development(self) -> bool:
return self.ENVIRONMENT == "development"
@lru_cache
def get_settings() -> Settings:
"""
单例模式获取配置,使用 lru_cache 缓存(对标 Spring 单例 Bean)。
测试时可用 get_settings.cache_clear() 清除缓存以注入测试配置。
"""
return Settings()
settings = get_settings()
1.2 三套环境配置文件
bash
# .env.development(本地开发)
APP_NAME=shop-api
ENVIRONMENT=development
DEBUG=true
SECRET_KEY=dev-only-secret-key-32-chars-minimum!!
DATABASE_URL=mysql+aiomysql://root:password@localhost:3306/shop_dev
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/1
CLICKHOUSE_HOST=localhost
DB_ECHO=true
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
bash
# .env.test(测试环境 / CI)
ENVIRONMENT=test
DEBUG=false
SECRET_KEY=test-secret-key-for-ci-pipeline-only!!
DATABASE_URL=mysql+aiomysql://root:testpass@localhost:3306/shop_test
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/1
CLICKHOUSE_HOST=localhost
DB_ECHO=false
bash
# .env.production(生产环境,此文件不进代码库!)
ENVIRONMENT=production
DEBUG=false
SECRET_KEY=<openssl rand -hex 32 生成的随机值>
DATABASE_URL=mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/2
CLICKHOUSE_HOST=clickhouse
CLICKHOUSE_PASSWORD=${CH_PASSWORD}
DB_POOL_SIZE=20
DB_MAX_OVERFLOW=40
ALLOWED_ORIGINS=https://shop.example.com
⚠️ 重要 :
.env.production和任何包含真实密码的文件绝对不能提交到代码库 。将它们加入.gitignore。生产密钥通过 CI/CD 系统(GitHub Secrets、Vault、K8s Secret)注入。
二、多阶段 Dockerfile
Spring Boot 打包成一个 Fat JAR,FastAPI 的对应方案是多阶段 Docker 构建:第一阶段安装依赖,第二阶段只复制必要文件,显著减小镜像体积。
dockerfile
# Dockerfile
# -------------------------------------------------------
# Stage 1: Builder --- 安装依赖(镜像会很大,但不会进入最终镜像)
# -------------------------------------------------------
FROM python:3.11-slim AS builder
WORKDIR /build
# 安装编译依赖(aiomysql、cryptography 等需要编译的包)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libssl-dev \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
# 先复制依赖文件,利用 Docker layer 缓存
# 只有 requirements.txt 变化时才重新安装
COPY requirements.txt .
# 安装到 /install 目录,与系统 Python 隔离
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# -------------------------------------------------------
# Stage 2: Runtime --- 最终镜像,只包含运行时必要内容
# -------------------------------------------------------
FROM python:3.11-slim AS runtime
WORKDIR /app
# 安全:创建非 root 用户运行应用(对标 Spring Boot 不以 root 运行)
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 从 builder 阶段复制已安装的依赖
COPY --from=builder /install /usr/local
# 复制应用代码
COPY app/ ./app/
COPY alembic/ ./alembic/
COPY alembic.ini .
# 设置 Python 环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app
# 切换到非 root 用户
USER appuser
# 健康检查(定期调用 /health 接口)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"
EXPOSE 8000
# 使用 Gunicorn + UvicornWorker 运行(生产推荐)
CMD ["gunicorn", "app.main:app", \
"--workers", "4", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--bind", "0.0.0.0:8000", \
"--access-logfile", "-", \
"--error-logfile", "-", \
"--log-level", "info", \
"--proxy-headers", \
"--forwarded-allow-ips", "*"]
2.1 镜像体积对比
| 方式 | 镜像大小 |
|---|---|
| 单阶段构建(含编译工具) | ~850MB |
| 多阶段构建(runtime only) | ~280MB |
加 .dockerignore 优化 |
~260MB |
text
# .dockerignore
.git
.gitignore
.env*
__pycache__
*.pyc
*.pyo
.pytest_cache
tests/
docs/
*.md
.venv
htmlcov/
三、Gunicorn + UvicornWorker
FastAPI 基于 ASGI,不能直接用 Gunicorn 的默认 WSGI Worker,需要 UvicornWorker。
bash
pip install gunicorn uvicorn[standard]
3.1 Worker 数量计算
bash
# 经典公式:2 * CPU 核数 + 1
# 4 核服务器推荐 9 个 worker
# 注意:worker 过多反而会因内存竞争降低性能
# 生产启动命令
gunicorn app.main:app \
--workers $((2 * $(nproc) + 1)) \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \ # 请求超时(秒)
--keepalive 5 \ # Keep-Alive 连接时长
--max-requests 1000 \ # 每个 worker 处理 N 个请求后自动重启(防内存泄漏)
--max-requests-jitter 100 \ # 随机抖动,避免所有 worker 同时重启
--access-logfile - \ # 输出到 stdout
--error-logfile - \
--log-level info \
--proxy-headers # 信任 Nginx 的 X-Forwarded-For
💡 提示 :容器环境推荐单进程运行(
--workers 1),通过横向扩容增加实例数,而非单容器多进程。这样更符合 12-Factor App 原则,也方便 Kubernetes 管理。
四、docker-compose.yml 全栈编排
yaml
# docker-compose.yml
version: "3.9"
services:
# -------------------------------------------------------
# FastAPI 应用(对标 Spring Boot 打包后的 JAR 部署)
# -------------------------------------------------------
app:
build:
context: .
dockerfile: Dockerfile
target: runtime
image: shop-api:latest
container_name: shop-api-app
restart: unless-stopped
env_file:
- .env.production
environment:
DATABASE_URL: mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
ports:
- "8000:8000"
depends_on:
mysql:
condition: service_healthy # 等待 MySQL 健康检查通过再启动
redis:
condition: service_healthy
networks:
- backend
volumes:
- app-logs:/app/logs
healthcheck:
test: ["CMD", "python", "-c",
"import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# -------------------------------------------------------
# MySQL 8.0
# -------------------------------------------------------
mysql:
image: mysql:8.0
container_name: shop-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: shop_prod
MYSQL_USER: shop_user
MYSQL_PASSWORD: ${DB_PASSWORD}
TZ: Asia/Shanghai # 时区(避免坑 1:容器内时区问题)
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./scripts/mysql-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root",
"-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# -------------------------------------------------------
# Redis 7
# -------------------------------------------------------
redis:
image: redis:7-alpine
container_name: shop-redis
restart: unless-stopped
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save 900 1
--save 300 10
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
# -------------------------------------------------------
# Celery Worker(对标 Spring @Async 的独立工作进程)
# -------------------------------------------------------
celery-worker:
build:
context: .
dockerfile: Dockerfile
target: runtime
image: shop-api:latest
container_name: shop-celery-worker
restart: unless-stopped
command: >
celery -A app.celery_app worker
--loglevel=info
--concurrency=4
--queues=default,high_priority,email
--hostname=worker@%h
env_file:
- .env.production
environment:
DATABASE_URL: mysql+aiomysql://shop_user:${DB_PASSWORD}@mysql:3306/shop_prod
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
volumes:
- app-logs:/app/logs
# -------------------------------------------------------
# Celery Beat(定时任务调度器,只能运行一个实例!)
# -------------------------------------------------------
celery-beat:
build:
context: .
dockerfile: Dockerfile
target: runtime
image: shop-api:latest
container_name: shop-celery-beat
restart: unless-stopped
command: >
celery -A app.celery_app beat
--loglevel=info
--scheduler django_celery_beat.schedulers:DatabaseScheduler
env_file:
- .env.production
depends_on:
- redis
- celery-worker
networks:
- backend
# -------------------------------------------------------
# Nginx 反向代理(对标 Spring Cloud Gateway / Nginx 前置)
# -------------------------------------------------------
nginx:
image: nginx:1.25-alpine
container_name: shop-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/nginx/certs:ro # SSL 证书
- nginx-logs:/var/log/nginx
depends_on:
app:
condition: service_healthy
networks:
- backend
- frontend
networks:
backend:
driver: bridge
frontend:
driver: bridge
volumes:
mysql-data:
redis-data:
app-logs:
nginx-logs:
五、Nginx 配置
nginx
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式(含请求耗时,方便性能分析)
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 20m;
# Gzip 压缩(API 响应通常是 JSON,压缩效果显著)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
}
nginx
# nginx/conf.d/shop-api.conf
upstream shop_api {
server app:8000; # 对应 docker-compose 中的 app 服务
keepalive 32; # 复用连接,减少握手开销
}
# HTTP → HTTPS 跳转
server {
listen 80;
server_name shop.example.com;
return 301 https://$host$request_uri;
}
# HTTPS 主服务
server {
listen 443 ssl http2;
server_name shop.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# 安全 Header
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# API 代理
location /api/ {
proxy_pass http://shop_api;
proxy_http_version 1.1;
# 传递真实客户端信息(FastAPI 通过 --proxy-headers 读取)
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 支持(如有 WS 接口)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时配置
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲配置
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# FastAPI Swagger UI(仅开发/测试环境暴露,生产可关闭)
location /docs {
proxy_pass http://shop_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /openapi.json {
proxy_pass http://shop_api;
}
# 健康检查端点(不记录访问日志,避免日志污染)
location /health {
proxy_pass http://shop_api;
access_log off;
}
}
六、健康检查端点
python
# app/routers/health.py
import asyncio
from fastapi import APIRouter, status
from sqlalchemy import text
from app.database import AsyncSessionLocal
from app.redis.client import get_redis
from app.config import settings
router = APIRouter(tags=["infrastructure"])
@router.get(
"/health",
status_code=status.HTTP_200_OK,
summary="服务健康检查",
)
async def health_check():
"""
检查应用自身、数据库、Redis 的连通性。
Docker HEALTHCHECK + Nginx upstream health check 都调用此接口。
对标 Spring Boot Actuator /health 端点。
"""
checks = {}
overall_status = "ok"
# 检查 MySQL
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
checks["mysql"] = "ok"
except Exception as e:
checks["mysql"] = f"error: {str(e)[:100]}"
overall_status = "degraded"
# 检查 Redis
try:
redis = await get_redis()
await redis.ping()
checks["redis"] = "ok"
except Exception as e:
checks["redis"] = f"error: {str(e)[:100]}"
overall_status = "degraded"
response_body = {
"status": overall_status,
"version": settings.APP_VERSION,
"environment": settings.ENVIRONMENT,
"checks": checks,
}
# 如果核心依赖不可用,返回 503(触发 Docker 重启策略)
if overall_status != "ok":
from fastapi import HTTPException
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=response_body,
)
return response_body
七、数据库迁移(容器启动时自动执行)
python
# scripts/start.sh(容器启动脚本,替换 CMD 中的直接调用)
#!/bin/bash
set -e
echo "==> 等待数据库就绪..."
python -c "
import asyncio
import sys
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
from app.config import settings
async def wait_for_db():
engine = create_async_engine(settings.DATABASE_URL)
for i in range(30):
try:
async with engine.connect() as conn:
await conn.execute(text('SELECT 1'))
print('数据库连接成功')
await engine.dispose()
return
except Exception as e:
print(f'等待数据库... ({i+1}/30): {e}')
await asyncio.sleep(2)
print('数据库连接超时,退出')
sys.exit(1)
asyncio.run(wait_for_db())
"
echo "==> 执行数据库迁移..."
alembic upgrade head
echo "==> 启动应用..."
exec gunicorn app.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--access-logfile - \
--error-logfile - \
--log-level info \
--proxy-headers \
--forwarded-allow-ips "*"
dockerfile
# Dockerfile 末尾修改 CMD
COPY scripts/start.sh /app/start.sh
RUN chmod +x /app/start.sh
CMD ["/app/start.sh"]
八、常见坑与最佳实践
坑 1:容器内时区错误
yaml
# ❌ 不设置时区,容器默认 UTC,日志时间和业务时间对不上
services:
app:
image: shop-api
# ✅ 统一设置时区(所有服务都要设置)
services:
app:
image: shop-api
environment:
TZ: Asia/Shanghai
mysql:
environment:
TZ: Asia/Shanghai
redis:
environment:
TZ: Asia/Shanghai
dockerfile
# ✅ 也可以在 Dockerfile 中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
坑 2:SECRET_KEY 泄漏
python
# ❌ 硬编码 SECRET_KEY,一旦代码库泄漏,所有 Token 作废
SECRET_KEY = "mysecretkey"
# ❌ 使用默认值,忘记在生产环境覆盖
SECRET_KEY: str = "change-me-in-production"
# ✅ 无默认值,强制外部注入
SECRET_KEY: str # 无默认值,必须由环境变量提供
# ✅ 生产环境用强随机值
# openssl rand -hex 32
# 输出:a3f8c2d1e4b5a6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1
坑 3:--reload 进入生产
bash
# ❌ 开发时习惯加 --reload,生产环境误用
uvicorn app.main:app --reload --host 0.0.0.0
# --reload 的危害:
# 1. 监听文件系统变化,CPU 和内存消耗高
# 2. 代码文件被修改时自动重启,生产环境不稳定
# 3. 不支持多 worker
# ✅ 生产环境用 Gunicorn 管理 worker
gunicorn app.main:app --worker-class uvicorn.workers.UvicornWorker --workers 4
坑 4:数据库未就绪导致应用启动失败
yaml
# ❌ 只用 depends_on 服务名,不等健康检查
services:
app:
depends_on:
- mysql # 只等 MySQL 容器启动,不等 MySQL 进程就绪
# ✅ 使用 condition: service_healthy,等待健康检查通过
services:
app:
depends_on:
mysql:
condition: service_healthy
mysql:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
坑 5:镜像中包含 .env 文件
dockerfile
# ❌ 不写 .dockerignore,.env 文件被打包进镜像
COPY . . # 把整个目录复制,包含 .env!
# ✅ 在 .dockerignore 中排除所有 .env 文件
# .dockerignore
.env
.env.*
!.env.example # 只保留示例文件
# ✅ 运行时通过 env_file 或环境变量注入,不打包进镜像
九、部署操作手册
bash
# 一键启动全栈(首次部署)
cp .env.production.example .env.production # 填写真实密钥
docker compose up -d --build
# 查看服务状态
docker compose ps
docker compose logs -f app # 实时查看应用日志
# 执行数据库迁移(升级时)
docker compose exec app alembic upgrade head
# 滚动更新应用(不停服)
docker compose build app
docker compose up -d --no-deps app # 只重启 app 服务
# 扩容 Celery Worker
docker compose up -d --scale celery-worker=3
# 停止并清理(保留数据卷)
docker compose down
# 停止并清理(包含数据卷,谨慎!)
docker compose down -v
十、全系列回顾
经过 12 篇的系统学习,shop-api 从零搭建到生产部署,覆盖了 FastAPI 完整技术栈。
| 篇次 | 主题 | 核心技术 | Spring Boot 对应 |
|---|---|---|---|
| 01 | 初体验 | FastAPI + Uvicorn + Pydantic | Spring Boot + Tomcat + Bean Validation |
| 02 | 路由与请求模型 | APIRouter + Pydantic v2 | @Controller + @RequestBody |
| 03 | 依赖注入 | Depends + yield | @Autowired + @Transactional |
| 04 | 数据库集成 | SQLAlchemy 2.0 async + Alembic | Spring Data JPA + Flyway |
| 05 | 认证与鉴权 | JWT + OAuth2 + python-jose | Spring Security + JWT |
| 06 | 异步编程 | asyncio + httpx + asyncio.gather | Spring WebFlux + Reactor |
| 07 | Redis 缓存 | redis.asyncio + Cache Aside | Spring Data Redis + @Cacheable |
| 08 | 后台任务 | BackgroundTasks + Celery + Beat | @Async + Spring Batch |
| 09 | 中间件与错误处理 | BaseHTTPMiddleware + structlog + slowapi | Filter + @ControllerAdvice |
| 10 | 测试 | pytest + AsyncClient + dependency_overrides | JUnit5 + MockMvc + @MockBean |
| 11 | ClickHouse 集成 | asynch + MergeTree + 物化视图 | Spring JDBC + OLAP 数据库 |
| 12 | 生产部署 | pydantic-settings + Dockerfile + docker-compose | Spring Profiles + Docker |
贯穿项目 shop-api 最终目录结构
shop-api/
├── app/
│ ├── main.py # FastAPI 应用入口,lifespan 配置
│ ├── config.py # pydantic-settings 配置管理
│ ├── database.py # SQLAlchemy 异步引擎
│ ├── models/ # ORM 模型(User, Product, Order)
│ ├── schemas/ # Pydantic 请求/响应模型
│ ├── routers/ # 路由层(APIRouter)
│ │ ├── auth.py
│ │ ├── products.py
│ │ ├── orders.py
│ │ ├── analytics.py # ClickHouse 统计 API
│ │ └── health.py
│ ├── services/ # 业务逻辑层
│ ├── repositories/ # 数据访问层
│ ├── auth/ # JWT + OAuth2
│ │ ├── security.py
│ │ └── dependencies.py
│ ├── redis/ # Redis 连接池 + 缓存工具
│ ├── clickhouse/ # ClickHouse 连接 + 查询
│ ├── tasks/ # Celery 任务定义
│ └── celery_app.py # Celery 应用配置
├── alembic/ # 数据库迁移
├── tests/ # 测试套件
│ ├── conftest.py
│ ├── unit/
│ └── integration/
├── nginx/ # Nginx 配置
├── scripts/ # 启动脚本
├── Dockerfile
├── docker-compose.yml
├── .env.development
├── .env.test
├── .env.production.example # 模板,真实值不提交
├── requirements.txt
└── pyproject.toml
总结
| 知识点 | FastAPI 方案 | Spring Boot 对应 |
|---|---|---|
| 配置管理 | pydantic-settings + .env | @ConfigurationProperties + application.yml |
| 多环境配置 | .env.development / .env.test / .env.production | spring.profiles.active |
| 容器化 | 多阶段 Dockerfile | Jib / Spring Boot Maven Plugin |
| 多服务编排 | docker-compose | Docker Compose / Kubernetes |
| 反向代理 | Nginx | Spring Cloud Gateway / Nginx |
| 进程管理 | Gunicorn + UvicornWorker | Spring Boot Embedded Tomcat |
| 健康检查 | /health + Docker HEALTHCHECK | Spring Actuator /health |
| 密钥管理 | 环境变量 + .gitignore | Spring Cloud Vault / Kubernetes Secret |
🎯 金句:代码写得再好,部署做不好,也只是本地的艺术品。Docker + 配置管理,是代码从开发机走向生产的最后一公里。
参考资料
- pydantic-settings 官方文档
- FastAPI 部署最佳实践
- Docker 多阶段构建
- Gunicorn + UvicornWorker
- Nginx 官方文档
- docker-compose 服务健康检查
系列完结
🎉 FastAPI 系列到此全部完结!
12 篇文章,带你从"Spring Boot 工程师的第一次 FastAPI 体验"一路走到"生产环境 Docker 部署",完整覆盖了:
路由 → 依赖注入 → 数据库 → 认证 → 异步 → 缓存 → 任务队列 → 中间件 → 测试 → OLAP → 部署
贯穿项目
shop-api从骨架到上线,Spring Boot 到 FastAPI 的心智模型转换已在代码注释和对比表格中悄悄完成。如果这个系列对你有帮助,欢迎在评论区留下你的实践心得。下个系列见!