feat(storage): 引入插件存储延迟写入与退出时强制保存机制
对插件本地存储API进行了重要优化,引入了延迟写入(de-bouncing)机制,以减少频繁的磁盘I/O操作,提升性能。现在,对存储的修改会在一个短暂的延迟后批量写入,而不是每次操作都立即写入。 此外,增加了程序退出时的钩子(atexit hook),确保在主程序关闭前,所有插件缓存中未保存的数据都会被强制写入磁盘,防止数据丢失。 同时,此提交包含了一些小的修复: - 修复了 `cross_context_api` 在私聊场景下 `user_info` 为空时可能出现的逻辑问题。 - 清理了 `plugin_base` 中不必要的 `ClassVar` 类型提示。
This commit is contained in:
@@ -27,6 +27,8 @@ async def get_context_group(chat_id: str) -> ContextGroup | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
is_group = current_stream.group_info is not None
|
is_group = current_stream.group_info is not None
|
||||||
|
if not is_group and not current_stream.user_info:
|
||||||
|
return None
|
||||||
if is_group:
|
if is_group:
|
||||||
assert current_stream.group_info is not None
|
assert current_stream.group_info is not None
|
||||||
current_chat_raw_id = current_stream.group_info.group_id
|
current_chat_raw_id = current_stream.group_info.group_id
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@Desc : 提供给插件使用的本地存储API(集成版)
|
@Desc : 提供给插件使用的本地存储API(集成版)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import atexit
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
@@ -43,6 +44,20 @@ class PluginStorageManager:
|
|||||||
logger.debug(f"从缓存中获取插件 '{name}' 的本地存储实例。")
|
logger.debug(f"从缓存中获取插件 '{name}' 的本地存储实例。")
|
||||||
return cls._instances[name]
|
return cls._instances[name]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def shutdown(cls):
|
||||||
|
"""
|
||||||
|
在程序退出时,强制保存所有插件实例中未保存的数据。
|
||||||
|
哼,别想留下任何烂摊子给我。
|
||||||
|
"""
|
||||||
|
logger.info("正在执行存储管理器关闭程序,检查并保存所有未写入的数据...")
|
||||||
|
with cls._lock:
|
||||||
|
for name, instance in cls._instances.items():
|
||||||
|
logger.debug(f"正在检查插件 '{name}' 的数据...")
|
||||||
|
# 直接调用实例的_save_data,它会检查_dirty标志
|
||||||
|
instance._save_data()
|
||||||
|
logger.info("所有插件数据均已妥善保存。")
|
||||||
|
|
||||||
|
|
||||||
# --- 单个存储实例部分 ---
|
# --- 单个存储实例部分 ---
|
||||||
|
|
||||||
@@ -60,6 +75,10 @@ class PluginStorage:
|
|||||||
self.file_path = os.path.join(base_path, f"{safe_filename}.json")
|
self.file_path = os.path.join(base_path, f"{safe_filename}.json")
|
||||||
self._data: dict[str, Any] = {}
|
self._data: dict[str, Any] = {}
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
# --- 延迟写入新增属性 ---
|
||||||
|
self._dirty = False # 数据是否被修改过的标志
|
||||||
|
self._write_timer: threading.Timer | None = None # 延迟写入的计时器
|
||||||
|
self.save_delay = 5 # 延迟5秒写入
|
||||||
|
|
||||||
self._ensure_directory_exists()
|
self._ensure_directory_exists()
|
||||||
self._load_data()
|
self._load_data()
|
||||||
@@ -88,11 +107,27 @@ class PluginStorage:
|
|||||||
logger.warning(f"从 '{self.file_path}' 加载数据失败: {e},将初始化为空数据。")
|
logger.warning(f"从 '{self.file_path}' 加载数据失败: {e},将初始化为空数据。")
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
|
def _schedule_save(self) -> None:
|
||||||
|
"""安排一次延迟保存操作。"""
|
||||||
|
with self._lock:
|
||||||
|
self._dirty = True
|
||||||
|
# 如果已经有计时器在跑,就取消它,用新的覆盖
|
||||||
|
if self._write_timer:
|
||||||
|
self._write_timer.cancel()
|
||||||
|
self._write_timer = threading.Timer(self.save_delay, self._save_data)
|
||||||
|
self._write_timer.start()
|
||||||
|
logger.debug(f"插件 '{self.name}' 的数据修改已暂存,计划在 {self.save_delay} 秒后写入磁盘。")
|
||||||
|
|
||||||
def _save_data(self) -> None:
|
def _save_data(self) -> None:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
if not self._dirty:
|
||||||
|
return # 数据没有被修改,不需要保存
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.file_path, "w", encoding="utf-8") as f:
|
with open(self.file_path, "w", encoding="utf-8") as f:
|
||||||
json.dump(self._data, f, indent=4, ensure_ascii=False)
|
json.dump(self._data, f, indent=4, ensure_ascii=False)
|
||||||
|
self._dirty = False # 保存后重置标志
|
||||||
|
logger.debug(f"插件 '{self.name}' 的数据已成功保存到磁盘。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"向 '{self.file_path}' 保存数据时发生错误: {e}", exc_info=True)
|
logger.error(f"向 '{self.file_path}' 保存数据时发生错误: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
@@ -108,7 +143,7 @@ class PluginStorage:
|
|||||||
"""
|
"""
|
||||||
logger.debug(f"在 '{self.name}' 存储中设置值: key='{key}'。")
|
logger.debug(f"在 '{self.name}' 存储中设置值: key='{key}'。")
|
||||||
self._data[key] = value
|
self._data[key] = value
|
||||||
self._save_data()
|
self._schedule_save()
|
||||||
|
|
||||||
def add(self, key: str, value: Any) -> bool:
|
def add(self, key: str, value: Any) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -122,19 +157,19 @@ class PluginStorage:
|
|||||||
if key not in self._data:
|
if key not in self._data:
|
||||||
logger.debug(f"在 '{self.name}' 存储中新增值: key='{key}'。")
|
logger.debug(f"在 '{self.name}' 存储中新增值: key='{key}'。")
|
||||||
self._data[key] = value
|
self._data[key] = value
|
||||||
self._save_data()
|
self._schedule_save()
|
||||||
return True
|
return True
|
||||||
logger.warning(f"尝试为已存在的键 '{key}' 新增值,操作被忽略。")
|
logger.warning(f"尝试为已存在的键 '{key}' 新增值,操作被忽略。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update(self, data: dict[str, Any]) -> None:
|
def update(self, data: dict[str, Any]) -> None:
|
||||||
self._data.update(data)
|
self._data.update(data)
|
||||||
self._save_data()
|
self._schedule_save()
|
||||||
|
|
||||||
def delete(self, key: str) -> bool:
|
def delete(self, key: str) -> bool:
|
||||||
if key in self._data:
|
if key in self._data:
|
||||||
del self._data[key]
|
del self._data[key]
|
||||||
self._save_data()
|
self._schedule_save()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -144,12 +179,16 @@ class PluginStorage:
|
|||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
logger.warning(f"插件 '{self.name}' 的本地存储将被清空!")
|
logger.warning(f"插件 '{self.name}' 的本地存储将被清空!")
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self._save_data()
|
self._schedule_save()
|
||||||
|
|
||||||
|
|
||||||
# --- 对外暴露的API函数 ---
|
# --- 对外暴露的API函数 ---
|
||||||
|
|
||||||
|
|
||||||
|
# 注册退出时的清理函数
|
||||||
|
atexit.register(PluginStorageManager.shutdown)
|
||||||
|
|
||||||
|
|
||||||
def get_local_storage(name: str) -> "PluginStorage":
|
def get_local_storage(name: str) -> "PluginStorage":
|
||||||
"""
|
"""
|
||||||
获取一个专属于插件的本地存储实例。
|
获取一个专属于插件的本地存储实例。
|
||||||
|
|||||||
@@ -206,12 +206,12 @@ class PluginBase(ABC):
|
|||||||
if not self.config_schema:
|
if not self.config_schema:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
config_data: ClassVar = {}
|
config_data = {}
|
||||||
|
|
||||||
# 遍历每个配置节
|
# 遍历每个配置节
|
||||||
for section, fields in self.config_schema.items():
|
for section, fields in self.config_schema.items():
|
||||||
if isinstance(fields, dict):
|
if isinstance(fields, dict):
|
||||||
section_data: ClassVar = {}
|
section_data = {}
|
||||||
|
|
||||||
# 遍历节内的字段
|
# 遍历节内的字段
|
||||||
for field_name, field in fields.items():
|
for field_name, field in fields.items():
|
||||||
|
|||||||
Reference in New Issue
Block a user