2025-07-08 17:45:40 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
统一配置管理器
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import logging
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Dict, Type, TypeVar, Optional
|
|
|
|
|
|
2025-07-09 11:51:49 +08:00
|
|
|
from core.config.models import (
|
|
|
|
|
BaseConfig, AIModelConfig, SystemConfig, GenerateTopicConfig, ResourceConfig,
|
|
|
|
|
GenerateContentConfig, PosterConfig, ContentConfig
|
|
|
|
|
)
|
2025-07-08 17:45:40 +08:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
T = TypeVar('T', bound=BaseConfig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigManager:
|
|
|
|
|
"""
|
|
|
|
|
统一配置管理器
|
|
|
|
|
负责加载、管理和访问所有配置
|
|
|
|
|
"""
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
def __init__(self):
|
|
|
|
|
self._configs: Dict[str, BaseConfig] = {}
|
|
|
|
|
self.config_dir: Optional[Path] = None
|
2025-07-09 11:51:49 +08:00
|
|
|
self.config_objects = {
|
|
|
|
|
'ai_model': AIModelConfig(),
|
|
|
|
|
'system': SystemConfig(),
|
|
|
|
|
'topic_gen': GenerateTopicConfig(),
|
|
|
|
|
'content_gen': GenerateContentConfig(),
|
|
|
|
|
'resource': ResourceConfig()
|
|
|
|
|
}
|
2025-07-08 17:45:40 +08:00
|
|
|
|
|
|
|
|
def load_from_directory(self, config_dir: str):
|
|
|
|
|
"""
|
|
|
|
|
从目录加载配置
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
Args:
|
|
|
|
|
config_dir: 配置文件目录
|
|
|
|
|
"""
|
|
|
|
|
self.config_dir = Path(config_dir)
|
2025-07-09 11:51:49 +08:00
|
|
|
if not self.config_dir.is_dir():
|
|
|
|
|
logger.error(f"配置目录不存在: {config_dir}")
|
|
|
|
|
raise FileNotFoundError(f"配置目录不存在: {config_dir}")
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-09 11:51:49 +08:00
|
|
|
# 注册所有已知的配置类型
|
2025-07-08 17:45:40 +08:00
|
|
|
self._register_configs()
|
|
|
|
|
|
2025-07-09 11:51:49 +08:00
|
|
|
# 动态加载目录中的所有.json文件
|
|
|
|
|
self._load_all_configs_from_dir()
|
2025-07-08 17:45:40 +08:00
|
|
|
|
|
|
|
|
def _register_configs(self):
|
|
|
|
|
"""注册所有配置"""
|
|
|
|
|
self.register_config('ai_model', AIModelConfig)
|
|
|
|
|
self.register_config('poster', PosterConfig)
|
|
|
|
|
self.register_config('content', ContentConfig)
|
|
|
|
|
self.register_config('resource', ResourceConfig)
|
|
|
|
|
self.register_config('system', SystemConfig)
|
|
|
|
|
self.register_config('topic_gen', GenerateTopicConfig)
|
2025-07-09 11:51:49 +08:00
|
|
|
self.register_config('content_gen', GenerateContentConfig)
|
2025-07-08 17:45:40 +08:00
|
|
|
|
|
|
|
|
def register_config(self, name: str, config_class: Type[T]):
|
|
|
|
|
"""
|
|
|
|
|
注册一个配置类
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name: 配置名称
|
|
|
|
|
config_class: 配置类 (必须是 BaseConfig 的子类)
|
|
|
|
|
"""
|
|
|
|
|
if not issubclass(config_class, BaseConfig):
|
|
|
|
|
raise TypeError("config_class must be a subclass of BaseConfig")
|
2025-07-09 11:51:49 +08:00
|
|
|
if name not in self._configs:
|
|
|
|
|
self._configs[name] = config_class()
|
2025-07-08 17:45:40 +08:00
|
|
|
|
|
|
|
|
def get_config(self, name: str, config_class: Type[T]) -> T:
|
|
|
|
|
"""
|
|
|
|
|
获取配置实例
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name: 配置名称
|
|
|
|
|
config_class: 配置类 (用于类型提示)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
配置实例
|
|
|
|
|
"""
|
|
|
|
|
config = self._configs.get(name)
|
|
|
|
|
if not isinstance(config, config_class):
|
2025-07-09 11:51:49 +08:00
|
|
|
# 如果配置不存在,先注册一个默认实例
|
|
|
|
|
if config is None:
|
|
|
|
|
self.register_config(name, config_class)
|
|
|
|
|
config = self._configs.get(name)
|
|
|
|
|
else:
|
|
|
|
|
raise TypeError(f"Configuration '{name}' is not of type '{config_class.__name__}'")
|
2025-07-08 17:45:40 +08:00
|
|
|
return config
|
|
|
|
|
|
2025-07-09 11:51:49 +08:00
|
|
|
def _load_all_configs_from_dir(self):
|
|
|
|
|
"""动态加载目录中的所有.json文件"""
|
2025-07-08 17:45:40 +08:00
|
|
|
try:
|
2025-07-09 11:51:49 +08:00
|
|
|
# 1. 优先加载旧的主配置文件以实现向后兼容
|
|
|
|
|
main_config_path = self.config_dir / 'poster_gen_config.json'
|
|
|
|
|
if main_config_path.exists():
|
|
|
|
|
self._load_main_config(main_config_path)
|
2025-07-08 17:45:40 +08:00
|
|
|
else:
|
2025-07-09 11:51:49 +08:00
|
|
|
logger.warning(f"旧的主配置文件不存在: {main_config_path}")
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-09 11:51:49 +08:00
|
|
|
# 2. 遍历并加载目录中所有其他的 .json 文件
|
|
|
|
|
for config_path in self.config_dir.glob('*.json'):
|
|
|
|
|
if config_path.name == 'poster_gen_config.json':
|
|
|
|
|
continue # 跳过已处理的主配置文件
|
|
|
|
|
|
|
|
|
|
config_name = config_path.stem # 'topic_gen.json' -> 'topic_gen'
|
|
|
|
|
if config_name in self._configs:
|
|
|
|
|
logger.info(f"加载配置文件 '{config_name}': {config_path}")
|
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
config_data = json.load(f)
|
|
|
|
|
self._configs[config_name].update(config_data)
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"在 '{config_path}' 中找到的配置 '{config_name}' 没有对应的已注册配置类型,已跳过。")
|
|
|
|
|
|
|
|
|
|
# 3. 最后应用环境变量覆盖
|
2025-07-08 17:45:40 +08:00
|
|
|
self._apply_env_overrides()
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
except Exception as e:
|
2025-07-09 11:51:49 +08:00
|
|
|
logger.error(f"从目录 '{self.config_dir}' 加载配置失败: {e}", exc_info=True)
|
2025-07-08 17:45:40 +08:00
|
|
|
raise
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
def _load_main_config(self, path: Path):
|
|
|
|
|
"""加载主配置文件,并分发到各个配置对象"""
|
|
|
|
|
logger.info(f"加载主配置文件: {path}")
|
|
|
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
|
|
|
config_data = json.load(f)
|
|
|
|
|
|
|
|
|
|
for name, config_obj in self._configs.items():
|
2025-07-09 11:51:49 +08:00
|
|
|
# 主配置文件可能是扁平结构或嵌套结构
|
|
|
|
|
if name in config_data: # 嵌套结构
|
2025-07-08 17:45:40 +08:00
|
|
|
config_obj.update(config_data[name])
|
2025-07-09 11:51:49 +08:00
|
|
|
else: # 尝试从根部更新 (扁平结构)
|
|
|
|
|
config_obj.update(config_data)
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
def _apply_env_overrides(self):
|
|
|
|
|
"""应用环境变量覆盖"""
|
|
|
|
|
logger.info("应用环境变量覆盖...")
|
|
|
|
|
# 示例: AI模型配置环境变量覆盖
|
|
|
|
|
ai_model_config = self.get_config('ai_model', AIModelConfig)
|
2025-07-09 11:51:49 +08:00
|
|
|
if not ai_model_config: return # 如果没有AI配置则跳过
|
|
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
env_mapping = {
|
|
|
|
|
'AI_MODEL': 'model',
|
|
|
|
|
'API_URL': 'api_url',
|
|
|
|
|
'API_KEY': 'api_key'
|
|
|
|
|
}
|
|
|
|
|
update_data = {}
|
|
|
|
|
for env_var, config_key in env_mapping.items():
|
|
|
|
|
if os.getenv(env_var):
|
|
|
|
|
update_data[config_key] = os.getenv(env_var)
|
|
|
|
|
|
|
|
|
|
if update_data:
|
|
|
|
|
ai_model_config.update(update_data)
|
2025-07-09 11:51:49 +08:00
|
|
|
logger.info(f"通过环境变量更新了AI模型配置: {list(update_data.keys())}")
|
2025-07-09 15:39:57 +08:00
|
|
|
|
2025-07-08 17:45:40 +08:00
|
|
|
def save_config(self, name: str):
|
|
|
|
|
"""
|
|
|
|
|
保存指定的配置到文件
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name: 要保存的配置名称
|
|
|
|
|
"""
|
2025-07-09 11:51:49 +08:00
|
|
|
if not self.config_dir:
|
|
|
|
|
raise ValueError("配置目录未设置,无法保存文件")
|
|
|
|
|
|
|
|
|
|
path = self.config_dir / f"{name}.json"
|
2025-07-08 17:45:40 +08:00
|
|
|
config_data = self.get_config(name, BaseConfig).to_dict()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
|
|
|
json.dump(config_data, f, indent=4, ensure_ascii=False)
|
|
|
|
|
logger.info(f"配置 '{name}' 已保存到 {path}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"保存配置 '{name}' 到 {path} 失败: {e}", exc_info=True)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 全局配置管理器实例
|
|
|
|
|
config_manager = ConfigManager()
|
|
|
|
|
|
|
|
|
|
def get_config_manager() -> ConfigManager:
|
|
|
|
|
return config_manager
|