mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-11 06:39:59 +08:00
🎉🍾♻️refactor(core): 重构核心鉴权与缓存机制,引入异步发送队列以提升性能 🚀 (#2088)
* 添加测试插件 * ✨ feat(auth): 添加缓存机制以优化用户和插件数据查询性能 * 🗑️ chore(help): 删除笨蛋检测插件代码 * ✨ feat(bot): 添加对扩展插件的加载支持 * ✨ feat(auth): 优化权限检查和缓存机制,增加用户和插件数据的并行查询 * ✨ feat(cache): 引入运行时缓存机制,优化用户和插件的ban记录管理 * ✨ feat(auth): 更新is_ban函数文档,添加参数和返回值说明 * ``` feat(auth): 使用内存缓存优化权限验证性能 - 移除数据库查询超时控制,改用 LevelUserMemoryCache、BotMemoryCache 和 PluginLimitMemoryCache 进行缓存查询 - 优化 auth_admin、auth_bot、auth_limit 权限验证逻辑,提升响应速度 - 添加 background 参数支持异步发送权限不足提示消息 - 移除 asyncio 依赖,简化代码结构 fix(auth): 修复限制通知频率控制问题 - 实现限制通知冷却机制,避免重复发送相同限制消息 - 添加 AUTH_LIMIT_NOTICE_CD 配置项,默认值为 2 秒 - 使用 FreqLimiter 控制限制通知发送频率 refactor(models): 增强模型数据变更时的缓存同步 - 在 BotConsole、GroupConsole、LevelUser、PluginLimit 模型的 create、update_or_create、save、delete 方法中自动更新对应缓存 - 确保数据库和内存缓存数据一致性 docs(ban_console): 修正文档注释并优化日志信息 - 修正 BanConsole 类中方法的文档字符串,使用标准参数和返回值格式 - 优化调试日志信息,使描述更加清晰准确 ``` * ✨ feat(auth): 更新Limit类以支持PluginLimitSnapshot,优化限制信息处理 * ✨ feat(bot_manage): 优化Bot控制台初始化逻辑,处理IntegrityError异常 * ✨ feat(mmm1): 新增消息推送功能,支持私聊和群聊事件处理 * ✨ feat(group_member_update): 优化群组成员更新逻辑,增加活动跟踪和消息记录功能 * ✨ feat(mmm1): 删除冗余的消息推送功能代码 * ✨ feat(auth): 优化权限检查逻辑,增加模块阻止功能和缓存处理 * ✨ feat(auth): 优化权限检查逻辑,增加快速ban检测和前置检查功能 * ✨ feat(chat_history): 增强消息处理规则,添加时间间隔限制以防止重复消息 ✨ feat(data_source): 引入异步获取群成员信息的功能,优化用户信息更新逻辑 * ✨ feat(send_queue): 添加异步发送队列以优化API调用和速率限制 * ✨ feat(auth): 添加缓存就绪检查以优化权限处理逻辑 * ✨ feat(plugins): 移除不必要的插件加载以简化插件管理 * ✨ feat(group_console): 优化群组获取逻辑,添加缓存检查以提升性能 ✨ 只接收缓存完成之后时间的消息 * ✨ feat(auth): 添加异步任务管理和超载检测,优化权限处理逻辑 ✨ feat(chat_history): 修改规则函数为异步,提升消息处理效率 ✨ feat(group_handle): 增加安全获取群组信息的异步方法,添加超时处理 ✨ feat(record_request): 引入安全获取群组信息的异步方法,优化群邀请处理 ✨ feat(ban_memory_cache): 增强禁言内存缓存,添加负缓存机制 ✨ feat(message_load): 新增消息负载检测功能,优化任务调度 ✨ feat(scheduler): 在调度器中集成消息压力检测,优化任务执行 ✨ feat(send_queue): 引入异步任务管理,优化发送队列处理 * ✨ feat(auth): 添加对 LevelUserSnapshot 和 BotSnapshot 的支持,优化权限检查逻辑 ✨ 格式化 * ✨ feat(db_context): 增强 get_or_create 方法,处理并发创建冲突并回退查询已存在记录 * ✨ feat(bot_manage): 增强 init_bot_console 方法,处理并发创建冲突并回退查询已存在的 bot 数据 * 🚨 auto fix by pre-commit hooks * ✨ feat(runtime_cache): 优化消息处理逻辑,支持 bytes 和 bytearray 类型的联合判断 * ✨ style(runtime_cache): 格式化代码,优化多行表达式的可读性 * 🚨 auto fix by pre-commit hooks * ✨ refactor: 优化类型注解,改进群组成员更新逻辑和平台处理 ✨ 格式化 * 🚨 auto fix by pre-commit hooks * ✨ refactor: 优化代码格式,增强可读性并修复类型注解 * 🚨 auto fix by pre-commit hooks * ✨ refactor: 更新文档注释,增强is_ban函数的可读性 * ✨ refactor: 优化代码结构,移除冗余函数,增强可读性并改进任务调度逻辑 * ✨ refactor: 调整定时任务时间,优化渲染服务的初始化逻辑,增强代码可读性 --------- Co-authored-by: ATTomatoo <1126160939@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
ATTomatoo
pre-commit-ci[bot]
parent
c9f0a8b9d9
commit
837330e30a
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
from nonebot import on_notice
|
||||
@@ -10,10 +11,12 @@ from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import Alconna, Arparma, on_alconna
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_uninfo import Scene, SceneType, get_interface
|
||||
|
||||
from zhenxun.configs.config import BotConfig
|
||||
from zhenxun.configs.utils import PluginExtraData
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.services.message_load import should_pause_tasks
|
||||
from zhenxun.services.tags import tag_manager
|
||||
from zhenxun.utils.enum import PluginType
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
@@ -38,6 +41,11 @@ __plugin_meta__ = PluginMetadata(
|
||||
).to_dict(),
|
||||
)
|
||||
|
||||
_FULL_REFRESH_INTERVAL_SECONDS = 24 * 60 * 60
|
||||
|
||||
_GROUP_LAST_UPDATE: dict[tuple[str, str], float] = {}
|
||||
_UPDATE_SEMAPHORE = asyncio.Semaphore(1)
|
||||
|
||||
|
||||
_matcher = on_alconna(
|
||||
Alconna("更新群组成员信息"),
|
||||
@@ -58,6 +66,34 @@ _update_all_matcher = on_alconna(
|
||||
)
|
||||
|
||||
|
||||
def _group_key(bot_id: str, group_id: str) -> tuple[str, str]:
|
||||
return bot_id, group_id
|
||||
|
||||
|
||||
async def _build_scene_map(bot: Bot) -> dict[str, Scene]:
|
||||
if not (interface := get_interface(bot)):
|
||||
return {}
|
||||
scenes = await interface.get_scenes(SceneType.GROUP)
|
||||
return {scene.id: scene for scene in scenes if scene.is_group}
|
||||
|
||||
|
||||
async def _run_update(
|
||||
bot: Bot,
|
||||
group_id: str,
|
||||
*,
|
||||
scene_map: dict[str, Scene] | None = None,
|
||||
platform: str | None = None,
|
||||
force: bool = False,
|
||||
) -> str | None:
|
||||
key = _group_key(bot.self_id, group_id)
|
||||
async with _UPDATE_SEMAPHORE:
|
||||
result = await MemberUpdateManage.update_group_member(
|
||||
bot, group_id, scene_map=scene_map, platform=platform
|
||||
)
|
||||
_GROUP_LAST_UPDATE[key] = time.time()
|
||||
return result
|
||||
|
||||
|
||||
async def _update_all_groups_task(bot: Bot, session: EventSession):
|
||||
"""
|
||||
在后台执行所有群组的更新任务,并向超级用户发送最终报告。
|
||||
@@ -69,21 +105,29 @@ async def _update_all_groups_task(bot: Bot, session: EventSession):
|
||||
|
||||
logger.info(f"Bot {bot_id}: 开始执行所有群组信息更新任务...", "更新所有群组")
|
||||
try:
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
total_count = len(group_list)
|
||||
for i, group in enumerate(group_list):
|
||||
scene_map = await _build_scene_map(bot)
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
group_ids = list(scene_map.keys())
|
||||
total_count = len(group_ids)
|
||||
for i, group_id in enumerate(group_ids):
|
||||
try:
|
||||
logger.debug(
|
||||
f"Bot {bot_id}: 正在更新第 {i + 1}/{total_count} 个群组: "
|
||||
f"{group.group_id}",
|
||||
f"{group_id}",
|
||||
"更新所有群组",
|
||||
)
|
||||
await MemberUpdateManage.update_group_member(bot, group.group_id)
|
||||
await _run_update(
|
||||
bot,
|
||||
group_id,
|
||||
scene_map=scene_map,
|
||||
platform=platform,
|
||||
force=True,
|
||||
)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
fail_count += 1
|
||||
logger.error(
|
||||
f"Bot {bot_id}: 更新群组 {group.group_id} 信息失败",
|
||||
f"Bot {bot_id}: 更新群组 {group_id} 信息失败",
|
||||
"更新所有群组",
|
||||
e=e,
|
||||
)
|
||||
@@ -118,18 +162,19 @@ async def _(bot: Bot, session: EventSession):
|
||||
|
||||
@_matcher.handle()
|
||||
async def _(bot: Bot, session: EventSession, arparma: Arparma):
|
||||
if gid := session.id3 or session.id2:
|
||||
logger.info("更新群组成员信息", arparma.header_result, session=session)
|
||||
result = await MemberUpdateManage.update_group_member(bot, gid)
|
||||
await MessageUtils.build_message(result).finish(reply_to=True)
|
||||
await tag_manager._invalidate_cache()
|
||||
await MessageUtils.build_message("群组id为空...").send()
|
||||
if not (gid := session.id3 or session.id2):
|
||||
await MessageUtils.build_message("群组id为空...").send()
|
||||
return
|
||||
logger.info("更新群组成员信息", arparma.header_result, session=session)
|
||||
result = await _run_update(bot, gid, force=True)
|
||||
await MessageUtils.build_message(result or "更新已完成").finish(reply_to=True)
|
||||
await tag_manager._invalidate_cache()
|
||||
|
||||
|
||||
@_notice.handle()
|
||||
async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
||||
if str(event.user_id) == bot.self_id:
|
||||
await MemberUpdateManage.update_group_member(bot, str(event.group_id))
|
||||
await _run_update(bot, str(event.group_id), force=True)
|
||||
logger.info(
|
||||
f"{BotConfig.self_nickname}加入群聊更新群组信息",
|
||||
"更新群组成员列表",
|
||||
@@ -140,29 +185,50 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent):
|
||||
|
||||
|
||||
@scheduler.scheduled_job(
|
||||
"interval",
|
||||
minutes=5,
|
||||
"cron",
|
||||
hour=3,
|
||||
minute=0,
|
||||
max_instances=1,
|
||||
coalesce=True,
|
||||
)
|
||||
async def _():
|
||||
for bot in nonebot.get_bots().values():
|
||||
if PlatformUtils.get_platform(bot) == "qq":
|
||||
try:
|
||||
group_list, _ = await PlatformUtils.get_group_list(bot)
|
||||
if group_list:
|
||||
for group in group_list:
|
||||
try:
|
||||
await MemberUpdateManage.update_group_member(
|
||||
bot, group.group_id
|
||||
)
|
||||
logger.debug("自动更新群组成员信息成功...")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Bot: {bot.self_id} 自动更新群组成员信息失败",
|
||||
target=group.group_id,
|
||||
e=e,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e)
|
||||
logger.debug(f"自动 Bot: {bot.self_id} 更新群组成员信息成功...")
|
||||
|
||||
await tag_manager._invalidate_cache()
|
||||
async def _nightly_full_refresh():
|
||||
if should_pause_tasks():
|
||||
return
|
||||
now = time.time()
|
||||
bots = nonebot.get_bots()
|
||||
if not bots:
|
||||
return
|
||||
updated = 0
|
||||
for bot in bots.values():
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
if platform != "qq":
|
||||
continue
|
||||
try:
|
||||
scene_map = await _build_scene_map(bot)
|
||||
if not scene_map:
|
||||
continue
|
||||
for group_id in scene_map:
|
||||
key = _group_key(bot.self_id, group_id)
|
||||
last_update = _GROUP_LAST_UPDATE.get(key, 0)
|
||||
if now - last_update < _FULL_REFRESH_INTERVAL_SECONDS:
|
||||
continue
|
||||
try:
|
||||
result = await _run_update(
|
||||
bot,
|
||||
group_id,
|
||||
scene_map=scene_map,
|
||||
platform=platform,
|
||||
force=True,
|
||||
)
|
||||
if result is not None:
|
||||
updated += 1
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Bot: {bot.self_id} 夜间更新群组成员信息失败",
|
||||
target=group_id,
|
||||
e=e,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Bot: {bot.self_id} 夜间更新群组信息", e=e)
|
||||
if updated:
|
||||
await tag_manager._invalidate_cache()
|
||||
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot_plugin_uninfo import Member, SceneType, get_interface
|
||||
from nonebot_plugin_uninfo import Member, Scene, SceneType, get_interface
|
||||
|
||||
from zhenxun.configs.config import Config
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
@@ -18,10 +18,13 @@ class MemberUpdateManage:
|
||||
async def __handle_user(
|
||||
cls,
|
||||
member: Member,
|
||||
db_user: list[GroupInfoUser],
|
||||
db_user_map: dict[str, list[GroupInfoUser]],
|
||||
group_id: str,
|
||||
data_list: tuple[list, list, list],
|
||||
data_list: tuple[list[GroupInfoUser], list[GroupInfoUser], list[int]],
|
||||
platform: str | None,
|
||||
*,
|
||||
default_auth: int | None,
|
||||
superusers: set[str],
|
||||
):
|
||||
"""单个成员操作
|
||||
|
||||
@@ -32,37 +35,32 @@ class MemberUpdateManage:
|
||||
data_list: 数据列表
|
||||
platform: 平台
|
||||
"""
|
||||
driver = nonebot.get_driver()
|
||||
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||
nickname = re.sub(
|
||||
r"[\x00-\x09\x0b-\x1f\x7f-\x9f]", "", member.nick or member.user.name or ""
|
||||
)
|
||||
role = member.role
|
||||
db_user_uid = [u.user_id for u in db_user]
|
||||
uid2name = {u.user_id: u.user_name for u in db_user}
|
||||
if member.id in driver.config.superusers:
|
||||
await LevelUser.set_level(member.id, group_id, 9)
|
||||
member_id = str(member.id)
|
||||
if member_id in superusers:
|
||||
await LevelUser.set_level(member_id, group_id, 9)
|
||||
elif role and default_auth:
|
||||
if role.id != "MEMBER" and not await LevelUser.is_group_flag(
|
||||
member.id, group_id
|
||||
member_id, group_id
|
||||
):
|
||||
if role.id == "OWNER":
|
||||
await LevelUser.set_level(member.id, group_id, default_auth + 1)
|
||||
await LevelUser.set_level(member_id, group_id, default_auth + 1)
|
||||
elif role.id == "ADMINISTRATOR":
|
||||
await LevelUser.set_level(member.id, group_id, default_auth)
|
||||
if cnt := db_user_uid.count(member.id):
|
||||
users = [u for u in db_user if u.user_id == member.id]
|
||||
if cnt > 1:
|
||||
for u in users[1:]:
|
||||
data_list[2].append(u.id)
|
||||
if nickname != uid2name.get(member.id):
|
||||
await LevelUser.set_level(member_id, group_id, default_auth)
|
||||
if users := db_user_map.get(member_id):
|
||||
if len(users) > 1:
|
||||
data_list[2].extend(u.id for u in users[1:])
|
||||
if nickname != users[0].user_name:
|
||||
user = users[0]
|
||||
user.user_name = nickname
|
||||
data_list[1].append(user)
|
||||
else:
|
||||
data_list[0].append(
|
||||
GroupInfoUser(
|
||||
user_id=member.id,
|
||||
user_id=member_id,
|
||||
group_id=group_id,
|
||||
user_name=nickname,
|
||||
user_join_time=member.joined_at or datetime.now(),
|
||||
@@ -71,7 +69,14 @@ class MemberUpdateManage:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def update_group_member(cls, bot: Bot, group_id: str) -> str:
|
||||
async def update_group_member(
|
||||
cls,
|
||||
bot: Bot,
|
||||
group_id: str,
|
||||
*,
|
||||
scene_map: dict[str, Scene] | None = None,
|
||||
platform: str | None = None,
|
||||
) -> str:
|
||||
"""更新群组成员信息
|
||||
|
||||
参数:
|
||||
@@ -85,23 +90,26 @@ class MemberUpdateManage:
|
||||
logger.warning(f"bot: {bot.self_id},group_id为空,无法更新群成员信息...")
|
||||
return "群组id为空..."
|
||||
if interface := get_interface(bot):
|
||||
scenes = await interface.get_scenes()
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
group_list = [s for s in scenes if s.is_group and s.id == group_id]
|
||||
if not group_list:
|
||||
if scene_map is None:
|
||||
scenes = await interface.get_scenes(SceneType.GROUP)
|
||||
scene_map = {scene.id: scene for scene in scenes if scene.is_group}
|
||||
if platform is None:
|
||||
platform = PlatformUtils.get_platform(bot)
|
||||
group_scene = scene_map.get(group_id) if scene_map else None
|
||||
if not group_scene:
|
||||
logger.warning(
|
||||
f"bot: {bot.self_id},group_id: {group_id},群组不存在,"
|
||||
"无法更新群成员信息..."
|
||||
)
|
||||
return "更新群组失败,群组不存在..."
|
||||
members = await interface.get_members(SceneType.GROUP, group_list[0].id)
|
||||
members = await interface.get_members(SceneType.GROUP, group_scene.id)
|
||||
|
||||
try:
|
||||
group_console, _ = await GroupConsole.get_or_create(
|
||||
group_id=group_id, defaults={"platform": platform}
|
||||
)
|
||||
group_console.member_count = len(members)
|
||||
group_console.group_name = group_list[0].name or ""
|
||||
group_console.group_name = group_scene.name or ""
|
||||
await group_console.save(update_fields=["member_count", "group_name"])
|
||||
logger.debug(
|
||||
f"已更新群组 {group_id} 的成员总数为 {len(members)}",
|
||||
@@ -115,13 +123,31 @@ class MemberUpdateManage:
|
||||
)
|
||||
|
||||
db_user = await GroupInfoUser.filter(group_id=group_id).all()
|
||||
db_user_uid = [u.user_id for u in db_user]
|
||||
data_list = ([], [], [])
|
||||
exist_member_list = []
|
||||
db_user_map: dict[str, list[GroupInfoUser]] = {}
|
||||
for user in db_user:
|
||||
db_user_map.setdefault(user.user_id, []).append(user)
|
||||
db_user_ids = set(db_user_map)
|
||||
data_list: tuple[list[GroupInfoUser], list[GroupInfoUser], list[int]] = (
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
exist_member_ids: set[str] = set()
|
||||
driver = nonebot.get_driver()
|
||||
superusers = set(driver.config.superusers)
|
||||
default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH")
|
||||
for member in members:
|
||||
logger.debug(f"即将更新群组成员: {member}", "更新群组成员信息")
|
||||
await cls.__handle_user(member, db_user, group_id, data_list, platform)
|
||||
exist_member_list.append(member.id)
|
||||
member_id = str(member.id)
|
||||
await cls.__handle_user(
|
||||
member,
|
||||
db_user_map,
|
||||
group_id,
|
||||
data_list,
|
||||
platform,
|
||||
default_auth=default_auth,
|
||||
superusers=superusers,
|
||||
)
|
||||
exist_member_ids.add(member_id)
|
||||
if data_list[0]:
|
||||
try:
|
||||
await GroupInfoUser.bulk_create(
|
||||
@@ -145,14 +171,12 @@ class MemberUpdateManage:
|
||||
await GroupInfoUser.filter(id__in=data_list[2]).delete()
|
||||
logger.debug(f"删除重复数据 Ids: {data_list[2]}", "更新群组成员信息")
|
||||
|
||||
if delete_member_list := [
|
||||
uid for uid in db_user_uid if uid not in exist_member_list
|
||||
]:
|
||||
if delete_member_ids := db_user_ids - exist_member_ids:
|
||||
await GroupInfoUser.filter(
|
||||
user_id__in=delete_member_list, group_id=group_id
|
||||
user_id__in=list(delete_member_ids), group_id=group_id
|
||||
).delete()
|
||||
logger.info(
|
||||
f"删除已退群用户 {len(delete_member_list)} 条",
|
||||
f"删除已退群用户 {len(delete_member_ids)} 条",
|
||||
"更新群组成员信息",
|
||||
group_id=group_id,
|
||||
platform="qq",
|
||||
|
||||
@@ -100,7 +100,7 @@ async def build_task(group_id: str | None) -> BuildImage:
|
||||
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
|
||||
group = None
|
||||
if group_id:
|
||||
group = await GroupConsole.get_group(group_id=group_id)
|
||||
group = await GroupConsole.get_group_db(group_id=group_id)
|
||||
if not group:
|
||||
raise GroupInfoNotFound()
|
||||
else:
|
||||
@@ -182,7 +182,7 @@ class PluginManager:
|
||||
)
|
||||
return f"成功将所有功能进群默认状态修改为: {'开启' if status else '关闭'}"
|
||||
if group_id:
|
||||
if group := await GroupConsole.get_group(group_id=group_id):
|
||||
if group := await GroupConsole.get_group_db(group_id=group_id):
|
||||
module_list = cast(
|
||||
list[str],
|
||||
await PluginInfo.filter(plugin_type=PluginType.NORMAL).values_list(
|
||||
@@ -214,7 +214,7 @@ class PluginManager:
|
||||
返回:
|
||||
bool: 是否醒来
|
||||
"""
|
||||
if c := await GroupConsole.get_group(group_id=group_id):
|
||||
if c := await GroupConsole.get_group_db(group_id=group_id):
|
||||
return c.status
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user