123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- 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()
|