初始化 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