from __future__ import annotations import os import secrets from pathlib import Path from dotenv import dotenv_values BASE_DIR = Path(__file__).resolve().parent.parent DATA_DIR = BASE_DIR / "data" STATIC_DIR = BASE_DIR / "static" UPLOAD_DIR = BASE_DIR / "uploads" IMAGE_DIR = STATIC_DIR / "images" def load_local_environment() -> None: """加载本地配置,优先级为系统环境变量 > .env.local > .env。""" values = dotenv_values(BASE_DIR / ".env") overrides = dotenv_values(BASE_DIR / ".env.local") # .env.local 中的空值表示“沿用 .env”,避免本机模板清空密钥。 values.update({key: value for key, value in overrides.items() if value}) for key, value in values.items(): if value: os.environ.setdefault(key, value) load_local_environment() def env_int(name: str, default: int) -> int: try: return int(os.environ.get(name, str(default))) except ValueError: return default def env_bool(name: str, default: bool = False) -> bool: value = os.environ.get(name) if value is None: return default return value.strip().lower() in {"1", "true", "yes", "on"} def app_env() -> str: return os.environ.get("APP_ENV", "development").strip().lower() or "development" class Config: APP_ENV = app_env() DEBUG = env_bool("DEBUG", APP_ENV in {"dev", "development", "local"}) SECRET_KEY = os.environ.get("SECRET_KEY") or secrets.token_hex(32) SECRET_KEY_GENERATED = not bool(os.environ.get("SECRET_KEY")) SQLALCHEMY_DATABASE_URI = os.environ.get( "DATABASE_URL", f"sqlite:///{DATA_DIR / 'pipe_survival_0331.db'}", ) SQLALCHEMY_TRACK_MODIFICATIONS = False MAX_CONTENT_LENGTH = env_int("MAX_UPLOAD_BYTES", 16 * 1024 * 1024) SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = "Lax" SESSION_COOKIE_SECURE = env_bool("SESSION_COOKIE_SECURE", APP_ENV == "production") FUSION_MODEL_CORE_DIR = os.environ.get( "FUSION_MODEL_CORE_DIR", str(BASE_DIR / "model_core"), ) ALLOW_REGISTRATION = env_bool("ALLOW_REGISTRATION", False) ADMIN_USERNAME = os.environ.get("ADMIN_USERNAME", "admin").strip() or "admin" ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD") ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL", "").strip().lower() APP_TIMEZONE = os.environ.get("APP_TIMEZONE", "Asia/Shanghai").strip() or "Asia/Shanghai" PASSWORD_RESET_TOKEN_MINUTES = env_int("PASSWORD_RESET_TOKEN_MINUTES", 30) RESEND_API_KEY = os.environ.get("RESEND_API_KEY", "").strip() RESEND_FROM_EMAIL = os.environ.get("RESEND_FROM_EMAIL", "").strip() EMAIL_CODE_MINUTES = env_int("EMAIL_CODE_MINUTES", 10) EMAIL_CODE_RESEND_SECONDS = env_int("EMAIL_CODE_RESEND_SECONDS", 60) EMAIL_CODE_MAX_ATTEMPTS = env_int("EMAIL_CODE_MAX_ATTEMPTS", 5) TRUSTED_DEVICE_DAYS = env_int("TRUSTED_DEVICE_DAYS", 30) def ensure_dirs() -> None: for path in (DATA_DIR, STATIC_DIR, IMAGE_DIR, UPLOAD_DIR): path.mkdir(parents=True, exist_ok=True)