geo/backend/app/config.py

143 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = ""
# 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"
ATTRIBUTION_WINDOW_DAYS: int = 28
# Sentry Monitoring
SENTRY_DSN: str = ""
ENVIRONMENT: str = "development"
# Rate Limiting
RATE_LIMIT_BACKEND: str = "memory" # "memory" or "redis"
@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()