修复3个OCR过滤问题:

1. 时间戳过滤误判:昨天前缀剥离后裸时间戳被当未来时间拦截,添加skip_future_check传递
2. 纯时间戳触发查库:_on_new_message入口检查中文字符,无中文跳过查库
3. 偏旁库补全:添加U+4E28(丨),启用U+4E3F(丿)过滤
This commit is contained in:
youpaishidifu
2026-08-07 12:43:36 +08:00
parent 4635e2c366
commit ad091561fe
3 changed files with 139 additions and 47 deletions
+118 -40
View File
@@ -7,6 +7,7 @@ import logging
import queue
import threading
import os
import re
import time
import customtkinter as ctk
@@ -48,6 +49,10 @@ class MsgBotApp(ctk.CTk):
# 自动回复
self.ar = AutoReply(self.monitor)
# ⭐ 跨平台消息处理锁:防止同一消息被QQ和微信同时处理
self._processing_lock = threading.Lock()
self._processing_messages = set() # 正在处理的消息hash集合
self.ar.set_enabled(self.cfg.get("auto_reply_enabled", False))
self.ar.set_min_score(self.cfg.get("auto_reply_min_score", 0.35))
@@ -301,60 +306,80 @@ class MsgBotApp(ctk.CTk):
def _on_new_message(self, platform: str, msg: str):
"""收到新消息 -> 搜索知识库 -> 日志输出"""
# ⭐ 纯时间戳/纯日期过滤:不含中文字符的消息不送查库
# OCR 常把日期"2026/8 / 6"作为独立 part 传入,匹配度虚高但答非所问
if not re.search(r'[\u4e00-\u9fff]', msg):
self._log_queue.put(f"[{platform.upper()}] ⚠ 消息无中文字符,跳过查库: {msg[:40]}")
return
if not self.kb.is_ready:
self._log_queue.put(f"[{platform.upper()}] {msg}")
self._log_queue.put(" ⚠ 知识库未加载,无法搜索答案")
return
# ⭐ 跨平台互斥:同一消息只允许一个平台处理
import hashlib
msg_hash = hashlib.md5(msg.strip().encode('utf-8')).hexdigest()
with self._processing_lock:
if msg_hash in self._processing_messages:
self._log_queue.put(f"[{platform.upper()}] ⚠ 消息已被其他平台处理,跳过: {msg[:40]}")
return
self._processing_messages.add(msg_hash)
# 在后台线程搜索
threading.Thread(target=self._search_and_log,
args=(platform, msg), daemon=True).start()
args=(platform, msg, msg_hash), daemon=True).start()
def _search_and_log(self, platform: str, msg: str):
def _search_and_log(self, platform: str, msg: str, msg_hash: str):
"""搜索知识库并输出结果,匹配成功时队列自动回复到主线程"""
results = self.kb.search(msg, top_k=3)
lines = [f"[{platform.upper()}] {msg}"]
if results:
answer_title = results[0][0]["title"]
answer_score = results[0][1]
lines.append(f" 🔍 找到 {len(results)} 条相关结果:")
for i, (doc, score) in enumerate(results):
title = doc["title"]
heading = doc.get("heading", "")
body = doc.get("body", "")
content = doc.get("content", "")
# 显示更详细的信息
lines.append(f" 📄 [{title}] (匹配度 {score:.2f})")
lines.append(f" 标题: {heading[:60]}")
lines.append(f" 正文片段: {body[:100].replace(chr(10), ' ')}...")
if i == 0:
lines.append(f" 完整内容片段: {content[:200].replace(chr(10), ' ')}...")
try:
results = self.kb.search(msg, top_k=3)
lines = [f"[{platform.upper()}] {msg}"]
if results:
answer_title = results[0][0]["title"]
answer_score = results[0][1]
lines.append(f" 🔍 找到 {len(results)} 条相关结果:")
for i, (doc, score) in enumerate(results):
title = doc["title"]
heading = doc.get("heading", "")
body = doc.get("body", "")
content = doc.get("content", "")
# 显示更详细的信息
lines.append(f" 📄 [{title}] (匹配度 {score:.2f})")
lines.append(f" 标题: {heading[:60]}")
lines.append(f" 正文片段: {body[:100].replace(chr(10), ' ')}...")
if i == 0:
lines.append(f" 完整内容片段: {content[:200].replace(chr(10), ' ')}...")
# 诊断日志:记录自动回复触发条件
if not self.ar.enabled:
lines.append(" ⚠ 自动回复已关闭,不触发回复")
elif answer_score < self.ar.min_score:
lines.append(f" ⚠ 匹配度 {answer_score:.2f} < 阈值 {self.ar.min_score},跳过")
else:
# 检查内容相关性
has_relevance = self.ar._has_content_relevance(msg, results[0][0], answer_score)
if not has_relevance:
heading = results[0][0].get("heading", "")[:40]
lines.append(f" ⚠ 内容相关性检查失败 (heading={heading!r}),跳过")
# 诊断日志:记录自动回复触发条件
if not self.ar.enabled:
lines.append(" ⚠ 自动回复已关闭,不触发回复")
elif answer_score < self.ar.min_score:
lines.append(f" ⚠ 匹配度 {answer_score:.2f} < 阈值 {self.ar.min_score},跳过")
else:
lines.append(" ✅ 匹配通过,准备发送自动回复")
# 检查内容相关性
has_relevance = self.ar._has_content_relevance(msg, results[0][0], answer_score)
if not has_relevance:
heading = results[0][0].get("heading", "")[:40]
lines.append(f" ⚠ 内容相关性检查失败 (heading={heading!r}),跳过")
else:
lines.append(" ✅ 匹配通过,准备发送自动回复")
# 自动回复:队列到主线程执行(daemon 线程不能可靠 SetForegroundWindow)
reply_data = self.ar.get_reply_data(platform, msg, results[0][0], results[0][1])
if reply_data:
self._reply_queue.put(reply_data)
# 自动回复:队列到主线程执行(daemon 线程不能可靠 SetForegroundWindow)
reply_data = self.ar.get_reply_data(platform, msg, results[0][0], results[0][1])
if reply_data:
self._reply_queue.put(reply_data)
else:
lines.append(" ⚠ get_reply_data 返回 None(重复命中或内部检查失败)")
else:
lines.append(" ⚠ get_reply_data 返回 None(重复命中或内部检查失败)")
else:
lines.append(" ❌ 知识库中未找到相关答案")
lines.append(" ❌ 知识库中未找到相关答案")
for line in lines:
self._log_queue.put(line)
for line in lines:
self._log_queue.put(line)
finally:
# ⭐ 处理完成后从集合中移除消息hash
with self._processing_lock:
self._processing_messages.discard(msg_hash)
def _log(self, text: str):
"""直接写日志(主线程)"""
@@ -394,9 +419,16 @@ class MsgBotApp(ctk.CTk):
platform = data["platform"]
answer = data["answer"]
sender = data.get("sender")
msg = data.get("msg", "")
max_retries = 3
retry_delay = 5 # 秒
# ⭐ 发送前强制检查时间戳:≤300秒则发送,≥300秒则丢弃
msg_age = self._check_message_freshness(msg)
if msg_age is not None and msg_age > 300:
self._log(f"[{platform}] ⚠ 消息时间戳过期({msg_age:.0f}秒>300秒),丢弃: {msg[:40]}")
return
# 设发送锁,阻止监控轮询切走焦点
self.monitor._sending_event.set()
try:
@@ -422,6 +454,52 @@ class MsgBotApp(ctk.CTk):
finally:
self.monitor._sending_event.clear()
def _check_message_freshness(self, msg: str) -> float or None:
"""检查消息时间戳是否新鲜(≤300秒)。返回消息年龄(秒),无法解析返回None。"""
import re
from datetime import datetime, timedelta
# 尝试从消息中提取时间戳
# 常见格式:HH:MM, YYYY-MM-DD HH:MM, MM-DD HH:MM
patterns = [
(r'(\d{1,2}):(\d{2})', 'time_only'), # HH:MM
(r'(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})', 'full'), # YYYY-MM-DD HH:MM
(r'(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})', 'date_time'), # MM-DD HH:MM
]
for pattern, fmt in patterns:
match = re.search(pattern, msg)
if match:
now = datetime.now()
try:
if fmt == 'time_only':
# 只有时间,假设是今天
hour, minute = int(match.group(1)), int(match.group(2))
msg_time = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
# 如果时间在未来,可能是昨天的消息
if msg_time > now:
msg_time -= timedelta(days=1)
elif fmt == 'full':
year, month, day = int(match.group(1)), int(match.group(2)), int(match.group(3))
hour, minute = int(match.group(4)), int(match.group(5))
msg_time = datetime(year, month, day, hour, minute)
elif fmt == 'date_time':
month, day = int(match.group(1)), int(match.group(2))
hour, minute = int(match.group(3)), int(match.group(4))
msg_time = now.replace(month=month, day=day, hour=hour, minute=minute, second=0, microsecond=0)
# 如果时间在未来,可能是去年的消息
if msg_time > now:
msg_time = msg_time.replace(year=now.year - 1)
age = (now - msg_time).total_seconds()
if age >= 0:
return age
except (ValueError, OverflowError):
continue
# 无法解析时间戳,返回None(允许发送)
return None
def _clear_log(self):
self._log_text.configure(state="normal")
self._log_text.delete("1.0", "end")