# /// # config.py # 描述:系统配置管理,支持多环境配置 # 作者:AI Generated # 创建日期:2026-04-05 # /// import os from typing import Dict, List, Optional from pydantic import BaseModel from pydantic_settings import BaseSettings class NodeConfig(BaseModel): node_id: str name: str api_url: str api_key: str enabled: bool = True description: str = "" group: str = "default" class PluginConfig(BaseModel): name: str enabled: bool = True config: Dict = {} class WebhookConfig(BaseModel): urls: List[str] = [] enabled: bool = False timeout: int = 10 retry_times: int = 3 format: str = "wechat" event_types: List[str] = ["api_error", "wechat_offline", "wechat_online", "node_offline", "node_online"] class MonitorConfig(BaseModel): enabled: bool = True check_interval: int = 60 class RedisConfig(BaseModel): url: str = "redis://localhost:6379/0" db: int = 0 password: Optional[str] = None class DatabaseConfig(BaseModel): url: str = "postgresql://wxauto:wxauto@localhost:5432/wxauto" pool_size: int = 10 max_overflow: int = 20 class Config: populate_by_name = True class Settings(BaseSettings): app_name: str = "WXAuto Center" host: str = "0.0.0.0" port: int = 8080 debug: bool = False secret_key: str = "wxauto-center-secret-key-change-in-production" nodes: List[NodeConfig] = [] plugins: List[PluginConfig] = [] webhook: WebhookConfig = WebhookConfig() monitor: MonitorConfig = MonitorConfig() redis: RedisConfig = RedisConfig() database: DatabaseConfig = DatabaseConfig() cors_origins: List[str] = ["*"] api_prefix: str = "/api/v1" external_api_key: str = "your-external-api-key" workers: int = 1 class Config: env_file = ".env" env_file_encoding = "utf-8" populate_by_name = True extra = "allow" env_nested_delimiter = "_" _settings: Optional[Settings] = None _config_file: str = "config.json" def get_settings() -> Settings: global _settings if _settings is None: _settings = Settings() _settings.database.url = os.environ.get("DATABASE_URL", _settings.database.url) _settings.redis.url = os.environ.get("REDIS_URL", _settings.redis.url) return _settings def update_settings(**kwargs): global _settings if _settings is None: _settings = Settings() for key, value in kwargs.items(): if hasattr(_settings, key): setattr(_settings, key, value) def load_config_from_file(config_file: str = "config.json") -> bool: global _settings, _config_file _config_file = config_file if not os.path.exists(config_file): return False try: import json with open(config_file, "r", encoding="utf-8") as f: data = json.load(f) settings = get_settings() settings.app_name = data.get("app_name", settings.app_name) settings.host = data.get("host", settings.host) settings.port = data.get("port", settings.port) settings.debug = data.get("debug", settings.debug) settings.secret_key = data.get("secret_key", settings.secret_key) settings.cors_origins = data.get("cors_origins", settings.cors_origins) settings.api_prefix = data.get("api_prefix", settings.api_prefix) settings.external_api_key = data.get("external_api_key", settings.external_api_key) plugins_data = data.get("plugins", []) settings.plugins = [PluginConfig(**p) for p in plugins_data] monitor_data = data.get("monitor", {}) settings.monitor = MonitorConfig(**monitor_data) if monitor_data else MonitorConfig() settings.workers = data.get("workers", 1) settings.database.url = os.environ.get("DATABASE_URL", settings.database.url) settings.redis.url = os.environ.get("REDIS_URL", settings.redis.url) return True except Exception: return False def save_config_to_file(config_file: str = None): global _config_file if config_file: _config_file = config_file import json settings = get_settings() config_data = { "app_name": settings.app_name, "host": settings.host, "port": settings.port, "debug": settings.debug, "secret_key": settings.secret_key, "plugins": [p.model_dump() for p in settings.plugins], "monitor": settings.monitor.model_dump(), "workers": settings.workers, "cors_origins": settings.cors_origins, "api_prefix": settings.api_prefix, "external_api_key": settings.external_api_key, } with open(_config_file, "w", encoding="utf-8") as f: json.dump(config_data, f, indent=2, ensure_ascii=False) class ConfigManager: def __init__(self, config_file: str = "config.json"): self.config_file = config_file def get_node(self, node_id: str) -> Optional[NodeConfig]: settings = get_settings() for node in settings.nodes: if node.node_id == node_id: return node return None def get_all_nodes(self) -> List[NodeConfig]: return get_settings().nodes def get_enabled_nodes(self) -> List[NodeConfig]: return [n for n in get_settings().nodes if n.enabled] def add_node(self, node: NodeConfig) -> bool: existing = self.get_node(node.node_id) if existing: return False get_settings().nodes.append(node) return True def update_node(self, node_id: str, **kwargs) -> bool: for i, node in enumerate(get_settings().nodes): if node.node_id == node_id: for key, value in kwargs.items(): if hasattr(node, key): setattr(node, key, value) return True return False def remove_node(self, node_id: str) -> bool: for i, node in enumerate(get_settings().nodes): if node.node_id == node_id: get_settings().nodes.pop(i) return True return False def save_config(self): save_config_to_file(self.config_file) def load_config(self) -> bool: return load_config_from_file(self.config_file)