123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- import os, yaml, datetime
- from typing import Any, List, Callable
- from watchdog.observers import Observer
- from watchdog.events import FileSystemEventHandler
- from .config import Config
- 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 = []
- # 环境变量映射配置
- ENV_MAPPING = {
- 'APP_NAME': 'app.name',
- 'APP_VERSION': 'app.version',
- 'APP_DEBUG': 'app.debug',
- 'DB_HOST': 'database.host',
- 'DB_PORT': 'database.port',
- 'DB_USER': 'database.user',
- 'DB_PASSWORD': 'database.password',
- 'DB_NAME': 'database.name',
- }
- 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 self.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] = []
- # 1. 加载默认值(通过Config类初始化)
- # 2. 加载通用配置
- self._config.update_from_dict(
- self._load_yaml_config('SERVER/app/application.yml'))
- # 3. 加载指定环境配置
- env = os.getenv('ENV', 'dev')
- self._config.update_from_dict(
- self._load_yaml_config(f'SERVER/app/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()
- observer.schedule(event_handler, path='SERVER/app/', recursive=False)
- observer.start()
- self._observers.append(observer)
- def reload_config(self):
- """重新加载配置"""
- self._load_config()
- 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()
|