fix Config reload

This commit is contained in:
jxxghp
2025-06-03 23:08:58 +08:00
parent b4ed2880f7
commit bbfd8ca3f5
22 changed files with 338 additions and 280 deletions

View File

@@ -4,115 +4,17 @@ import os
import secrets
import sys
import threading
from collections import defaultdict
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Type, Callable
from typing import Any, Dict, List, Optional, Tuple, Type
from dotenv import set_key
from pydantic import BaseModel, BaseSettings, validator, Field
from app.log import logger, log_settings, LogConfigModel
from app.utils.object import ObjectUtils
from app.utils.system import SystemUtils
from app.utils.url import UrlUtils
class ConfigChangeType(Enum):
"""
配置变更类型
"""
ADD = "add"
UPDATE = "update"
DELETE = "delete"
class ConfigChangeEvent:
"""
配置变更事件
"""
def __init__(self, key: str, old_value: Any, new_value: Any,
change_type: ConfigChangeType = ConfigChangeType.UPDATE):
self.key = key
self.old_value = old_value
self.new_value = new_value
self.change_type = change_type
self.timestamp = threading.Event()
class ConfigObserver:
"""
配置观察者接口
"""
def on_config_changed(self, event: ConfigChangeEvent):
"""
配置变更回调
"""
pass
class ConfigNotifier:
"""
配置变更通知器
"""
def __init__(self):
self._observers: Dict[str, List[ConfigObserver]] = defaultdict(list)
self._global_observers: List[ConfigObserver] = []
self._lock = threading.RLock()
def add_observer(self, observer: ConfigObserver, config_keys: Optional[List[str]] = None):
"""
添加观察者
:param observer: 观察者对象
:param config_keys: 监听的配置键列表为None时监听所有配置变更
"""
with self._lock:
if config_keys is None:
self._global_observers.append(observer)
else:
for key in config_keys:
self._observers[key].append(observer)
def remove_observer(self, observer: ConfigObserver, config_keys: Optional[List[str]] = None):
"""
移除观察者
:param observer: 观察者对象
:param config_keys: 监听的配置键列表为None时移除全局观察者
"""
with self._lock:
if config_keys is None:
if observer in self._global_observers:
self._global_observers.remove(observer)
else:
for key in config_keys:
if observer in self._observers[key]:
self._observers[key].remove(observer)
def notify(self, event: ConfigChangeEvent):
"""
通知观察者配置变更
"""
with self._lock:
# 通知全局观察者
for observer in self._global_observers:
try:
observer.on_config_changed(event)
except Exception as e:
logger.error(f"配置观察者 {observer} 处理配置变更时出错: {e}")
# 通知特定配置键的观察者
for observer in self._observers.get(event.key, []):
try:
observer.on_config_changed(event)
except Exception as e:
logger.error(f"配置观察者 {observer} 处理配置变更 {event.key} 时出错: {e}")
# 全局配置通知器
config_notifier = ConfigNotifier()
from app.schemas.types import EventType
from app.schemas import ConfigChangeEventData
class ConfigModel(BaseModel):
@@ -558,8 +460,13 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
if hasattr(log_settings, key):
setattr(log_settings, key, converted_value)
# 发送配置变更通知
event = ConfigChangeEvent(key, old_value, converted_value)
config_notifier.notify(event)
from app.core.event import eventmanager
eventmanager.send_event(etype=EventType.ConfigChanged, data=ConfigChangeEventData(
key=key,
old_value=old_value,
new_value=converted_value,
change_type="update"
))
return success, message
return True, ""
@@ -791,91 +698,3 @@ class GlobalVar(object):
# 全局标识
global_vars = GlobalVar()
class HotReloadManager(ConfigObserver):
"""
配置热更新管理器
"""
def __init__(self):
self._reload_handlers: Dict[str, Callable] = {}
# 注册为全局配置观察者
config_notifier.add_observer(self)
def register_handler(self, config_keys: List[str], handler: Callable[[Any, Any], None]):
"""
注册配置变更处理器
:param config_keys: 配置键列表
:param handler: 处理函数,接收 (old_value, new_value) 参数
"""
for key in config_keys:
self._reload_handlers[key] = handler
@staticmethod
def __get_callable(name: str):
"""
根据类名获取类实例,首先检查全局变量中是否存在该类,如果不存在则尝试动态导入模块。
:param name: 方法名/类名.方法名
:return: 类的实例
"""
# 检查类是否在全局变量中
if name in globals():
try:
class_obj = globals()[name]()
return class_obj
except Exception as e:
logger.error(str(e))
return None
# TODO 如果类不在全局变量中,尝试动态导入模块并创建实例
return None
def on_config_changed(self, event: ConfigChangeEvent):
"""
处理配置变更事件
"""
if event.key in self._reload_handlers:
try:
handler = self._reload_handlers[event.key]
# 可执行函数
func = self.__get_callable(handler.__qualname__)
# 参数数量
args_num = ObjectUtils.arguments(func)
if args_num < 2:
func()
else:
func(event.old_value, event.new_value)
logger.info(f"配置 {event.key} 热更新成功:{func}")
except Exception as e:
logger.error(f"配置 {event.key} 热更新失败: {e}")
# 初始化热更新管理器
hot_reload_manager = HotReloadManager()
def on_config_change(config_keys: List[str]):
"""
装饰器:用于注册配置变更处理函数
使用示例:
@on_config_change(['PROXY_HOST', 'TMDB_API_KEY'])
def handle_config_change(old_value, new_value):
pass
"""
def decorator(func: Callable[[Any, Any], None]):
hot_reload_manager.register_handler(config_keys, func)
return func
return decorator
@on_config_change(['DEBUG', 'LOG_LEVEL'])
def handle_logger_change():
"""
默认的配置变更处理函数
"""
logger.update_loggers()

