You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/app/config_store.py

153 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
统一配置存储模块
支持 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:
"""写入配置,始终写 JSONMySQL 可用时同时写数据库。"""
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