初始化 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:
杨煜 2025-09-26 17:54:05 +08:00
commit 227bade97c
26 changed files with 990 additions and 0 deletions

View File

@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(python:*)",
"Bash(curl:*)",
"Bash(git init:*)",
"Bash(git remote:*)",
"Bash(git add:*)"
],
"deny": [],
"ask": []
}
}

73
.env.example Normal file
View File

@ -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

156
.gitignore vendored Normal file
View File

@ -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

133
CLAUDE.md Normal file
View File

@ -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
app/__init__.py Normal file
View File

0
app/api/__init__.py Normal file
View File

0
app/api/v1/__init__.py Normal file
View File

View File

View File

@ -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

View File

@ -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

21
app/api/v1/router.py Normal file
View File

@ -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
app/core/__init__.py Normal file
View File

104
app/core/application.py Normal file
View File

@ -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...")
# 可以在这里添加关闭时的清理操作
# 如:关闭数据库连接、保存缓存等

144
app/core/config.py Normal file
View File

@ -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
app/models/__init__.py Normal file
View File

15
app/models/product.py Normal file
View File

@ -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

14
app/models/user.py Normal file
View File

@ -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
app/schemas/__init__.py Normal file
View File

25
app/schemas/product.py Normal file
View File

@ -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

23
app/schemas/user.py Normal file
View File

@ -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
app/services/__init__.py Normal file
View File

View File

@ -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()

View File

@ -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()

23
main.py Normal file
View File

@ -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
)

6
requirements.txt Normal file
View File

@ -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

109
start.py Normal file
View File

@ -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()