import os, yaml, datetime from typing import Any, List, Callable from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from dotenv import load_dotenv from .config import Config from .config_env_mapping import ENV_MAPPING class ConfigReloadHandler(FileSystemEventHandler): """配置文件变更处理器""" def __init__(self, callback: Callable): self.callback = callback def on_modified(self, event): if not event.is_directory: self.callback() class ConfigLoader: """配置加载器""" _instance = None _observers = [] def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._load_config() return cls._instance def _load_env_vars(self, config: Config) -> Config: """加载环境变量配置""" for env_key, config_path in ENV_MAPPING.items(): value = os.getenv(env_key) if value is None: continue try: # 按路径分割 keys = config_path.split('.') current = config # 遍历配置路径 for key in keys[:-1]: if not hasattr(current, key): setattr(current, key, Config()) current = getattr(current, key) # 获取目标字段类型 last_key = keys[-1] target_type = type(getattr(current, last_key, str)) # 根据目标类型转换值 if target_type == bool: # 支持多种布尔值表示 value_lower = value.lower() if value_lower in ('true', 'yes', 'y', '1'): setattr(current, last_key, True) elif value_lower in ('false', 'no', 'n', '0'): setattr(current, last_key, False) else: raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的布尔值") elif target_type == int: # 支持数字字符串 try: setattr(current, last_key, int(float(value))) except (ValueError, TypeError): raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的整数") elif target_type == float: # 支持浮点数字符串 try: setattr(current, last_key, float(value)) except (ValueError, TypeError): raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的浮点数") elif target_type == list: # 支持数组类型,格式:value1,value2,value3 try: setattr(current, last_key, [item.strip() for item in value.split(',')]) except Exception: raise ValueError( f"环境变量 {env_key} 的值 {value} 不是有效的数组格式") elif target_type == set: # 支持集合类型,格式:value1,value2,value3 try: setattr(current, last_key, set(item.strip() for item in value.split(','))) except Exception: raise ValueError( f"环境变量 {env_key} 的值 {value} 不是有效的集合格式") elif target_type == dict: # 支持字典类型,格式:key1:value1,key2:value2 try: setattr( current, last_key, dict(item.split(':') for item in value.split(','))) except Exception: raise ValueError( f"环境变量 {env_key} 的值 {value} 不是有效的字典格式") elif target_type == datetime.datetime: # 支持时间类型,格式:YYYY-MM-DD HH:MM:SS try: setattr( current, last_key, datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')) except Exception: raise ValueError( f"环境变量 {env_key} 的值 {value} 不是有效的时间格式") else: setattr(current, last_key, value) except Exception as e: print(f"[ERROR] 加载环境变量 {env_key} 时出错: {str(e)}") continue return config def _load_yaml_config(self, file_path: str) -> dict: """加载YAML配置文件""" try: if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) or {} if not isinstance(config, dict): raise ValueError(f"配置文件 {file_path} 格式错误:应为字典格式") return config else: print(f"配置文件 {file_path} 不存在") return {} except yaml.YAMLError as e: print(f"YAML解析错误:{str(e)}") return {} except Exception as e: print(f"加载配置文件 {file_path} 时发生错误:{str(e)}") return {} def _load_config(self): """加载配置""" # 初始化配置实例 self._config = Config() self._callbacks: List[Callable] = [] # 获取当前文件所在目录并保存到类变量 self._current_dir = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) load_dotenv(os.path.join(self._current_dir, '.env')) # print(f"配置当前目录为:{self._current_dir}") # 1. 加载默认值(通过Config类初始化) # 2. 加载通用配置 self._config.update_from_dict( self._load_yaml_config( os.path.join(self._current_dir, 'application.yml'))) # 3. 加载指定环境配置 env = os.getenv('ENV') print(f"当前环境:{env}") if env: self._config.update_from_dict( self._load_yaml_config( os.path.join(self._current_dir, f'application-{env}.yml'))) # 4. 最后加载环境变量(最高优先级) self._config = self._load_env_vars(self._config) # 启动配置监控 self._start_config_watcher() def _start_config_watcher(self): """启动配置文件监控""" event_handler = ConfigReloadHandler(self.reload_config) observer = Observer() # 只监控application.yml和application-{env}.yml文件 env = os.getenv('ENV', 'dev') config_files = [ os.path.join(self._current_dir, 'application.yml'), os.path.join(self._current_dir, f'application-{env}.yml') ] for config_file in config_files: if os.path.exists(config_file): observer.schedule(event_handler, path=os.path.dirname(config_file), recursive=False) observer.start() self._observers.append(observer) def reload_config(self): """重新加载配置""" # 1. 重新加载配置文件 self._load_config() # 2. 重新加载环境变量(确保覆盖配置文件) self._config = self._load_env_vars(self._config) # 3. 触发回调 for callback in self._callbacks: callback() def get(self, key: str, default=None) -> Any: """获取配置值""" keys = key.split('.') current = self._config try: for k in keys: current = getattr(current, k) return current except (KeyError, AttributeError): return default def on_config_change(self, callback: Callable): """注册配置变更回调""" self._callbacks.append(callback) def __del__(self): """清理资源""" for observer in self._observers: observer.stop() observer.join()