loader_config.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import os, yaml, datetime
  2. from typing import Any, List, Callable
  3. from watchdog.observers import Observer
  4. from watchdog.events import FileSystemEventHandler
  5. from dotenv import load_dotenv
  6. from .config import Config
  7. from .config_env_mapping import ENV_MAPPING
  8. class ConfigReloadHandler(FileSystemEventHandler):
  9. """配置文件变更处理器"""
  10. def __init__(self, callback: Callable):
  11. self.callback = callback
  12. def on_modified(self, event):
  13. if not event.is_directory:
  14. self.callback()
  15. class ConfigLoader:
  16. """配置加载器"""
  17. _instance = None
  18. _observers = []
  19. def __new__(cls):
  20. if cls._instance is None:
  21. cls._instance = super().__new__(cls)
  22. cls._instance._load_config()
  23. return cls._instance
  24. def _load_env_vars(self, config: Config) -> Config:
  25. """加载环境变量配置"""
  26. for env_key, config_path in ENV_MAPPING.items():
  27. value = os.getenv(env_key)
  28. if value is None:
  29. continue
  30. try:
  31. # 按路径分割
  32. keys = config_path.split('.')
  33. current = config
  34. # 遍历配置路径
  35. for key in keys[:-1]:
  36. if not hasattr(current, key):
  37. setattr(current, key, Config())
  38. current = getattr(current, key)
  39. # 获取目标字段类型
  40. last_key = keys[-1]
  41. target_type = type(getattr(current, last_key, str))
  42. # 根据目标类型转换值
  43. if target_type == bool:
  44. # 支持多种布尔值表示
  45. value_lower = value.lower()
  46. if value_lower in ('true', 'yes', 'y', '1'):
  47. setattr(current, last_key, True)
  48. elif value_lower in ('false', 'no', 'n', '0'):
  49. setattr(current, last_key, False)
  50. else:
  51. raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的布尔值")
  52. elif target_type == int:
  53. # 支持数字字符串
  54. try:
  55. setattr(current, last_key, int(float(value)))
  56. except (ValueError, TypeError):
  57. raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的整数")
  58. elif target_type == float:
  59. # 支持浮点数字符串
  60. try:
  61. setattr(current, last_key, float(value))
  62. except (ValueError, TypeError):
  63. raise ValueError(f"环境变量 {env_key} 的值 {value} 不是有效的浮点数")
  64. elif target_type == list:
  65. # 支持数组类型,格式:value1,value2,value3
  66. try:
  67. setattr(current, last_key,
  68. [item.strip() for item in value.split(',')])
  69. except Exception:
  70. raise ValueError(
  71. f"环境变量 {env_key} 的值 {value} 不是有效的数组格式")
  72. elif target_type == set:
  73. # 支持集合类型,格式:value1,value2,value3
  74. try:
  75. setattr(current, last_key,
  76. set(item.strip() for item in value.split(',')))
  77. except Exception:
  78. raise ValueError(
  79. f"环境变量 {env_key} 的值 {value} 不是有效的集合格式")
  80. elif target_type == dict:
  81. # 支持字典类型,格式:key1:value1,key2:value2
  82. try:
  83. setattr(
  84. current, last_key,
  85. dict(item.split(':') for item in value.split(',')))
  86. except Exception:
  87. raise ValueError(
  88. f"环境变量 {env_key} 的值 {value} 不是有效的字典格式")
  89. elif target_type == datetime.datetime:
  90. # 支持时间类型,格式:YYYY-MM-DD HH:MM:SS
  91. try:
  92. setattr(
  93. current, last_key,
  94. datetime.datetime.strptime(value,
  95. '%Y-%m-%d %H:%M:%S'))
  96. except Exception:
  97. raise ValueError(
  98. f"环境变量 {env_key} 的值 {value} 不是有效的时间格式")
  99. else:
  100. setattr(current, last_key, value)
  101. except Exception as e:
  102. print(f"[ERROR] 加载环境变量 {env_key} 时出错: {str(e)}")
  103. continue
  104. return config
  105. def _load_yaml_config(self, file_path: str) -> dict:
  106. """加载YAML配置文件"""
  107. try:
  108. if os.path.exists(file_path):
  109. with open(file_path, 'r', encoding='utf-8') as f:
  110. config = yaml.safe_load(f) or {}
  111. if not isinstance(config, dict):
  112. raise ValueError(f"配置文件 {file_path} 格式错误:应为字典格式")
  113. return config
  114. else:
  115. print(f"配置文件 {file_path} 不存在")
  116. return {}
  117. except yaml.YAMLError as e:
  118. print(f"YAML解析错误:{str(e)}")
  119. return {}
  120. except Exception as e:
  121. print(f"加载配置文件 {file_path} 时发生错误:{str(e)}")
  122. return {}
  123. def _load_config(self):
  124. """加载配置"""
  125. # 初始化配置实例
  126. self._config = Config()
  127. self._callbacks: List[Callable] = []
  128. # 获取当前文件所在目录并保存到类变量
  129. self._current_dir = os.path.dirname(
  130. os.path.dirname(os.path.abspath(__file__)))
  131. load_dotenv(os.path.join(self._current_dir, '.env'))
  132. # print(f"配置当前目录为:{self._current_dir}")
  133. # 1. 加载默认值(通过Config类初始化)
  134. # 2. 加载通用配置
  135. self._config.update_from_dict(
  136. self._load_yaml_config(
  137. os.path.join(self._current_dir, 'application.yml')))
  138. # 3. 加载指定环境配置
  139. env = os.getenv('ENV')
  140. print(f"当前环境:{env}")
  141. if env:
  142. self._config.update_from_dict(
  143. self._load_yaml_config(
  144. os.path.join(self._current_dir, f'application-{env}.yml')))
  145. # 4. 最后加载环境变量(最高优先级)
  146. self._config = self._load_env_vars(self._config)
  147. # 启动配置监控
  148. self._start_config_watcher()
  149. def _start_config_watcher(self):
  150. """启动配置文件监控"""
  151. event_handler = ConfigReloadHandler(self.reload_config)
  152. observer = Observer()
  153. # 只监控application.yml和application-{env}.yml文件
  154. env = os.getenv('ENV', 'dev')
  155. config_files = [
  156. os.path.join(self._current_dir, 'application.yml'),
  157. os.path.join(self._current_dir, f'application-{env}.yml')
  158. ]
  159. for config_file in config_files:
  160. if os.path.exists(config_file):
  161. observer.schedule(event_handler,
  162. path=os.path.dirname(config_file),
  163. recursive=False)
  164. observer.start()
  165. self._observers.append(observer)
  166. def reload_config(self):
  167. """重新加载配置"""
  168. # 1. 重新加载配置文件
  169. self._load_config()
  170. # 2. 重新加载环境变量(确保覆盖配置文件)
  171. self._config = self._load_env_vars(self._config)
  172. # 3. 触发回调
  173. for callback in self._callbacks:
  174. callback()
  175. def get(self, key: str, default=None) -> Any:
  176. """获取配置值"""
  177. keys = key.split('.')
  178. current = self._config
  179. try:
  180. for k in keys:
  181. current = getattr(current, k)
  182. return current
  183. except (KeyError, AttributeError):
  184. return default
  185. def on_config_change(self, callback: Callable):
  186. """注册配置变更回调"""
  187. self._callbacks.append(callback)
  188. def __del__(self):
  189. """清理资源"""
  190. for observer in self._observers:
  191. observer.stop()
  192. observer.join()