from functools import lru_cache from pathlib import Path from typing import Literal from pydantic import model_validator 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 = "OpenCloud API" environment: Literal["development", "test", "production"] = "development" api_v1_prefix: str = "/api/v1" debug: bool = False database_url: str database_pool_size: int = 10 database_max_overflow: int = 20 secret_key: str jwt_algorithm: str = "HS256" access_token_expire_minutes: int = 15 refresh_token_expire_days: int = 30 refresh_cookie_name: str = "opencloud_refresh" cookie_secure: bool = False cookie_samesite: Literal["lax", "strict", "none"] = "lax" cookie_domain: str | None = None frontend_url: str = "http://localhost:5173" public_base_url: str = "http://localhost:8000" cors_origins: str = "http://localhost:5173" upload_dir: Path = Path("data/uploads") max_upload_bytes: int = 20 * 1024 * 1024 max_image_pixels: int = 50_000_000 thumbnail_max_edge: int = 640 email_delivery_mode: Literal["console", "smtp"] = "console" smtp_host: str | None = None smtp_port: int = 587 smtp_username: str | None = None smtp_password: str | None = None smtp_starttls: bool = True email_from: str = "OpenCloud " email_confirmation_expire_hours: int = 24 password_reset_expire_minutes: int = 30 admin_email: str | None = None admin_password: str | None = None admin_username: str = "管理员" @property def cors_origin_list(self) -> list[str]: return [item.strip() for item in self.cors_origins.split(",") if item.strip()] @property def media_base_url(self) -> str: return f"{self.public_base_url.rstrip('/')}/media" @model_validator(mode="after") def validate_production_settings(self) -> "Settings": if self.environment == "production": if self.secret_key.startswith("change-me") or len(self.secret_key) < 32: raise ValueError("生产环境必须配置至少 32 个字符的 SECRET_KEY") if not self.cookie_secure: raise ValueError("生产环境必须启用 COOKIE_SECURE") if self.email_delivery_mode == "smtp" and not self.smtp_host: raise ValueError("SMTP 模式必须配置 SMTP_HOST") return self @lru_cache def get_settings() -> Settings: return Settings() settings = get_settings()