""" 统一配置存储模块 支持 MySQL 优先、JSON 文件 fallback 的读写策略。 """ import json import logging from pathlib import Path from typing import Optional from app.models import AppConfig from app.storage_manager import get_storage_manager from app.mysql_database import MySQLSessionLocal logger = logging.getLogger(__name__) CONFIG_DIR = Path(__file__).resolve().parent.parent / "config" CONFIG_FILES = { "symbols": CONFIG_DIR / "symbols_config.json", "ai": CONFIG_DIR / "ai_config.json", } DEFAULT_CONFIGS = { "symbols": {"futures": {}, "stock": {}}, "ai": { "models": [], "active_model": None, "analysis_settings": { "enable_technical_analysis": True, "enable_fundamental_analysis": False, "enable_sentiment_analysis": False, "risk_tolerance": "medium", "max_position_pct": 10, }, }, } class ConfigStore: """统一配置存储入口。""" def __init__( self, storage_manager=None, config_dir: Path = CONFIG_DIR, session_maker=None, ): self.storage_manager = storage_manager or get_storage_manager() self.config_dir = config_dir self.session_maker = session_maker or MySQLSessionLocal self.config_files = { "symbols": self.config_dir / "symbols_config.json", "ai": self.config_dir / "ai_config.json", } def get_config(self, key: str, fallback: Optional[dict] = None) -> dict: """读取配置,优先从 MySQL,其次 JSON 文件。""" if key not in self.config_files: raise ValueError(f"未知配置键: {key}") if self.storage_manager.check_mysql(): try: db_value = self._get_from_mysql(key) if db_value is not None: return db_value # 数据库未命中:从 JSON 读取并回填 json_value = self._load_json(key, fallback) self._save_to_mysql(key, json_value) return json_value except Exception as e: logger.warning(f"从 MySQL 读取配置 [{key}] 失败,降级到 JSON: {e}") return self._load_json(key, fallback) def set_config(self, key: str, value: dict) -> bool: """写入配置,始终写 JSON,MySQL 可用时同时写数据库。""" if key not in CONFIG_FILES: raise ValueError(f"未知配置键: {key}") # 始终写入 JSON 文件,保证 fallback 最新 self._save_to_json(key, value) if self.storage_manager.check_mysql(): try: self._save_to_mysql(key, value) return True except Exception as e: logger.warning(f"写入 MySQL 配置 [{key}] 失败,已保存到 JSON: {e}") return False return True def _get_from_mysql(self, key: str) -> Optional[dict]: """从 MySQL 读取配置。""" if self.session_maker is None: return None with self.session_maker() as db: record = db.query(AppConfig).filter(AppConfig.config_key == key).first() if record: return record.config_value return None def _save_to_mysql(self, key: str, value: dict) -> bool: """保存配置到 MySQL。""" if self.session_maker is None: return False with self.session_maker() as db: record = db.query(AppConfig).filter(AppConfig.config_key == key).first() if record: record.config_value = value else: record = AppConfig(config_key=key, config_value=value) db.add(record) db.commit() return True def _load_json(self, key: str, fallback: Optional[dict] = None) -> dict: """从 JSON 文件读取配置。""" file_path = self.config_files[key] if file_path.exists(): try: with open(file_path, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: logger.warning(f"读取 JSON 配置 [{key}] 失败: {e}") return fallback if fallback is not None else DEFAULT_CONFIGS.get(key, {}) def _save_to_json(self, key: str, value: dict) -> None: """保存配置到 JSON 文件。""" file_path = self.config_files[key] self.config_dir.mkdir(parents=True, exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: json.dump(value, f, ensure_ascii=False, indent=4) def _config_exists_in_mysql(self, key: str) -> bool: """检查 MySQL 中是否已存在配置。""" return self._get_from_mysql(key) is not None _config_store = None def get_config_store() -> ConfigStore: """获取 ConfigStore 单例。""" global _config_store if _config_store is None: _config_store = ConfigStore() return _config_store