name: env-config
description: |
用于设置环境配置、加载.env文件或跨环境管理应用程序设置时使用。
触发场景:.env设置、加载环境变量、Pydantic BaseSettings、
配置验证或密钥管理。
不适用于:特定代码逻辑、功能标志或运行时功能切换。
环境配置技能
为Python/FastAPI项目提供专业的环境配置管理,支持安全密钥处理和多环境支持。
快速参考
| 模式 |
用法 |
| 加载.env |
在应用启动时使用 load_dotenv() |
| 访问变量 |
settings.DB_URL, settings.JWT_SECRET |
| 必需变量 |
Field(..., description="数据库URL") |
| 可选变量 |
DB_HOST: str = "localhost" |
| 密钥类型 |
敏感值使用 SecretStr |
项目结构
project/
├── .env # 本地开发环境(不提交)
├── .env.example # 包含所有必需变量的模板(提交)
├── .env.staging # 预发布环境
├── .env.production # 生产环境(由基础设施管理)
└── config/
├── __init__.py
└── settings.py # Pydantic BaseSettings
# config/settings.py
from functools import lru_cache
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# 应用程序
APP_NAME: str = "ERP系统"
DEBUG: bool = False
API_V1_PREFIX: str = "/v1"
# 数据库
DB_URL: str = Field(
...,
description="PostgreSQL连接URL",
examples=["postgresql://user:pass@localhost:5432/dbname"],
)
DB_POOL_SIZE: int = Field(default=5, ge=1, le=100)
# JWT认证
JWT_SECRET_KEY: SecretStr = Field(
...,
description="JWT签名密钥",
)
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRATION_MINUTES: int = Field(default=15, ge=1)
# Redis(可选)
REDIS_URL: str | None = None
# 日志
LOG_LEVEL: str = Field(default="INFO", pattern="^(DEBUG|INFO|WARNING|ERROR)$")
# CORS
CORS_ORIGINS: list[str] = ["http://localhost:3000"]
@property
def is_production(self) -> bool:
return not self.DEBUG
@lru_cache
def get_settings() -> Settings:
"""缓存的设置实例,用于应用生命周期。"""
return Settings()
.env.example - 模板文件
# .env.example - 复制到.env并填写值
# 不要提交实际密钥!
# 应用程序
APP_NAME="ERP系统"
DEBUG=false
API_V1_PREFIX="/v1"
# 数据库(必需)
DB_URL="postgresql://user:password@localhost:5432/erp_db"
# JWT认证(必需 - 使用openssl rand -hex 32生成)
JWT_SECRET_KEY="your-secret-key-here-generate-with-openssl-rand-hex-32"
JWT_ALGORITHM="HS256"
JWT_EXPIRATION_MINUTES=15
# Redis(可选)
# REDIS_URL="redis://localhost:6379/0"
# 日志
LOG_LEVEL="INFO"
# CORS
CORS_ORIGINS="http://localhost:3000"
.env - 本地开发
# .env - 仅用于本地开发
# 切勿将此文件提交到版本控制
APP_NAME="ERP系统"
DEBUG=true
API_V1_PREFIX="/v1"
# 本地PostgreSQL
DB_URL="postgresql://postgres:postgres@localhost:5432/erp_dev"
# 使用openssl rand -hex 32生成
JWT_SECRET_KEY="local-dev-secret-key-change-in-production"
JWT_ALGORITHM="HS256"
JWT_EXPIRATION_MINUTES=15
# 本地Redis(如果使用)
REDIS_URL="redis://localhost:6379/0"
LOG_LEVEL="DEBUG"
CORS_ORIGINS="http://localhost:3000,http://localhost:5173"
在应用程序中使用
FastAPI应用程序
# main.py
from contextlib import asynccontextmanager
from dotenv import load_dotenv
load_dotenv() # 加载.env文件
from config.settings import get_settings
@asynccontextmanager
async def lifespan(app):
settings = get_settings()
print(f"启动{settings.APP_NAME},模式:{'DEBUG' if settings.DEBUG else 'PROD'}")
yield
print("正在关闭...")
app = FastAPI(
title=get_settings().APP_NAME,
lifespan=lifespan,
)
# 包含路由
from app.routers import fees, students
app.include_router(fees.router, prefix=get_settings().API_V1_PREFIX)
app.include_router(students.router, prefix=get_settings().API_V1_PREFIX)
数据库连接
# database.py
from sqlmodel import create_engine, Session
from config.settings import get_settings
settings = get_settings()
engine = create_engine(
settings.DB_URL.get_secret_value() if hasattr(settings.DB_URL, 'get_secret_value') else settings.DB_URL,
pool_size=settings.DB_POOL_SIZE,
max_overflow=10,
)
def get_session():
with Session(engine) as session:
yield session
JWT配置
# auth/jwt.py
from datetime import timedelta
from config.settings import get_settings
settings = get_settings()
JWT_SECRET = settings.JWT_SECRET_KEY.get_secret_value()
JWT_ALGORITHM = settings.JWT_ALGORITHM
ACCESS_TOKEN_EXPIRE_MINUTES = settings.JWT_EXPIRATION_MINUTES
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
# ... 令牌创建逻辑
pass
多环境支持
环境特定配置
# config/settings.py
class Settings(BaseSettings):
# ... 共享设置
@classmethod
def from_env(cls, env: str = "development") -> "Settings":
"""加载特定环境的设置。"""
env_file = {
"development": ".env",
"staging": ".env.staging",
"production": ".env.production",
}.get(env, ".env")
return cls(_env_file=env_file)
生产环境覆盖
# 生产环境应使用环境变量,而非.env文件
# 在部署平台(Docker、K8s、Cloud Run等)中设置这些变量
export DB_URL="postgresql://prod_user:prod_pass@prod-db.example.com:5432/erp_prod"
export JWT_SECRET_KEY="production-secret-key-from-secrets-manager"
export DEBUG=false
export LOG_LEVEL="WARNING"
密钥管理
生成密钥
# 生成安全随机密钥
openssl rand -hex 32 # 用于JWT_SECRET_KEY
# 生成数据库密码
openssl rand -base64 32
密钥轮换脚本
# scripts/rotate_secret.py
"""在所有环境中轮换密钥。"""
import os
import re
def rotate_secret(env_file: str, key: str, new_value: str):
"""在.env文件中替换密钥值。"""
with open(env_file, "r") as f:
content = f.read()
# 匹配KEY=value的模式
pattern = f"^{key}=.*$"
replacement = f"{key}={new_value}"
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
with open(env_file, "w") as f:
f.write(new_content)
print(f"已在{env_file}中轮换{key}")
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("用法:rotate_secret.py <env_file> <key> <new_value>")
sys.exit(1)
rotate_secret(sys.argv[1], sys.argv[2], sys.argv[3])
质量检查清单
- [ ] 加载时验证:所有必需字段都有
Field(..., ...)验证
- [ ] 日志无泄露:敏感值使用
SecretStr,永不打印设置
- [ ] 提交.env.example:模板显示所有必需变量
- [ ] .env被git忽略:本地开发文件从版本控制中排除
- [ ] 生成密钥:JWT密钥使用
openssl rand -hex 32
- [ ] 环境隔离:开发/预发布/生产环境有不同的配置
- [ ] 类型安全:所有设置都有正确的类型注解
与其他技能的集成
| 技能 |
集成点 |
@jwt-auth |
从设置中获取JWT_SECRET_KEY |
@sqlmodel-crud |
从设置中获取DB_URL |
@fastapi-app |
从设置中获取所有应用设置 |
@db-migration |
用于迁移的数据库URL |
@api-route-design |
API前缀、CORS来源 |
安全最佳实践
应该做
- 密码、API密钥、令牌使用
SecretStr
- 定期轮换密钥
- 每个环境使用不同的密钥
- 将生产密钥存储在密钥管理器中
- 在启动时验证所有必需设置
不应该做
- 切勿将
.env文件提交到版本控制
- 切勿在源代码中硬编码密钥
- 切勿记录设置或环境变量
- 切勿在生产中使用默认/占位符密钥
- 切勿在错误消息中暴露配置详情
启动验证
# config/validate.py
"""在启动时验证必需配置。"""
from pydantic import ValidationError
from config.settings import Settings
def validate_settings() -> bool:
"""确保所有必需设置都已配置。"""
try:
settings = Settings()
return True
except ValidationError as e:
print("配置验证失败:")
for error in e.errors():
print(f" - {error['loc'][0]}: {error['msg']}")
return False
if __name__ == "__main__":
if not validate_settings():
exit(1)