View File

@@ -10,7 +10,6 @@ from functools import lru_cache
from queue import Empty, PriorityQueue
from typing import Callable, Dict, List, Optional, Union
from app.helper.message import MessageHelper
from app.helper.thread import ThreadHelper
from app.log import logger
from app.schemas import ChainEventData
@@ -75,7 +74,6 @@ class EventManager(metaclass=Singleton):
__event = threading.Event()
def __init__(self):
self.__messagehelper = MessageHelper()
self.__executor = ThreadHelper() # 动态线程池,用于消费事件
self.__consumer_threads = [] # 用于保存启动的事件消费者线程
self.__event_queue = PriorityQueue() # 优先级队列
@@ -140,11 +138,12 @@ class EventManager(metaclass=Singleton):
"""
event = Event(etype, data, priority)
if isinstance(etype, EventType):
self.__trigger_broadcast_event(event)
return self.__trigger_broadcast_event(event)
elif isinstance(etype, ChainEventType):
return self.__trigger_chain_event(event)
else:
logger.error(f"Unknown event type: {etype}")
return None
def add_event_listener(self, event_type: Union[EventType, ChainEventType], handler: Callable,
priority: Optional[int] = DEFAULT_EVENT_PRIORITY):
@@ -293,7 +292,7 @@ class EventManager(metaclass=Singleton):
# 对于类实例(实现了 __call__ 方法)
if not inspect.isfunction(handler) and hasattr(handler, "__call__"):
handler_cls = handler.__class__ # noqa
handler_cls = handler.__class__ # noqa
return cls.__get_handler_identifier(handler_cls)
# 对于未绑定方法、静态方法、类方法,使用 __qualname__ 提取类信息
@@ -303,6 +302,7 @@ class EventManager(metaclass=Singleton):
module = inspect.getmodule(handler)
module_name = module.__name__ if module else "unknown_module"
return f"{module_name}.{class_name}"
return None
def __is_handler_enabled(self, handler: Callable) -> bool:
"""
@@ -398,16 +398,28 @@ class EventManager(metaclass=Singleton):
try:
from app.core.plugin import PluginManager
from app.core.module import ModuleManager
if class_name in PluginManager().get_plugin_ids():
# 定义一个插件调用函数
def plugin_callable():
"""
插件调用函数
"""
PluginManager().run_plugin_method(class_name, method_name, event_to_process)
if is_broadcast_event:
self.__executor.submit(plugin_callable)
else:
plugin_callable()
elif class_name in ModuleManager().get_module_ids():
module = ModuleManager().get_running_module(class_name)
if module:
method = getattr(module, method_name, None)
if method:
if is_broadcast_event:
self.__executor.submit(method, event_to_process)
else:
method(event_to_process)
else:
# 获取全局对象或模块类的实例
class_obj = self.__get_class_instance(class_name)
@@ -441,11 +453,20 @@ class EventManager(metaclass=Singleton):
if class_name == "Command":
module_name = "app.command"
module = importlib.import_module(module_name)
elif class_name == "Monitor":
module_name = "app.monitor"
module = importlib.import_module(module_name)
elif class_name == "Scheduler":
module_name = "app.scheduler"
module = importlib.import_module(module_name)
elif class_name == "PluginManager":
module_name = "app.core.plugin"
module = importlib.import_module(module_name)
elif class_name.endswith("Chain"):
module_name = f"app.chain.{class_name[:-5].lower()}"
module = importlib.import_module(module_name)
else:
logger.debug(f"事件处理出错:无效的 Chain 类名: {class_name},类名必须以 'Chain' 结尾")
logger.debug(f"事件处理出错:不支持的类名: {class_name}")
return None
if hasattr(module, class_name):
class_obj = getattr(module, class_name)()
@@ -491,9 +512,11 @@ class EventManager(metaclass=Singleton):
names = handler.__qualname__.split(".")
class_name, method_name = names[0], names[1]
self.__messagehelper.put(title=f"{event.event_type} 事件处理出错",
message=f"{class_name}.{method_name}{str(e)}",
role="system")
# 发送系统错误通知
from app.helper.message import MessageHelper
MessageHelper().put(title=f"{event.event_type} 事件处理出错",
message=f"{class_name}.{method_name}{str(e)}",
role="system")
self.send_event(
EventType.SystemError,
{

View File

@@ -1,5 +1,5 @@
import traceback
from typing import Generator, Optional, Tuple, Any, Union
from typing import Generator, Optional, Tuple, Any, Union, List
from app.core.config import settings
from app.core.event import eventmanager
@@ -164,3 +164,9 @@ class ModuleManager(metaclass=Singleton):
获取模块列表
"""
return self._modules
def get_module_ids(self) -> List[str]:
"""
获取模块id列表
"""
return list(self._modules.keys())

View File

@@ -15,8 +15,8 @@ from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from app import schemas
from app.core.config import settings, on_config_change
from app.core.event import eventmanager
from app.core.config import settings
from app.core.event import eventmanager, Event
from app.db.plugindata_oper import PluginDataOper
from app.db.systemconfig_oper import SystemConfigOper
from app.helper.module import ModuleHelper
@@ -241,11 +241,17 @@ class PluginManager(metaclass=Singleton):
"""
return self._plugins
@on_config_change(['PLUGIN_AUTO_RELOAD', 'DEV'])
def handle_config_change(self):
@eventmanager.register(EventType.ConfigChanged)
def handle_config_changed(self, event: Event):
"""
处理配置变更事件,重新加载插件监测
处理配置变更事件
:param event: 事件对象
"""
if not event:
return
event_data: schemas.ConfigChangeEventData = event.event_data
if event_data.key not in ['DEV', 'PLUGIN_AUTO_RELOAD']:
return
self.reload_monitor()
def reload_monitor(self):