初始化 FastAPI 演示项目
- 实现分层架构:API层、服务层、模式层、模型层 - 应用工厂模式和路由模块化设计 - 完整的配置管理系统,支持多环境配置 - 类似 Spring Boot 的配置文件体系 - 提供便捷启动脚本和详细文档 主要功能: - 用户和产品管理 API - 多环境配置支持 (.env.dev/.env.test/.env.prod) - 配置优先级管理 - 热重载和生产环境支持 - CORS、日志、限流等中间件配置 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
227bade97c
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(curl:*)",
|
||||||
|
"Bash(git init:*)",
|
||||||
|
"Bash(git remote:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
# 应用基础配置
|
||||||
|
PROJECT_NAME=FastAPI Demo
|
||||||
|
VERSION=1.0.0
|
||||||
|
DESCRIPTION=A simple FastAPI learning project
|
||||||
|
DEBUG=True
|
||||||
|
ENVIRONMENT=development
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=8000
|
||||||
|
WORKERS=1
|
||||||
|
RELOAD=True
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# API 配置
|
||||||
|
API_V1_STR=/api/v1
|
||||||
|
DOCS_URL=/docs
|
||||||
|
REDOC_URL=/redoc
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_URL=postgresql://user:password@localhost/dbname
|
||||||
|
# DATABASE_URL=mysql://user:password@localhost/dbname
|
||||||
|
# DATABASE_URL=sqlite:///./app.db
|
||||||
|
DB_ECHO=True
|
||||||
|
DB_POOL_SIZE=5
|
||||||
|
DB_MAX_OVERFLOW=10
|
||||||
|
|
||||||
|
# Redis 配置
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DB=0
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# CORS 配置
|
||||||
|
CORS_ORIGINS=["http://localhost:3000","http://localhost:8080"]
|
||||||
|
CORS_ALLOW_CREDENTIALS=True
|
||||||
|
CORS_ALLOW_METHODS=["*"]
|
||||||
|
CORS_ALLOW_HEADERS=["*"]
|
||||||
|
|
||||||
|
# JWT 认证配置
|
||||||
|
SECRET_KEY=your-secret-key-here-change-in-production
|
||||||
|
ALGORITHM=HS256
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
|
||||||
|
# 邮件配置
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=your-email@gmail.com
|
||||||
|
SMTP_PASSWORD=your-password
|
||||||
|
SMTP_FROM=noreply@example.com
|
||||||
|
|
||||||
|
# 文件上传配置
|
||||||
|
UPLOAD_DIR=./uploads
|
||||||
|
MAX_UPLOAD_SIZE=10485760 # 10MB in bytes
|
||||||
|
ALLOWED_EXTENSIONS=["jpg","jpeg","png","pdf","doc","docx"]
|
||||||
|
|
||||||
|
# 第三方 API 配置
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
STRIPE_API_KEY=
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_REGION=us-east-1
|
||||||
|
|
||||||
|
# 监控配置
|
||||||
|
SENTRY_DSN=
|
||||||
|
PROMETHEUS_ENABLED=False
|
||||||
|
METRICS_PATH=/metrics
|
||||||
|
|
||||||
|
# 限流配置
|
||||||
|
RATE_LIMIT_ENABLED=True
|
||||||
|
RATE_LIMIT_REQUESTS=100
|
||||||
|
RATE_LIMIT_PERIOD=60 # seconds
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.dev
|
||||||
|
.env.test
|
||||||
|
.env.prod
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# FastAPI specific
|
||||||
|
uploads/
|
||||||
|
logs/
|
||||||
|
*.db
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
dev.db
|
||||||
|
test.db
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
此文件为 Claude Code (claude.ai/code) 在此代码库中工作时提供指导。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
### 运行应用程序
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 1. 直接运行(使用 .env 或默认配置)
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
# 2. 指定环境运行(推荐)
|
||||||
|
ENVIRONMENT=development python main.py # 使用 .env.dev 配置
|
||||||
|
ENVIRONMENT=testing python main.py # 使用 .env.test 配置
|
||||||
|
ENVIRONMENT=production python main.py # 使用 .env.prod 配置
|
||||||
|
|
||||||
|
# 3. Windows 系统设置环境变量
|
||||||
|
set ENVIRONMENT=development && python main.py
|
||||||
|
set ENVIRONMENT=production && python main.py
|
||||||
|
|
||||||
|
# 4. 手动指定 uvicorn 参数(不推荐,应优先使用配置文件)
|
||||||
|
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
# 5. 临时覆盖配置(环境变量优先级最高)
|
||||||
|
PORT=9000 HOST=127.0.0.1 python main.py
|
||||||
|
|
||||||
|
# 6. 使用便捷启动脚本(推荐)
|
||||||
|
python start.py --env dev # 开发环境
|
||||||
|
python start.py --env prod # 生产环境
|
||||||
|
python start.py --port 9000 # 指定端口
|
||||||
|
python start.py --env dev --reload # 开发环境+强制热重载
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件启动说明
|
||||||
|
应用会自动读取配置并使用对应的启动参数:
|
||||||
|
|
||||||
|
- **开发环境** (`.env.dev`):自动启用热重载、调试模式、允许所有CORS
|
||||||
|
- **测试环境** (`.env.test`):关闭API文档、严格CORS限制
|
||||||
|
- **生产环境** (`.env.prod`):多worker、关闭热重载、严格安全设置
|
||||||
|
|
||||||
|
### 配置管理
|
||||||
|
```bash
|
||||||
|
# 复制环境配置模板
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 查看当前配置
|
||||||
|
python -c "from app.core.config import settings; print(f'Environment: {settings.ENVIRONMENT}'); print(f'Debug: {settings.DEBUG}')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 API
|
||||||
|
```bash
|
||||||
|
# 访问交互式 API 文档
|
||||||
|
# 在浏览器中打开 http://localhost:8000/docs (Swagger UI)
|
||||||
|
# 或 http://localhost:8000/redoc (ReDoc)
|
||||||
|
|
||||||
|
# 健康检查端点
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 架构说明
|
||||||
|
|
||||||
|
### 项目结构模式
|
||||||
|
这个 FastAPI 演示项目采用分层架构和应用工厂模式,具有清晰的关注点分离:
|
||||||
|
|
||||||
|
- **应用工厂** (`app/core/application.py`):使用工厂模式创建和配置应用实例,集中管理中间件、路由注册和事件处理器。
|
||||||
|
|
||||||
|
- **路由聚合** (`app/api/v1/router.py`):集中管理 v1 版本的所有路由注册,保持主文件精简。
|
||||||
|
|
||||||
|
- **API 层** (`app/api/v1/endpoints/`):处理 HTTP 请求和响应的 FastAPI 路由器。每个路由器对应一个资源(用户、产品)并定义 API 端点。
|
||||||
|
|
||||||
|
- **服务层** (`app/services/`):使用静态服务类实现业务逻辑。目前使用内存中的模拟数据库进行演示。服务处理 CRUD 操作和业务规则。
|
||||||
|
|
||||||
|
- **模式层** (`app/schemas/`):用于请求/响应验证和序列化的 Pydantic 模型。为每个实体遵循 Base、Create、Update 和 Response 模型的模式。
|
||||||
|
|
||||||
|
- **模型层** (`app/models/`):表示核心实体的领域模型。这些是配置了 `from_attributes = True` 的 Pydantic BaseModel,以兼容 ORM。
|
||||||
|
|
||||||
|
- **核心配置** (`app/core/config.py`):使用 pydantic-settings 的集中式设置管理,支持多环境配置文件和环境变量覆盖。
|
||||||
|
|
||||||
|
### 关键架构决策
|
||||||
|
|
||||||
|
1. **应用工厂模式**:使用 `create_application()` 函数创建应用,便于测试和配置管理,支持多环境部署。
|
||||||
|
|
||||||
|
2. **路由模块化**:通过 `router.py` 集中管理路由注册,`main.py` 保持精简,只负责应用启动。
|
||||||
|
|
||||||
|
3. **版本化的 API 路由**:所有端点都以 `/api/v1/` 为前缀,便于 API 版本迭代和兼容性管理。
|
||||||
|
|
||||||
|
4. **服务模式**:使用单例服务实例(`user_service`、`product_service`)封装所有业务逻辑,保持路由器精简。
|
||||||
|
|
||||||
|
5. **模式分离**:为不同操作(Create、Update、Response)设置不同的模式,在 API 设计中提供灵活性,而不暴露内部模型细节。
|
||||||
|
|
||||||
|
6. **内存数据存储**:目前使用内存中的模拟列表进行数据存储。这是有意为演示而设计的 - 实际实现将在服务层中集成数据库。
|
||||||
|
|
||||||
|
7. **CORS 中间件**:配置了宽松的设置(`allow_origins=["*"]`)适合开发环境。生产环境中应该限制。
|
||||||
|
|
||||||
|
8. **事件处理器**:在 `application.py` 中注册启动和关闭事件,便于管理应用生命周期(如数据库连接、缓存初始化等)。
|
||||||
|
|
||||||
|
### 配置管理详情
|
||||||
|
|
||||||
|
项目采用类似 Spring Boot 的分层配置管理:
|
||||||
|
|
||||||
|
1. **配置文件优先级**(从高到低):
|
||||||
|
- 环境变量
|
||||||
|
- `.env.{environment}` (如 `.env.dev`, `.env.prod`)
|
||||||
|
- `.env` 基础配置文件
|
||||||
|
- 代码中的默认值
|
||||||
|
|
||||||
|
2. **多环境配置**:
|
||||||
|
- `.env.dev` - 开发环境:调试开启、允许所有CORS、使用SQLite
|
||||||
|
- `.env.test` - 测试环境:关闭API文档、严格限流、测试数据库
|
||||||
|
- `.env.prod` - 生产环境:关闭调试、使用PostgreSQL、启用监控
|
||||||
|
|
||||||
|
3. **配置特性**:
|
||||||
|
- 类型验证和转换
|
||||||
|
- 环境变量解析(如CORS_ORIGINS支持逗号分隔)
|
||||||
|
- 配置验证器(如环境名称校验)
|
||||||
|
- 便利属性(如 `settings.is_development`)
|
||||||
|
|
||||||
|
4. **敏感信息管理**:
|
||||||
|
- 密钥、密码等敏感信息通过环境变量管理
|
||||||
|
- `.env` 文件已添加到 `.gitignore`
|
||||||
|
- 提供 `.env.example` 作为配置模板
|
||||||
|
|
||||||
|
### 添加新功能
|
||||||
|
|
||||||
|
添加新资源/实体时:
|
||||||
|
1. 在 `app/models/` 中创建模型
|
||||||
|
2. 在 `app/schemas/` 中创建模式(Base、Create、Update、Response)
|
||||||
|
3. 在 `app/services/` 中创建包含 CRUD 操作的服务类
|
||||||
|
4. 在 `app/api/v1/endpoints/` 中创建包含端点的路由器
|
||||||
|
5. 在 `app/api/v1/router.py` 中注册新的路由器,设置适当的前缀和标签
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from typing import List
|
||||||
|
from app.schemas.product import ProductResponse, ProductCreate
|
||||||
|
from app.services.product_service import product_service
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[ProductResponse])
|
||||||
|
async def get_products():
|
||||||
|
"""获取所有商品列表"""
|
||||||
|
products = product_service.get_all_products()
|
||||||
|
return products
|
||||||
|
|
||||||
|
@router.get("/{product_id}", response_model=ProductResponse)
|
||||||
|
async def get_product(product_id: int):
|
||||||
|
"""根据ID获取单个商品"""
|
||||||
|
product = product_service.get_product_by_id(product_id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(status_code=404, detail="Product not found")
|
||||||
|
return product
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from typing import List
|
||||||
|
from app.schemas.user import UserResponse, UserCreate
|
||||||
|
from app.services.user_service import user_service
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[UserResponse])
|
||||||
|
async def get_users():
|
||||||
|
"""获取所有用户列表"""
|
||||||
|
users = user_service.get_all_users()
|
||||||
|
return users
|
||||||
|
|
||||||
|
@router.get("/{user_id}", response_model=UserResponse)
|
||||||
|
async def get_user(user_id: int):
|
||||||
|
"""根据ID获取单个用户"""
|
||||||
|
user = user_service.get_user_by_id(user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
return user
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""
|
||||||
|
API v1 路由聚合
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from app.api.v1.endpoints import users, products
|
||||||
|
|
||||||
|
# 创建 v1 版本的主路由
|
||||||
|
api_router = APIRouter()
|
||||||
|
|
||||||
|
# 注册所有端点路由
|
||||||
|
api_router.include_router(
|
||||||
|
users.router,
|
||||||
|
prefix="/users",
|
||||||
|
tags=["用户管理"]
|
||||||
|
)
|
||||||
|
|
||||||
|
api_router.include_router(
|
||||||
|
products.router,
|
||||||
|
prefix="/products",
|
||||||
|
tags=["产品管理"]
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
"""
|
||||||
|
应用工厂模式 - 创建和配置 FastAPI 应用实例
|
||||||
|
"""
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
|
||||||
|
def create_application() -> FastAPI:
|
||||||
|
"""
|
||||||
|
创建 FastAPI 应用实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
FastAPI: 配置完成的应用实例
|
||||||
|
"""
|
||||||
|
app = FastAPI(
|
||||||
|
title=settings.PROJECT_NAME,
|
||||||
|
version=settings.VERSION,
|
||||||
|
description=settings.DESCRIPTION,
|
||||||
|
debug=settings.DEBUG,
|
||||||
|
docs_url=settings.DOCS_URL,
|
||||||
|
redoc_url=settings.REDOC_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置中间件
|
||||||
|
setup_middleware(app)
|
||||||
|
|
||||||
|
# 注册路由
|
||||||
|
register_routers(app)
|
||||||
|
|
||||||
|
# 注册事件处理器
|
||||||
|
register_events(app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def setup_middleware(app: FastAPI) -> None:
|
||||||
|
"""
|
||||||
|
配置应用中间件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI 应用实例
|
||||||
|
"""
|
||||||
|
# CORS 中间件配置
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=settings.CORS_ORIGINS,
|
||||||
|
allow_credentials=settings.CORS_ALLOW_CREDENTIALS,
|
||||||
|
allow_methods=settings.CORS_ALLOW_METHODS,
|
||||||
|
allow_headers=settings.CORS_ALLOW_HEADERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 可以在这里添加其他中间件
|
||||||
|
# app.add_middleware(...)
|
||||||
|
|
||||||
|
|
||||||
|
def register_routers(app: FastAPI) -> None:
|
||||||
|
"""
|
||||||
|
注册所有路由
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI 应用实例
|
||||||
|
"""
|
||||||
|
from app.api.v1.router import api_router
|
||||||
|
|
||||||
|
# 注册 API v1 路由
|
||||||
|
app.include_router(api_router, prefix="/api/v1")
|
||||||
|
|
||||||
|
# 根路径路由
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {
|
||||||
|
"message": "Welcome to FastAPI Demo",
|
||||||
|
"version": settings.VERSION,
|
||||||
|
"docs": "/docs",
|
||||||
|
"redoc": "/redoc"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 健康检查路由
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy", "service": settings.PROJECT_NAME}
|
||||||
|
|
||||||
|
|
||||||
|
def register_events(app: FastAPI) -> None:
|
||||||
|
"""
|
||||||
|
注册应用事件处理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI 应用实例
|
||||||
|
"""
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
|
"""应用启动时执行"""
|
||||||
|
print(f"[INFO] {settings.PROJECT_NAME} v{settings.VERSION} is starting up...")
|
||||||
|
# 可以在这里添加启动时的初始化操作
|
||||||
|
# 如:数据库连接、缓存初始化等
|
||||||
|
|
||||||
|
@app.on_event("shutdown")
|
||||||
|
async def shutdown_event():
|
||||||
|
"""应用关闭时执行"""
|
||||||
|
print(f"[INFO] {settings.PROJECT_NAME} is shutting down...")
|
||||||
|
# 可以在这里添加关闭时的清理操作
|
||||||
|
# 如:关闭数据库连接、保存缓存等
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
"""
|
||||||
|
应用配置管理
|
||||||
|
支持从环境变量和 .env 文件加载配置
|
||||||
|
"""
|
||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import Field, validator
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""应用配置类"""
|
||||||
|
|
||||||
|
# === 应用基础配置 ===
|
||||||
|
PROJECT_NAME: str = "FastAPI Demo"
|
||||||
|
VERSION: str = "1.0.0"
|
||||||
|
DESCRIPTION: str = "A simple FastAPI learning project"
|
||||||
|
DEBUG: bool = False
|
||||||
|
ENVIRONMENT: str = Field(default="production", description="运行环境: development, testing, production")
|
||||||
|
|
||||||
|
# === 服务器配置 ===
|
||||||
|
HOST: str = "127.0.0.1"
|
||||||
|
PORT: int = 8000
|
||||||
|
WORKERS: int = 1
|
||||||
|
RELOAD: bool = False
|
||||||
|
LOG_LEVEL: str = "info"
|
||||||
|
|
||||||
|
# === API 配置 ===
|
||||||
|
API_V1_STR: str = "/api/v1"
|
||||||
|
DOCS_URL: Optional[str] = "/docs"
|
||||||
|
REDOC_URL: Optional[str] = "/redoc"
|
||||||
|
|
||||||
|
# === 数据库配置 ===
|
||||||
|
DATABASE_URL: Optional[str] = None
|
||||||
|
DB_ECHO: bool = False
|
||||||
|
DB_POOL_SIZE: int = 5
|
||||||
|
DB_MAX_OVERFLOW: int = 10
|
||||||
|
|
||||||
|
# === Redis 配置 ===
|
||||||
|
REDIS_HOST: str = "localhost"
|
||||||
|
REDIS_PORT: int = 6379
|
||||||
|
REDIS_DB: int = 0
|
||||||
|
REDIS_PASSWORD: Optional[str] = None
|
||||||
|
|
||||||
|
# === CORS 配置 ===
|
||||||
|
CORS_ORIGINS: List[str] = ["*"]
|
||||||
|
CORS_ALLOW_CREDENTIALS: bool = True
|
||||||
|
CORS_ALLOW_METHODS: List[str] = ["*"]
|
||||||
|
CORS_ALLOW_HEADERS: List[str] = ["*"]
|
||||||
|
|
||||||
|
# === JWT 认证配置 ===
|
||||||
|
SECRET_KEY: str = Field(default="your-secret-key-change-in-production", min_length=32)
|
||||||
|
ALGORITHM: str = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
||||||
|
|
||||||
|
# === 邮件配置 ===
|
||||||
|
SMTP_HOST: Optional[str] = None
|
||||||
|
SMTP_PORT: int = 587
|
||||||
|
SMTP_USER: Optional[str] = None
|
||||||
|
SMTP_PASSWORD: Optional[str] = None
|
||||||
|
SMTP_FROM: Optional[str] = None
|
||||||
|
|
||||||
|
# === 文件上传配置 ===
|
||||||
|
UPLOAD_DIR: str = "./uploads"
|
||||||
|
MAX_UPLOAD_SIZE: int = 10485760 # 10MB
|
||||||
|
ALLOWED_EXTENSIONS: List[str] = ["jpg", "jpeg", "png", "pdf", "doc", "docx"]
|
||||||
|
|
||||||
|
# === 第三方 API 配置 ===
|
||||||
|
OPENAI_API_KEY: Optional[str] = None
|
||||||
|
STRIPE_API_KEY: Optional[str] = None
|
||||||
|
AWS_ACCESS_KEY_ID: Optional[str] = None
|
||||||
|
AWS_SECRET_ACCESS_KEY: Optional[str] = None
|
||||||
|
AWS_REGION: str = "us-east-1"
|
||||||
|
|
||||||
|
# === 监控配置 ===
|
||||||
|
SENTRY_DSN: Optional[str] = None
|
||||||
|
PROMETHEUS_ENABLED: bool = False
|
||||||
|
METRICS_PATH: str = "/metrics"
|
||||||
|
|
||||||
|
# === 限流配置 ===
|
||||||
|
RATE_LIMIT_ENABLED: bool = True
|
||||||
|
RATE_LIMIT_REQUESTS: int = 100
|
||||||
|
RATE_LIMIT_PERIOD: int = 60 # seconds
|
||||||
|
|
||||||
|
@validator("ENVIRONMENT")
|
||||||
|
def validate_environment(cls, v):
|
||||||
|
"""验证运行环境"""
|
||||||
|
allowed = ["development", "testing", "production"]
|
||||||
|
if v not in allowed:
|
||||||
|
raise ValueError(f"ENVIRONMENT must be one of {allowed}")
|
||||||
|
return v
|
||||||
|
|
||||||
|
@validator("CORS_ORIGINS", pre=True)
|
||||||
|
def parse_cors_origins(cls, v):
|
||||||
|
"""解析 CORS 来源配置"""
|
||||||
|
if isinstance(v, str):
|
||||||
|
return [origin.strip() for origin in v.split(",")]
|
||||||
|
return v
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_development(self) -> bool:
|
||||||
|
"""是否为开发环境"""
|
||||||
|
return self.ENVIRONMENT == "development"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_production(self) -> bool:
|
||||||
|
"""是否为生产环境"""
|
||||||
|
return self.ENVIRONMENT == "production"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_testing(self) -> bool:
|
||||||
|
"""是否为测试环境"""
|
||||||
|
return self.ENVIRONMENT == "testing"
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
env_file_encoding = "utf-8"
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
|
||||||
|
# 根据环境加载不同的配置文件
|
||||||
|
def get_settings() -> Settings:
|
||||||
|
"""获取配置实例"""
|
||||||
|
import os
|
||||||
|
env = os.getenv("ENVIRONMENT", "development")
|
||||||
|
|
||||||
|
env_files = [".env"]
|
||||||
|
if env == "development":
|
||||||
|
env_files.append(".env.dev")
|
||||||
|
elif env == "testing":
|
||||||
|
env_files.append(".env.test")
|
||||||
|
elif env == "production":
|
||||||
|
env_files.append(".env.prod")
|
||||||
|
|
||||||
|
# 尝试加载对应的环境配置文件
|
||||||
|
for env_file in reversed(env_files):
|
||||||
|
if os.path.exists(env_file):
|
||||||
|
return Settings(_env_file=env_file)
|
||||||
|
|
||||||
|
return Settings()
|
||||||
|
|
||||||
|
|
||||||
|
# 全局配置实例
|
||||||
|
settings = get_settings()
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class Product(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
price: float
|
||||||
|
stock: int = 0
|
||||||
|
is_available: bool = True
|
||||||
|
created_at: datetime = datetime.now()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
full_name: Optional[str] = None
|
||||||
|
is_active: bool = True
|
||||||
|
created_at: datetime = datetime.now()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class ProductBase(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
price: float
|
||||||
|
stock: int = 0
|
||||||
|
|
||||||
|
class ProductCreate(ProductBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ProductUpdate(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
price: Optional[float] = None
|
||||||
|
stock: Optional[int] = None
|
||||||
|
is_available: Optional[bool] = None
|
||||||
|
|
||||||
|
class ProductResponse(ProductBase):
|
||||||
|
id: int
|
||||||
|
is_available: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
from pydantic import BaseModel, EmailStr
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class UserBase(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: EmailStr
|
||||||
|
full_name: Optional[str] = None
|
||||||
|
|
||||||
|
class UserCreate(UserBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
username: Optional[str] = None
|
||||||
|
email: Optional[EmailStr] = None
|
||||||
|
full_name: Optional[str] = None
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
|
||||||
|
class UserResponse(UserBase):
|
||||||
|
id: int
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
from app.models.product import Product
|
||||||
|
from app.schemas.product import ProductCreate, ProductUpdate
|
||||||
|
|
||||||
|
fake_products_db = [
|
||||||
|
Product(id=1, name="Laptop", description="High performance laptop", price=999.99, stock=10),
|
||||||
|
Product(id=2, name="Mouse", description="Wireless mouse", price=29.99, stock=50),
|
||||||
|
Product(id=3, name="Keyboard", description="Mechanical keyboard", price=89.99, stock=25),
|
||||||
|
]
|
||||||
|
|
||||||
|
class ProductService:
|
||||||
|
@staticmethod
|
||||||
|
def get_all_products() -> List[Product]:
|
||||||
|
return fake_products_db
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_product_by_id(product_id: int) -> Optional[Product]:
|
||||||
|
for product in fake_products_db:
|
||||||
|
if product.id == product_id:
|
||||||
|
return product
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_product(product_data: ProductCreate) -> Product:
|
||||||
|
new_id = max([p.id for p in fake_products_db]) + 1 if fake_products_db else 1
|
||||||
|
new_product = Product(
|
||||||
|
id=new_id,
|
||||||
|
name=product_data.name,
|
||||||
|
description=product_data.description,
|
||||||
|
price=product_data.price,
|
||||||
|
stock=product_data.stock
|
||||||
|
)
|
||||||
|
fake_products_db.append(new_product)
|
||||||
|
return new_product
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_product(product_id: int, product_data: ProductUpdate) -> Optional[Product]:
|
||||||
|
for i, product in enumerate(fake_products_db):
|
||||||
|
if product.id == product_id:
|
||||||
|
update_data = product_data.model_dump(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(product, field, value)
|
||||||
|
return product
|
||||||
|
return None
|
||||||
|
|
||||||
|
product_service = ProductService()
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
from app.models.user import User
|
||||||
|
from app.schemas.user import UserCreate, UserUpdate
|
||||||
|
|
||||||
|
fake_users_db = [
|
||||||
|
User(id=1, username="admin", email="admin@example.com", full_name="Admin User"),
|
||||||
|
User(id=2, username="john", email="john@example.com", full_name="John Doe"),
|
||||||
|
User(id=3, username="jane", email="jane@example.com", full_name="Jane Smith"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class UserService:
|
||||||
|
@staticmethod
|
||||||
|
def get_all_users() -> List[User]:
|
||||||
|
return fake_users_db
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_user_by_id(user_id: int) -> Optional[User]:
|
||||||
|
for user in fake_users_db:
|
||||||
|
if user.id == user_id:
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_user(user_data: UserCreate) -> User:
|
||||||
|
new_id = max([u.id for u in fake_users_db]) + 1 if fake_users_db else 1
|
||||||
|
new_user = User(
|
||||||
|
id=new_id,
|
||||||
|
username=user_data.username,
|
||||||
|
email=user_data.email,
|
||||||
|
full_name=user_data.full_name
|
||||||
|
)
|
||||||
|
fake_users_db.append(new_user)
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_user(user_id: int, user_data: UserUpdate) -> Optional[User]:
|
||||||
|
for i, user in enumerate(fake_users_db):
|
||||||
|
if user.id == user_id:
|
||||||
|
update_data = user_data.model_dump(exclude_unset=True)
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(user, field, value)
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
user_service = UserService()
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
FastAPI 应用主入口
|
||||||
|
"""
|
||||||
|
from app.core.application import create_application
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
# 创建应用实例
|
||||||
|
app = create_application()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
# 根据环境决定是否使用字符串形式的 app(支持热重载)
|
||||||
|
app_str = "main:app" if settings.RELOAD else app
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
app_str,
|
||||||
|
host=settings.HOST,
|
||||||
|
port=settings.PORT,
|
||||||
|
reload=settings.RELOAD,
|
||||||
|
log_level=settings.LOG_LEVEL.lower(),
|
||||||
|
workers=1 if settings.RELOAD else settings.WORKERS, # 热重载模式下只能用1个worker
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
fastapi==0.109.0
|
||||||
|
uvicorn[standard]==0.27.0
|
||||||
|
pydantic==2.5.3
|
||||||
|
pydantic-settings==2.1.0
|
||||||
|
python-multipart==0.0.6
|
||||||
|
email-validator==2.1.0
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
便捷启动脚本 - 支持命令行参数指定环境
|
||||||
|
用法:
|
||||||
|
python start.py # 使用默认配置
|
||||||
|
python start.py --env dev # 开发环境
|
||||||
|
python start.py --env test # 测试环境
|
||||||
|
python start.py --env prod # 生产环境
|
||||||
|
python start.py --port 9000 # 临时指定端口
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="启动 FastAPI 应用")
|
||||||
|
parser.add_argument(
|
||||||
|
"--env",
|
||||||
|
choices=["dev", "test", "prod"],
|
||||||
|
help="指定运行环境 (dev/test/prod)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--host",
|
||||||
|
help="指定主机地址"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--port",
|
||||||
|
type=int,
|
||||||
|
help="指定端口号"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--workers",
|
||||||
|
type=int,
|
||||||
|
help="指定 worker 数量(生产环境)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--reload",
|
||||||
|
action="store_true",
|
||||||
|
help="启用热重载"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--log-level",
|
||||||
|
choices=["critical", "error", "warning", "info", "debug"],
|
||||||
|
help="日志级别"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
if args.env:
|
||||||
|
env_map = {"dev": "development", "test": "testing", "prod": "production"}
|
||||||
|
os.environ["ENVIRONMENT"] = env_map[args.env]
|
||||||
|
print(f"[INFO] 启动环境: {env_map[args.env]} (配置文件: .env.{args.env})")
|
||||||
|
|
||||||
|
# 临时覆盖配置
|
||||||
|
if args.host:
|
||||||
|
os.environ["HOST"] = args.host
|
||||||
|
if args.port:
|
||||||
|
os.environ["PORT"] = str(args.port)
|
||||||
|
if args.workers:
|
||||||
|
os.environ["WORKERS"] = str(args.workers)
|
||||||
|
if args.reload:
|
||||||
|
os.environ["RELOAD"] = "True"
|
||||||
|
if args.log_level:
|
||||||
|
os.environ["LOG_LEVEL"] = args.log_level
|
||||||
|
|
||||||
|
# 导入并启动应用
|
||||||
|
try:
|
||||||
|
from main import app
|
||||||
|
from app.core.config import settings
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
print(f"[CONFIG] 当前配置:")
|
||||||
|
print(f" - 环境: {settings.ENVIRONMENT}")
|
||||||
|
print(f" - 主机: {settings.HOST}")
|
||||||
|
print(f" - 端口: {settings.PORT}")
|
||||||
|
print(f" - 调试: {settings.DEBUG}")
|
||||||
|
print(f" - 热重载: {settings.RELOAD}")
|
||||||
|
print(f" - Workers: {settings.WORKERS}")
|
||||||
|
print(f" - 日志级别: {settings.LOG_LEVEL}")
|
||||||
|
|
||||||
|
if settings.DOCS_URL:
|
||||||
|
print(f"[DOCS] API 文档: http://{settings.HOST}:{settings.PORT}{settings.DOCS_URL}")
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# 根据环境决定是否使用字符串形式的 app(支持热重载)
|
||||||
|
app_str = "main:app" if settings.RELOAD else app
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
app_str,
|
||||||
|
host=settings.HOST,
|
||||||
|
port=settings.PORT,
|
||||||
|
reload=settings.RELOAD,
|
||||||
|
log_level=settings.LOG_LEVEL.lower(),
|
||||||
|
workers=1 if settings.RELOAD else settings.WORKERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"[ERROR] 导入错误: {e}")
|
||||||
|
print("请确保在正确的目录中运行此脚本")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] 启动失败: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue