import sys from pathlib import Path from pydantic import field_validator, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict from typing import Optional _env_path = Path(__file__).resolve().parent.parent.parent / ".env" if not _env_path.exists(): _env_path = Path(".env") class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=str(_env_path), extra="ignore") DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres123@db:5432/geo_platform" REDIS_URL: str = "redis://redis:6379/0" # JWT 密钥:必须通过环境变量设置,不提供任何默认值 JWT_SECRET: str JWT_EXPIRE_HOURS: int = 24 # NextAuth 密钥 SECRET_KEY: Optional[str] = None PLAYWRIGHT_BROWSERS_PATH: str = "/ms-playwright" ENABLE_LLM: bool = True ZHIPU_API_KEY: str = "" TONGYI_API_KEY: str = "" CORS_ORIGINS: str = "http://localhost:3000,http://localhost:3001" @field_validator("CORS_ORIGINS") @classmethod def validate_cors_origins(cls, v: str) -> str: import os if os.getenv("ENVIRONMENT", "development") == "production": origins = [o.strip() for o in v.split(",") if o.strip()] localhost_origins = [o for o in origins if "localhost" in o or "127.0.0.1" in o] if localhost_origins: print( f"[WARNING] CORS_ORIGINS contains localhost in production: {localhost_origins}. " "This is a security risk. Please configure proper production origins.", file=sys.stderr, ) return v # ---- LLM Provider 配置 ---- DEFAULT_LLM_PROVIDER: str = "openai" DEFAULT_LLM_MODEL: str = "qwen3-coder-plus" OPENAI_API_KEY: str = "" OPENAI_MODEL: str = "qwen3-coder-plus" OPENAI_BASE_URL: str = "https://coding.dashscope.aliyuncs.com/v1" DEEPSEEK_API_KEY: str = "" DEEPSEEK_MODEL: str = "deepseek-chat" DEEPSEEK_BASE_URL: str = "https://api.deepseek.com/v1" DEEPSEEK_MAX_CONTEXT: int = 64000 # AI平台API配置 MOONSHOT_API_KEY: str = "" # Kimi (月之暗面) API Key BAIDU_QIANFAN_API_KEY: str = "" # 百度千帆 API Key BAIDU_QIANFAN_SECRET_KEY: str = "" # 百度千帆 Secret Key DOUBAO_API_KEY: str = "" # 豆包 (字节跳动) API Key DOUBAO_ENDPOINT_ID: str = "" # 豆包推理接入点 ID # AI平台API调用频率限制(每分钟请求数) API_RATE_LIMIT_RPM: int = 10 # Payment Gateway Configuration WECHAT_PAY_MCH_ID: str = "" WECHAT_PAY_API_KEY: str = "" WECHAT_PAY_APP_ID: str = "" WECHAT_PAY_CERT_PATH: str = "" WECHAT_PAY_NOTIFY_URL: str = "" ALIPAY_APP_ID: str = "" ALIPAY_PRIVATE_KEY_PATH: str = "" ALIPAY_PUBLIC_KEY_PATH: str = "" ALIPAY_NOTIFY_URL: str = "" PAYMENT_MODE: str = "mock" ZHIHU_CLIENT_ID: str = "" ZHIHU_CLIENT_SECRET: str = "" ZHIHU_ACCESS_TOKEN: str = "" TOUTIAO_APP_ID: str = "" TOUTIAO_APP_SECRET: str = "" TOUTIAO_ACCESS_TOKEN: str = "" WECHAT_OFFICIAL_APP_ID: str = "" WECHAT_OFFICIAL_APP_SECRET: str = "" SMTP_HOST: str = "" SMTP_PORT: int = 587 SMTP_USER: str = "" SMTP_PASSWORD: str = "" SMTP_FROM_EMAIL: str = "noreply@geo-platform.com" SMTP_FROM_NAME: str = "GEO平台" EMAIL_MODE: str = "mock" SENDGRID_API_KEY: str = "" ALIYUN_MAIL_ACCESS_KEY: str = "" ALIYUN_MAIL_ACCESS_SECRET: str = "" ALIYUN_MAIL_REGION: str = "cn-hangzhou" DISTRIBUTION_MODE: str = "mock" @field_validator("JWT_SECRET") @classmethod def validate_jwt_secret(cls, v: str) -> str: if not v or v.strip() == "": print( "[FATAL] JWT_SECRET is not set. " "Please set a strong secret key (>= 32 characters) in your .env file.", file=sys.stderr, ) sys.exit(1) if len(v) < 32: print( f"[FATAL] JWT_SECRET is too short ({len(v)} chars). " "It must be at least 32 characters long.", file=sys.stderr, ) sys.exit(1) return v @model_validator(mode="after") def validate_secret_key(self) -> "Settings": if self.SECRET_KEY is not None: if len(self.SECRET_KEY) < 32: print( f"[FATAL] SECRET_KEY is too short ({len(self.SECRET_KEY)} chars). " "It must be at least 32 characters long.", file=sys.stderr, ) sys.exit(1) return self settings = Settings()