From a9e8b716850de938f0fd62d8b24a682031c68165 Mon Sep 17 00:00:00 2001 From: liuwei Date: Fri, 3 Apr 2026 11:37:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E7=BE=A4=E6=80=BB=E7=BB=93?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E8=87=AA=E5=8A=A8=E5=85=A5=E5=BA=93=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重新新增群总结数据库操作类并自动建表 t_message_summary - 在群总结插件初始化时接入总结入库数据库对象 - 定时总结成功发送后自动写入数据库,保留文本结果、图片路径和消息数量 - 失败提醒不入库,避免脏数据进入总结表 --- db/message_summary_db.py | 113 ++++++++++++++++++++++++++++++++ plugins/message_summary/main.py | 61 +++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 db/message_summary_db.py diff --git a/db/message_summary_db.py b/db/message_summary_db.py new file mode 100644 index 0000000..f7b92d7 --- /dev/null +++ b/db/message_summary_db.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +import json +from datetime import datetime +from typing import Dict, Optional + +from db.base import BaseDBOperator +from db.connection import DBConnectionManager + + +class MessageSummaryDBOperator(BaseDBOperator): + """群消息总结数据库操作""" + + def __init__(self, db_manager: DBConnectionManager): + super().__init__(db_manager) + self._create_tables() + + def _create_tables(self): + try: + self.execute_update(""" + CREATE TABLE IF NOT EXISTS t_message_summary ( + id INT AUTO_INCREMENT PRIMARY KEY, + chatroom_id VARCHAR(64) NOT NULL COMMENT '群聊ID', + group_name VARCHAR(128) DEFAULT '' COMMENT '群名称', + summary_type VARCHAR(16) NOT NULL COMMENT '总结类型 daily|manual', + period_key VARCHAR(32) NOT NULL COMMENT '周期主键,如 2026-04-01', + period_start DATETIME NULL COMMENT '总结周期开始时间', + period_end DATETIME NULL COMMENT '总结周期结束时间', + source_message_count INT NOT NULL DEFAULT 0 COMMENT '源消息数量', + summary_text LONGTEXT COMMENT '总结文本', + image_path VARCHAR(255) DEFAULT NULL COMMENT '总结图片路径', + meta_json LONGTEXT COMMENT '附加元数据JSON', + last_generated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后生成时间', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + UNIQUE KEY idx_message_summary (chatroom_id, summary_type, period_key), + KEY idx_message_summary_lookup (chatroom_id, period_end) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群消息总结表'; + """) + except Exception as e: + self.LOG.error(f"创建群消息总结表失败: {e}") + + def save_summary(self, summary: Dict) -> bool: + try: + data = { + "chatroom_id": summary.get("chatroom_id", ""), + "group_name": summary.get("group_name", ""), + "summary_type": summary.get("summary_type", "daily"), + "period_key": summary.get("period_key", ""), + "period_start": summary.get("period_start"), + "period_end": summary.get("period_end"), + "source_message_count": int(summary.get("source_message_count", 0) or 0), + "summary_text": summary.get("summary_text", ""), + "image_path": summary.get("image_path"), + "meta_json": json.dumps(summary.get("meta", {}), ensure_ascii=False), + "last_generated_at": summary.get( + "last_generated_at", + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ), + } + fields = ", ".join(data.keys()) + placeholders = ", ".join(["%s"] * len(data)) + update_clause = ", ".join( + [ + f"{key}=VALUES({key})" + for key in data.keys() + if key not in ("chatroom_id", "summary_type", "period_key") + ] + ) + sql = f""" + INSERT INTO t_message_summary ({fields}) + VALUES ({placeholders}) + ON DUPLICATE KEY UPDATE {update_clause} + """ + return self.execute_update(sql, tuple(data.values())) + except Exception as e: + self.LOG.error(f"保存群消息总结失败: {e}") + return False + + def get_summary(self, chatroom_id: str, summary_type: str, period_key: str) -> Optional[Dict]: + try: + sql = """ + SELECT * + FROM t_message_summary + WHERE chatroom_id = %s AND summary_type = %s AND period_key = %s + LIMIT 1 + """ + row = self.execute_query(sql, (chatroom_id, summary_type, period_key), fetch_one=True) + return self._deserialize_row(row) + except Exception as e: + self.LOG.error(f"获取群消息总结失败: {e}") + return None + + @staticmethod + def _deserialize_row(row: Optional[Dict]) -> Optional[Dict]: + if not row: + return row + + meta_json = row.get("meta_json") + if meta_json: + try: + row["meta_json"] = json.loads(meta_json) + except Exception: + row["meta_json"] = {} + else: + row["meta_json"] = {} + + for key in ("period_start", "period_end", "last_generated_at", "create_time", "update_time"): + value = row.get(key) + if isinstance(value, datetime): + row[key] = value.strftime("%Y-%m-%d %H:%M:%S") + + row["meta"] = row.get("meta_json", {}) + return row diff --git a/plugins/message_summary/main.py b/plugins/message_summary/main.py index c1713eb..10f9913 100644 --- a/plugins/message_summary/main.py +++ b/plugins/message_summary/main.py @@ -12,6 +12,7 @@ from loguru import logger from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.plugin_interface import PluginStatus +from db.message_summary_db import MessageSummaryDBOperator from utils.compress_chat_data import compress_chat_data from utils.decorator.async_job import async_job from utils.decorator.plugin_decorators import plugin_stats_decorator @@ -68,6 +69,7 @@ class MessageSummaryPlugin(MessagePluginInterface): def __init__(self): super().__init__() self.message_storage = None + self.message_summary_db = None self.revoke = None self._auto_revoke = None # 注册功能权限 @@ -83,6 +85,9 @@ class MessageSummaryPlugin(MessagePluginInterface): self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo") self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages") self.message_storage = MessageStorage() + db_manager = context.get("db_manager") + if db_manager: + self.message_summary_db = MessageSummaryDBOperator(db_manager) self.LOG.debug(f"初始化 {self.name} 插件成功") return True @@ -223,6 +228,44 @@ class MessageSummaryPlugin(MessagePluginInterface): revoke_manager.add_message_to_revoke(target, client_msg_id, create_time, new_msg_id, delay) return client_msg_id, create_time, new_msg_id + def _save_daily_summary_record( + self, + group_id: str, + group_name: str, + period_start: datetime, + period_end: datetime, + message_count: int, + summary_text: str, + image_path: Optional[str], + ) -> bool: + """保存每日总结到数据库""" + if not self.message_summary_db: + self.LOG.warning("群总结数据库未初始化,跳过总结入库") + return False + + summary_record = { + "chatroom_id": group_id, + "group_name": group_name, + "summary_type": "daily", + "period_key": period_start.strftime("%Y-%m-%d"), + "period_start": period_start.strftime("%Y-%m-%d %H:%M:%S"), + "period_end": period_end.strftime("%Y-%m-%d %H:%M:%S"), + "source_message_count": message_count, + "summary_text": summary_text or "", + "image_path": image_path, + "meta": { + "source": "message_summary_plugin", + "summary_date": period_start.strftime("%Y-%m-%d"), + "has_image": bool(image_path), + }, + } + saved = self.message_summary_db.save_summary(summary_record) + if saved: + self.LOG.info(f"群总结入库成功: group_id={group_id}, period_key={summary_record['period_key']}") + else: + self.LOG.error(f"群总结入库失败: group_id={group_id}, period_key={summary_record['period_key']}") + return saved + async def _generate_summary(self, chat_content: str, group_name: str) -> Tuple[str, Optional[str]]: """生成总结""" # Dify API配置 @@ -404,6 +447,15 @@ class MessageSummaryPlugin(MessagePluginInterface): if image_path: # 图片生成成功,发送图片 await self.bot.send_image_message(group_id, Path(image_path)) + self._save_daily_summary_record( + group_id, + group_name, + yesterday_start, + yesterday_end, + message_count, + summary, + str(image_path), + ) self.LOG.info(f"成功发送群 {group_name} 的昨日总结图片") else: # 图片生成失败,发送文本消息 @@ -417,6 +469,15 @@ class MessageSummaryPlugin(MessagePluginInterface): self.LOG.warning(f"群 {group_name} 的昨日总结生成失败,已发送可撤回失败提醒") else: await self.bot.send_text_message(group_id, summary) + self._save_daily_summary_record( + group_id, + group_name, + yesterday_start, + yesterday_end, + message_count, + summary, + None, + ) self.LOG.info(f"成功发送群 {group_name} 的昨日总结文本") else: await self._send_text_with_revoke(group_id, f"❌ [{yesterday.strftime('%Y-%m-%d')}] 聊天总结生成失败,请稍后再试", 5)