""" app/core/config.py — 环境变量加载 + 启动校验 必填项缺失则启动失败,不静默。 """ import os from functools import lru_cache from pydantic import field_validator from pydantic_settings import BaseSettings class Settings(BaseSettings): # ── 必填(缺失启动保护)─────────────────────────── FERNET_KEY: str JWT_SECRET: str DATABASE_URL: str MONGO_URI: str REDIS_URL: str # ── AI 提供商(不写死默认,可配置)────────────────── IMAGE_PROVIDER_PRIMARY: str = "gpt" IMAGE_PROVIDER_FALLBACK: str = "gemini" IMAGE_API_BASE: str = "" IMAGE_MODEL: str = "gpt-image-2" MODEL_IMAGE: str = "gpt-image-2" # 别名,与 IMAGE_MODEL 同义 MODEL_TEXT: str = "claude-opus-4-8" MODEL_VISION: str = "" # 视觉模型(verify_real_image 脚本用) CLAUDE_API_URL: str = "" GEMINI_API_URL: str = "" # ── 多 Provider Key(各人自录,测试/真实出图脚本用)── APIPORTS_BASE_URL: str = "" APIPORTS_KEY: str = "" CODEPROXY_BASE_URL: str = "" CODEPROXY_KEY: str = "" # ── 应用配置 ────────────────────────────────────── APP_ENV: str = "development" JWT_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7天 CELERY_BROKER_URL: str = "" CELERY_RESULT_BACKEND: str = "" # ── 并发限制(可配置,不写死)───────────────────── # 倩倩姐红线:每用户并发上限 5 个任务(客户侧实际跑几个再观察调整)。 MAX_CONCURRENT_TASKS_PER_USER: int = 5 # ── 文件存储路径 ────────────────────────────────── UPLOAD_BASE_PATH: str = "uploads/packages" # StaticFiles 实际服务的绝对目录(与 main.py app.mount("/uploads", .../uploads) 一致)。 # 容器内 main.py 在 /app/main.py,故 uploads 绝对根 = /app/uploads。 # 所有写盘/读盘统一锚定此根,避免 api(cwd=/app) 与 worker(cwd=/) 相对路径解析不一致。 UPLOAD_ABS_ROOT: str = "/app/uploads" model_config = {"env_file": ".env", "case_sensitive": True, "extra": "ignore"} @field_validator("FERNET_KEY") @classmethod def fernet_key_not_empty(cls, v: str) -> str: if not v or len(v) < 32: raise ValueError("FERNET_KEY must be set and at least 32 chars") return v @field_validator("JWT_SECRET") @classmethod def jwt_secret_not_empty(cls, v: str) -> str: if not v or len(v) < 32: raise ValueError("JWT_SECRET must be at least 32 characters") return v def celery_broker(self) -> str: return self.CELERY_BROKER_URL or self.REDIS_URL def celery_backend(self) -> str: return self.CELERY_RESULT_BACKEND or self.REDIS_URL @lru_cache() def get_settings() -> Settings: """单例配置,启动时校验一次。""" return Settings()