fix: 修复消息去重失效问题:添加数据库查重逻辑 (Task #1639)\n\nmonitor.py:\n- 添加 _clean_text_for_dedup 函数:清理文本只保留中文字符\n- 添加 _is_similar_to_history 函数:检查消息是否与历史消息相似\n- 修改 record_user_message 函数:入库前进行数据库查重,清理后计算相似度,重复率≥50%就跳过\n- 在轮询逻辑中添加数据库查重:在候选消息构建时检查,与历史消息相似则跳过\n- 修复用户报告的问题:1条消息产生4条记录,去重失效

This commit is contained in:
youpaishidifu
2026-08-30 15:38:32 +08:00
parent 0552df5287
commit 778d269e50
+94
View File
@@ -423,8 +423,90 @@ class MessageHistory:
# 更新最近bot回复内容(用于检测OCR噪声导致的重复)
self._last_bot_reply_content[platform] = content.strip()
def _clean_text_for_dedup(self, text: str) -> str:
"""清理文本用于去重:去掉英文字母、数字、标点等,只保留中文字符。
用于数据库查重:不同用户绝对不可能一个问题一样的问法。
清理后计算相似度,重复率≥50%就跳过。
"""
if not text:
return ""
# 只保留中文字符(CJK统一汉字)
cleaned = ''.join(c for c in text if '\u4e00' <= c <= '\u9fff')
return cleaned
def _is_similar_to_history(self, platform: str, content: str, threshold: float = 0.5) -> bool:
"""检查消息是否与数据库历史消息相似(清理后计算相似度)。
参数:
platform: 平台名称
content: 消息内容
threshold: 相似度阈值,默认0.5(50%)
返回:
True = 重复消息,应跳过;False = 新消息,可继续处理。
逻辑:
1. 清理当前消息:去掉英文字母、数字、标点等,只保留中文字符
2. 查询该平台最近10条用户消息
3. 清理每条历史消息:同样只保留中文字符
4. 计算清理后文本的相似度(字符重叠率)
5. 相似度≥阈值(50%)视为重复消息
"""
if not content or not content.strip():
return False
cleaned_current = self._clean_text_for_dedup(content)
if len(cleaned_current) < 2: # 太短无法比对
return False
conn = self._get_conn()
try:
# 查询该平台最近10条用户消息
cursor = conn.execute(
"SELECT content FROM user_messages WHERE platform=? ORDER BY created_at DESC LIMIT 10",
(platform,)
)
rows = cursor.fetchall()
for row in rows:
historical_content = row[0]
cleaned_historical = self._clean_text_for_dedup(historical_content)
if len(cleaned_historical) < 2:
continue
# 计算相似度:字符重叠率
# 方法:计算两个字符串的公共字符比例(使用集合交集)
set_current = set(cleaned_current)
set_historical = set(cleaned_historical)
if not set_current or not set_historical:
continue
# 交集大小除以较小集合的大小(Szymkiewicz-Simpson系数)
intersection = len(set_current & set_historical)
min_size = min(len(set_current), len(set_historical))
similarity = intersection / min_size if min_size > 0 else 0
if similarity >= threshold:
logger.debug(
f"[{platform}] 数据库查重: 相似度{similarity:.0%} >= {threshold:.0%}, "
f"跳过重复消息: '{content[:40]!r}' ~ '{historical_content[:40]!r}'"
)
return True
finally:
conn.close()
return False
def record_user_message(self, platform: str, content: str):
"""记录用户消息到user_messages表(环形缓冲区,max 10条/平台)"""
# ⭐ 先进行数据库查重:清理后计算相似度,重复率≥50%就跳过
if self._is_similar_to_history(platform, content, threshold=0.5):
logger.info(f"[{platform}] 消息去重: 与历史消息相似,跳过入库: {content[:60]!r}")
return # 重复消息,不入库
content_hash = hashlib.md5(content.strip().encode('utf-8')).hexdigest()
conn = self._get_conn()
try:
@@ -9972,6 +10054,11 @@ class MessageMonitor:
if is_duplicate:
self._last_new_msg_time[platform] = time.time()
continue
# ⭐ 数据库查重:清理后计算相似度,重复率≥50%就跳过(用户要求)
if self._msg_history and self._msg_history._is_similar_to_history(platform, cleaned_item, threshold=0.5):
logger.info(f"[{platform}] 数据库查重: 与历史消息相似,跳过: {cleaned_item[:60]}")
self._last_new_msg_time[platform] = time.time()
continue
# ⭐ 跨轮语义去重:5分钟内相似用户查询跳过
if self._is_semantically_duplicate(cleaned_item, platform):
logger.info(f"[{platform}] 用户查询语义去重: 5分钟内相似查询,跳过: {cleaned_item[:60]}")
@@ -10051,6 +10138,13 @@ class MessageMonitor:
logger.info(f"[{platform}] 新消息[{engine_tag}]: {_best_raw[:60]}")
# ⭐ Task #1572: 记录OCR消息到暂存表(用于后续查重)
if self._msg_history:
# ⭐ 先进行数据库查重:清理后计算相似度,重复率≥50%就跳过入库和回复
if self._msg_history._is_similar_to_history(platform, _best_cleaned, threshold=0.5):
logger.info(f"[{platform}] 轮询消息去重: 与历史消息相似,跳过入库和回复: {_best_cleaned[:60]!r}")
# 虽然不入库,但仍需标记为已处理,防止重复处理
self._last_new_msg_time[platform] = time.time()
# 不回复,直接返回
return
try:
self._msg_history.record_ocr_message(platform, _best_cleaned)
except Exception as e: