feat: 添加多线程架构支持并优化性能

实现线程管理器以支持高并发场景,添加GIL-free模式提升Python 3.14下的多线程性能
新增B站API集成和本地文件服务器功能,改进镜像插件支持GIF处理
更新文档说明多线程架构和GIL-free模式的使用方法
This commit is contained in:
2026-03-01 16:01:51 +08:00
parent 734c112ee4
commit ff4a4d92a5
20 changed files with 2071 additions and 317 deletions

View File

@@ -4,18 +4,25 @@
功能:
- 仅限管理员在私聊中调用。
- 通过回复一条消息并发送指令,将该消息转发给机器人所在的所有群聊。
- 此插件不写入 __plugin_meta__保持隐藏。
- 支持跨机器人广播:当任意机器人接收到广播消息时,会通过 Redis 发布消息,
所有其他机器人订阅后也会转发给它们各自的群聊。
- 使用通用消息格式,不使用合并转发(聊天记录)格式。
"""
import asyncio
import json
from core.managers.command_manager import matcher
from models.events.message import MessageEvent, PrivateMessageEvent
from core.permission import Permission
from core.utils.logger import logger
from core.managers.redis_manager import redis_manager
# --- 会话状态管理 ---
# 结构: {user_id: asyncio.TimerHandle}
broadcast_sessions: dict[int, asyncio.TimerHandle] = {}
# 广播消息订阅任务
_broadcast_subscription_task = None
def cleanup_session(user_id: int):
"""
清理超时的广播会话。
@@ -24,6 +31,103 @@ def cleanup_session(user_id: int):
del broadcast_sessions[user_id]
logger.info(f"[Broadcast] 会话 {user_id} 已超时,自动取消。")
async def broadcast_message_to_groups(bot, message, source_robot_id: str = "unknown"):
"""
将消息广播到所有群聊
Args:
bot: 机器人实例
message: 要发送的消息
source_robot_id: 消息来源机器人ID用于日志
"""
try:
group_list = await bot.get_group_list()
if not group_list:
logger.warning(f"[Broadcast] 机器人 {source_robot_id} 目前没有加入任何群聊")
return
success_count, failed_count = 0, 0
total_groups = len(group_list)
for group in group_list:
try:
await bot.send_group_msg(group.group_id, message)
success_count += 1
except Exception as e:
failed_count += 1
logger.error(f"[Broadcast] 机器人 {source_robot_id} 发送至群聊 {group.group_id} 失败: {e}")
logger.success(f"[Broadcast] 机器人 {source_robot_id} 广播完成: {total_groups} 个群聊, 成功 {success_count}, 失败 {failed_count}")
except Exception as e:
logger.error(f"[Broadcast] 机器人 {source_robot_id} 获取群聊列表失败: {e}")
async def start_broadcast_subscription():
"""
启动 Redis 广播消息订阅
"""
global _broadcast_subscription_task
if _broadcast_subscription_task is None:
_broadcast_subscription_task = asyncio.create_task(broadcast_subscription_loop())
logger.success("[Broadcast] Redis 广播订阅已启动")
async def stop_broadcast_subscription():
"""
停止 Redis 广播消息订阅
"""
global _broadcast_subscription_task
if _broadcast_subscription_task:
_broadcast_subscription_task.cancel()
try:
await _broadcast_subscription_task
except asyncio.CancelledError:
pass
_broadcast_subscription_task = None
logger.info("[Broadcast] Redis 广播订阅已停止")
async def broadcast_subscription_loop():
"""
Redis 广播消息订阅循环
"""
if redis_manager.redis is None:
logger.warning("[Broadcast] Redis 未初始化,无法启动广播订阅")
return
try:
pubsub = redis_manager.redis.pubsub()
await pubsub.subscribe("neobot_broadcast")
logger.success("[Broadcast] 已订阅 Redis 广播频道")
async for message in pubsub.listen():
if message["type"] == "message":
try:
data = json.loads(message["data"])
robot_id = data.get("robot_id", "unknown")
message_data = data.get("message")
logger.info(f"[Broadcast] 收到跨机器人广播消息: 来源 {robot_id}")
# 获取当前机器人的实例
from core.ws import WS
if WS.instance:
await broadcast_message_to_groups(WS.instance, message_data, robot_id)
except json.JSONDecodeError as e:
logger.error(f"[Broadcast] 解析广播消息失败: {e}")
except Exception as e:
logger.error(f"[Broadcast] 处理广播消息失败: {e}")
except Exception as e:
logger.error(f"[Broadcast] 广播订阅循环异常: {e}")
@matcher.command("broadcast", "广播", permission=Permission.ADMIN)
async def broadcast_start(event: MessageEvent):
"""
@@ -49,12 +153,15 @@ async def broadcast_start(event: MessageEvent):
user_id
)
broadcast_sessions[user_id] = timeout_handler
# 确保广播订阅已启动
await start_broadcast_subscription()
@matcher.on_message()
async def handle_broadcast_content(event: MessageEvent):
"""
通用消息处理器,用于捕获广播模式下的消息输入。
将捕获到的消息打包成一个新的合并转发消息并广播
将捕获到的消息直接发送给机器人所在的所有群聊,并通过 Redis 发布给其他机器人
"""
# 仅处理私聊消息,且用户在广播会话中
if not isinstance(event, PrivateMessageEvent) or event.user_id not in broadcast_sessions:
@@ -71,46 +178,27 @@ async def handle_broadcast_content(event: MessageEvent):
await event.reply("捕获到的消息为空,已取消广播。")
return True
# --- 执行广播逻辑 ---
bot = event.bot
try:
group_list = await bot.get_group_list()
if not group_list:
await event.reply("机器人目前没有加入任何群聊。")
return True
except Exception as e:
logger.error(f"[Broadcast] 获取群聊列表失败: {e}")
await event.reply(f"获取群聊列表时发生错误: {e}")
return True
success_count, failed_count = 0, 0
total_groups = len(group_list)
await event.reply(f"已收到广播内容,准备打包并向 {total_groups} 个群聊广播...")
# --- 将管理员发送的消息打包成一个单节点的合并转发消息 ---
try:
nodes_to_send = [
bot.build_forward_node(
user_id=event.user_id,
nickname=event.sender.nickname if event.sender else "未知用户",
message=message_to_broadcast
)
]
except Exception as e:
logger.error(f"[Broadcast] 构建转发节点失败: {e}")
await event.reply(f"构建转发消息节点时发生错误: {e}")
return True
# --- 向所有群聊发送打包好的合并转发消息 ---
for group in group_list:
try:
await bot.send_group_forward_msg(group.group_id, nodes_to_send)
success_count += 1
except Exception as e:
failed_count += 1
logger.error(f"[Broadcast] 转发至群聊 {group.group_id} 失败: {e}")
# 获取当前机器人ID使用反向WS的机器人ID
from core.ws import WS
robot_id = "unknown"
if WS.instance and hasattr(WS.instance, 'self_id'):
robot_id = str(WS.instance.self_id)
report = f"广播完成。\n总群聊: {total_groups}\n成功: {success_count}\n失败: {failed_count}"
await event.reply(report)
# --- 执行本地广播 ---
await broadcast_message_to_groups(event.bot, message_to_broadcast, robot_id)
# --- 通过 Redis 发布消息给其他机器人 ---
try:
if redis_manager.redis:
broadcast_data = {
"robot_id": robot_id,
"message": message_to_broadcast
}
await redis_manager.redis.publish("neobot_broadcast", json.dumps(broadcast_data))
logger.success(f"[Broadcast] 已通过 Redis 发布广播消息: 来源 {robot_id}")
except Exception as e:
logger.error(f"[Broadcast] 发布 Redis 消息失败: {e}")
await event.reply("广播已完成!")
return True # 消费事件,防止其他处理器响应