mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 21:52:56 +08:00
224 lines
8.1 KiB
Python
224 lines
8.1 KiB
Python
|
|
from typing import Any, TypeVar, overload
|
|||
|
|
|
|||
|
|
from pydantic import BaseModel, ValidationError
|
|||
|
|
import ujson as json
|
|||
|
|
|
|||
|
|
from zhenxun.configs.config import Config
|
|||
|
|
from zhenxun.models.group_plugin_setting import GroupPluginSetting
|
|||
|
|
from zhenxun.services.cache import Cache
|
|||
|
|
from zhenxun.services.data_access import DataAccess
|
|||
|
|
from zhenxun.services.log import logger
|
|||
|
|
from zhenxun.utils.pydantic_compat import model_dump, model_validate, parse_as
|
|||
|
|
|
|||
|
|
T = TypeVar("T", bound=BaseModel)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class GroupSettingsService:
|
|||
|
|
"""
|
|||
|
|
一个用于管理插件分群配置的服务。
|
|||
|
|
集成了聚合缓存、批量操作和版本迁移功能。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.dao = DataAccess(GroupPluginSetting)
|
|||
|
|
self._cache = Cache[dict]("group_plugin_settings")
|
|||
|
|
|
|||
|
|
async def set(
|
|||
|
|
self, group_id: str, plugin_name: str, settings_model: BaseModel
|
|||
|
|
) -> None:
|
|||
|
|
"""
|
|||
|
|
为一个插件在指定群组中设置完整的配置模型。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
group_id: 目标群组ID。
|
|||
|
|
plugin_name: 插件的模块名。
|
|||
|
|
settings_model: 包含完整配置的Pydantic模型实例。
|
|||
|
|
"""
|
|||
|
|
settings_dict = model_dump(settings_model)
|
|||
|
|
json_value = json.dumps(settings_dict, ensure_ascii=False)
|
|||
|
|
|
|||
|
|
await self.dao.update_or_create(
|
|||
|
|
defaults={"settings": json_value}, # type: ignore
|
|||
|
|
group_id=group_id,
|
|||
|
|
plugin_name=plugin_name,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
await self.dao.clear_cache(group_id=group_id, plugin_name=plugin_name)
|
|||
|
|
|
|||
|
|
async def set_key_value(
|
|||
|
|
self, group_id: str, plugin_name: str, key: str, value: Any
|
|||
|
|
) -> None:
|
|||
|
|
"""为一个插件在指定群组中设置单个配置项的值。"""
|
|||
|
|
setting_entry, _ = await GroupPluginSetting.get_or_create(
|
|||
|
|
defaults={"settings": {}},
|
|||
|
|
group_id=group_id,
|
|||
|
|
plugin_name=plugin_name,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not isinstance(setting_entry.settings, dict):
|
|||
|
|
setting_entry.settings = {}
|
|||
|
|
|
|||
|
|
setting_entry.settings[key] = value
|
|||
|
|
await setting_entry.save(update_fields=["settings"])
|
|||
|
|
await self.dao.clear_cache(group_id=group_id, plugin_name=plugin_name)
|
|||
|
|
|
|||
|
|
async def reset_key(self, group_id: str, plugin_name: str, key: str) -> bool:
|
|||
|
|
"""重置单个配置项"""
|
|||
|
|
setting = await self.dao.get_or_none(group_id=group_id, plugin_name=plugin_name)
|
|||
|
|
if setting and isinstance(setting.settings, dict) and key in setting.settings:
|
|||
|
|
del setting.settings[key]
|
|||
|
|
if not setting.settings:
|
|||
|
|
await setting.delete()
|
|||
|
|
else:
|
|||
|
|
await setting.save(update_fields=["settings"])
|
|||
|
|
await self.dao.clear_cache(group_id=group_id, plugin_name=plugin_name)
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
async def get(
|
|||
|
|
self, group_id: str, plugin_name: str, key: str, default: Any = None
|
|||
|
|
) -> Any:
|
|||
|
|
"""
|
|||
|
|
获取一个分群配置项的值,如果群组未单独设置,则回退到全局默认值。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
group_id: 目标群组ID。
|
|||
|
|
plugin_name: 插件的模块名。
|
|||
|
|
key: 配置项的键。
|
|||
|
|
default: 如果找不到配置项,返回的默认值。
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
配置项的值。
|
|||
|
|
"""
|
|||
|
|
full_settings = await self.get_all_for_plugin(group_id, plugin_name)
|
|||
|
|
return full_settings.get(key, default)
|
|||
|
|
|
|||
|
|
async def reset_all_for_plugin(self, group_id: str, plugin_name: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
重置一个插件在指定群组的配置,使其回退到全局默认值。
|
|||
|
|
这通过删除数据库中的对应记录来实现。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
group_id: 目标群组ID。
|
|||
|
|
plugin_name: 插件的模块名。
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
bool: 如果成功删除了一个条目,则返回 True,否则返回 False。
|
|||
|
|
"""
|
|||
|
|
deleted_count = await self.dao.delete(
|
|||
|
|
group_id=group_id, plugin_name=plugin_name
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if deleted_count > 0:
|
|||
|
|
await self.dao.clear_cache(group_id=group_id, plugin_name=plugin_name)
|
|||
|
|
logger.debug(f"已重置插件 '{plugin_name}' 在群组 '{group_id}' 的配置。")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
@overload
|
|||
|
|
async def get_all_for_plugin(
|
|||
|
|
self, group_id: str, plugin_name: str, *, parse_model: type[T]
|
|||
|
|
) -> T: ...
|
|||
|
|
|
|||
|
|
@overload
|
|||
|
|
async def get_all_for_plugin(
|
|||
|
|
self, group_id: str, plugin_name: str, *, parse_model: None = None
|
|||
|
|
) -> dict[str, Any]: ...
|
|||
|
|
|
|||
|
|
async def get_all_for_plugin(
|
|||
|
|
self, group_id: str, plugin_name: str, *, parse_model: type[T] | None = None
|
|||
|
|
) -> T | dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
获取一个插件在指定群组中的完整配置,应用了“继承与覆盖”逻辑。
|
|||
|
|
它首先获取全局默认配置,然后用数据库中存储的群组特定配置覆盖它。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
group_id: 目标群组ID。
|
|||
|
|
plugin_name: 插件的模块名。
|
|||
|
|
parse_model: (可选) Pydantic模型,用于解析和验证配置。
|
|||
|
|
"""
|
|||
|
|
cache_key = f"{group_id}:{plugin_name}"
|
|||
|
|
cached_settings = await self._cache.get(cache_key)
|
|||
|
|
if cached_settings is not None:
|
|||
|
|
logger.debug(f"缓存命中: {cache_key}")
|
|||
|
|
if parse_model:
|
|||
|
|
try:
|
|||
|
|
return parse_as(parse_model, cached_settings)
|
|||
|
|
except (ValidationError, TypeError) as e:
|
|||
|
|
logger.warning(
|
|||
|
|
f"缓存数据 '{cache_key}' 与模型 '{parse_model.__name__}' "
|
|||
|
|
f"不匹配: {e}。将从数据库重新加载。"
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
return cached_settings
|
|||
|
|
|
|||
|
|
logger.debug(f"缓存未命中: {cache_key},从数据库加载。")
|
|||
|
|
|
|||
|
|
global_config_group = Config.get(plugin_name)
|
|||
|
|
final_settings_dict = {
|
|||
|
|
key: global_config_group.get(key, build_model=False)
|
|||
|
|
for key in global_config_group.configs.keys()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
group_setting_entry = await self.dao.get_or_none(
|
|||
|
|
group_id=group_id, plugin_name=plugin_name
|
|||
|
|
)
|
|||
|
|
if group_setting_entry:
|
|||
|
|
try:
|
|||
|
|
group_specific_settings = group_setting_entry.settings
|
|||
|
|
if isinstance(group_specific_settings, dict):
|
|||
|
|
final_settings_dict.update(group_specific_settings)
|
|||
|
|
else:
|
|||
|
|
logger.warning(
|
|||
|
|
f"群组 {group_id} 插件 '{plugin_name}' 的配置格式不正确"
|
|||
|
|
f"(不是字典),已忽略。"
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.warning(
|
|||
|
|
f"加载群组 {group_id} 插件 '{plugin_name}' 的特定配置时出错: {e}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
await self._cache.set(cache_key, final_settings_dict)
|
|||
|
|
|
|||
|
|
if parse_model:
|
|||
|
|
try:
|
|||
|
|
return parse_as(parse_model, final_settings_dict)
|
|||
|
|
except (ValidationError, TypeError) as e:
|
|||
|
|
logger.warning(
|
|||
|
|
f"插件 '{plugin_name}' 的配置无法解析为 '{parse_model.__name__}'。"
|
|||
|
|
f"值: {final_settings_dict}, 错误: {e}。将返回一个默认模型实例。"
|
|||
|
|
)
|
|||
|
|
return parse_as(parse_model, {})
|
|||
|
|
|
|||
|
|
return final_settings_dict
|
|||
|
|
|
|||
|
|
async def set_bulk(
|
|||
|
|
self, group_ids: list[str], plugin_name: str, key: str, value: Any
|
|||
|
|
) -> tuple[int, int]:
|
|||
|
|
"""
|
|||
|
|
为多个群组批量设置同一个配置项。
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
group_ids: 目标群组ID列表。
|
|||
|
|
plugin_name: 插件模块名。
|
|||
|
|
key: 配置项的键。
|
|||
|
|
value: 要设置的值。
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
一个元组 (updated_count, created_count)。
|
|||
|
|
"""
|
|||
|
|
if not group_ids:
|
|||
|
|
return 0, 0
|
|||
|
|
|
|||
|
|
for group_id in group_ids:
|
|||
|
|
current_settings = await self.get_all_for_plugin(group_id, plugin_name)
|
|||
|
|
current_settings[key] = value
|
|||
|
|
await self.set(
|
|||
|
|
group_id, plugin_name, model_validate(BaseModel, current_settings)
|
|||
|
|
)
|
|||
|
|
return len(group_ids), 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
group_settings_service = GroupSettingsService()
|