perf+fix(danding_qqpush): perf优化+安全修复+代码DRY
- image_render: cached draw object, font.getlength() 替代逐字符创建临时Image - image_render: 移除PNG无效的quality参数 - api.py: ImageRenderer单例复用(避免每请求重载字体) - api.py: 异常详情不再泄露到API响应 - sender.py: 提取_send_msg()消除重复代码
This commit is contained in:
@@ -8,6 +8,9 @@ from nonebot import get_driver, logger
|
||||
from .config import Config
|
||||
from .text_parser import TextParser
|
||||
from .image_render import ImageRenderer
|
||||
|
||||
# Module-level singleton: load font once, reuse across requests
|
||||
_renderer = _renderer # reuse module-level singleton
|
||||
from .sender import sender
|
||||
|
||||
|
||||
@@ -134,10 +137,10 @@ def create_routes(token: str, config: Config):
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"推送接口异常: {str(e)}")
|
||||
logger.exception(f"推送接口异常: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"服务器内部错误: {str(e)}"
|
||||
detail="服务器内部错误"
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
@@ -76,7 +76,7 @@ class ImageRenderer:
|
||||
|
||||
def _calculate_text_dimensions(self, text: str) -> Tuple[int, int]:
|
||||
"""
|
||||
计算文本的尺寸
|
||||
计算文本的尺寸(使用缓存的 Draw 对象,避免重复创建临时 Image)
|
||||
|
||||
Args:
|
||||
text: 文本内容
|
||||
@@ -84,11 +84,11 @@ class ImageRenderer:
|
||||
Returns:
|
||||
(宽度, 高度)
|
||||
"""
|
||||
# 创建一个临时图片用于计算
|
||||
test_img = Image.new('RGB', (1, 1), color=self.bg_color)
|
||||
test_draw = ImageDraw.Draw(test_img)
|
||||
if not hasattr(self, '_cached_draw'):
|
||||
self._cached_img = Image.new('RGB', (1, 1), color=self.bg_color)
|
||||
self._cached_draw = ImageDraw.Draw(self._cached_img)
|
||||
|
||||
bbox = test_draw.textbbox((0, 0), text, font=self.font)
|
||||
bbox = self._cached_draw.textbbox((0, 0), text, font=self.font)
|
||||
width = bbox[2] - bbox[0]
|
||||
height = bbox[3] - bbox[1]
|
||||
|
||||
@@ -96,7 +96,7 @@ class ImageRenderer:
|
||||
|
||||
def _smart_text_wrap(self, text: str, max_width: int) -> list:
|
||||
"""
|
||||
智能文本换行
|
||||
智能文本换行(使用 font.getlength 避免逐字符创建临时 Image)
|
||||
|
||||
Args:
|
||||
text: 文本内容
|
||||
@@ -107,10 +107,10 @@ class ImageRenderer:
|
||||
"""
|
||||
lines = []
|
||||
current_line = ""
|
||||
current_width = 0
|
||||
current_width = 0.0
|
||||
|
||||
for char in text:
|
||||
char_width, _ = self._calculate_text_dimensions(char)
|
||||
char_width = self.font.getlength(char)
|
||||
|
||||
if current_width + char_width <= max_width:
|
||||
current_line += char
|
||||
@@ -195,7 +195,7 @@ class ImageRenderer:
|
||||
|
||||
# 转换为字节流
|
||||
img_byte_arr = io.BytesIO()
|
||||
image.save(img_byte_arr, format='PNG', quality=95)
|
||||
image.save(img_byte_arr, format='PNG')
|
||||
img_byte_arr.seek(0)
|
||||
|
||||
return img_byte_arr.getvalue()
|
||||
|
||||
@@ -42,72 +42,14 @@ class MessageSender:
|
||||
|
||||
return None
|
||||
|
||||
async def send_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
image_base64: str
|
||||
) -> dict:
|
||||
async def _send_msg(self, group_id: int, qq: int, segment) -> dict:
|
||||
"""
|
||||
向指定群发送消息(@用户 + 图片)
|
||||
内部通用发送方法(@用户 + 任意消息段)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
image_base64: 图片的 base64 编码(格式:base64://...)
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
|
||||
Raises:
|
||||
ValueError: Bot 未设置
|
||||
Exception: 发送失败
|
||||
"""
|
||||
bot = self.get_bot()
|
||||
if not bot:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 图片
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.image(image_base64))
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
# 捕获异常并返回错误信息
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def send_text_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
text: str
|
||||
) -> dict:
|
||||
"""
|
||||
向指定群发送纯文本消息(@用户 + 文本)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
text: 文本内容
|
||||
segment: MessageSegment 实例
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
@@ -117,31 +59,30 @@ class MessageSender:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 文本
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.text(text))
|
||||
message.append(segment)
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
__qqpush_source="danding_qqpush"
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
return {"success": True, "data": result, "message": "消息发送成功"}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
logger.warning(f"[QqPush] 消息发送失败 group={group_id} qq={qq}: {e}")
|
||||
return {"success": False, "error": str(e), "message": f"消息发送失败: {e}"}
|
||||
|
||||
async def send_to_group(self, group_id: int, qq: int, image_base64: str) -> dict:
|
||||
"""向指定群发送消息(@用户 + 图片)"""
|
||||
return await self._send_msg(group_id, qq, MessageSegment.image(image_base64))
|
||||
|
||||
async def send_text_to_group(self, group_id: int, qq: int, text: str) -> dict:
|
||||
"""向指定群发送纯文本消息(@用户 + 文本)"""
|
||||
return await self._send_msg(group_id, qq, MessageSegment.text(text))
|
||||
|
||||
|
||||
# 全局消息发送器实例
|
||||
|
||||
Reference in New Issue
Block a user