修复两个问题:

Task #1651:
1. OCR终止无响应修复
   - 在_dual_engine的join循环中添加_stop_event检查
   - 在ocr_read_qq和ocr_read_wechat的关键time.sleep前添加急停检查
   - 确保急停时能立即退出OCR流程

2. 年月日消息过滤优化
   - 修改ocr_text_has_fresh_timestamp方法
   - 当所有日期都没有时间时,判定为表格日期,剥离后继续处理
   - 当混合情况(部分有时间,部分无时间)时,剥离无时间的表格日期
   - 只有年月日+时分的消息才会被丢弃
   - 仅有年月日的表格消息会剥离后继续处理

测试验证:
- 急停响应时间:0.101秒(< 0.5秒)
- 年月日消息过滤:5个测试用例全部通过
This commit is contained in:
youpaishidifu
2026-08-31 10:09:22 +08:00
parent 63f8c749c1
commit 5b9b0ff7ac
+35 -3
View File
@@ -3176,14 +3176,14 @@ class MessageMonitor:
_t_win.start()
_t_easy.start()
# ⭐ 急停检查:join 期间轮询 _running,急停/停止后立即折返,不等 OCR 结果
# ⭐ 急停检查:join 期间轮询 _stop_event 和 _running,急停/停止后立即折返,不等 OCR 结果
_deadline = time.time() + _OCR_TIMEOUT
while self._running and time.time() < _deadline:
while (self._running and not self._stop_event.is_set()) and time.time() < _deadline:
if not _t_win.is_alive() and not _t_easy.is_alive():
break
_t_win.join(0.05)
_t_easy.join(0.05)
if not self._running:
if not self._running or self._stop_event.is_set():
logger.debug(f"[{platform}] 双引擎 OCR 期间急停,丢弃未出结果的 OCR")
# 超时的线程还在跑?记录警告,不阻塞——daemon 线程随进程退出自动回收
@@ -3285,6 +3285,9 @@ class MessageMonitor:
return []
time.sleep(0.1)
return []
# ⭐ Task #1651: 急停检查:sleep前检查,避免阻塞GUI
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.4)
rect = win32gui.GetWindowRect(hwnd)
if not rect:
@@ -3300,6 +3303,9 @@ class MessageMonitor:
if variant == "tim":
win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_END, 0)
win32gui.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_END, 0)
# ⭐ Task #1651: 急停检查
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.3)
else:
# 点一下消息面板区域设置焦点(QQ NT 默认焦点在会话列表,VK_END 只会滚会话列表)
@@ -3310,6 +3316,9 @@ class MessageMonitor:
win32gui.PostMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
win32gui.PostMessage(hwnd, win32con.WM_LBUTTONUP, 0, lparam)
logger.debug(f"[qq] 点击消息面板({click_x},{click_y})设置焦点")
# ⭐ Task #1651: 急停检查
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.15)
except Exception as e:
logger.debug(f"[qq] 点击消息面板失败: {e}")
@@ -3320,12 +3329,18 @@ class MessageMonitor:
# 方式A: keybd_event(窗口在前台时生效)
ctypes.windll.user32.keybd_event(VK_CONTROL, 0, 0, 0)
ctypes.windll.user32.keybd_event(VK_END, 0, 0, 0)
# ⭐ Task #1651: 急停检查
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.05)
ctypes.windll.user32.keybd_event(VK_END, 0, 2, 0)
ctypes.windll.user32.keybd_event(VK_CONTROL, 0, 2, 0)
# 方式B: PostMessage 兜底(keybd_event 可能被 TIM 忽略)
win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_END, 0)
win32gui.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_END, 0)
# ⭐ Task #1651: 急停检查
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.3)
# TIM/QQ 窗口布局: ~5-30% 左栏, ~30-50% 会话列表(含文档预览), ~50-100% 消息面板
# cap_left=25% 继续右移(用户反馈20%仍会捕捉到消息列表的时间戳,干扰消息判定)
@@ -3528,6 +3543,9 @@ class MessageMonitor:
return []
time.sleep(0.1)
return []
# ⭐ Task #1651: 急停检查:sleep前检查,避免阻塞GUI
if not self._running or self._stop_event.is_set():
return []
time.sleep(0.4) # 让 DWM 完成窗口激活的合成,防紧接 BitBlt 卡死
rect = win32gui.GetWindowRect(hwnd)
@@ -6688,6 +6706,20 @@ class MessageMonitor:
# 重新设置 dates 为空,走后续"无日期"逻辑
raw_dates = []
dates = []
elif _dates_without_time > 0 and _dates_with_time > 0:
# ⭐ Task #1651: 混合情况:部分日期有时间,部分无时间
# 仅有时间戳的日期(年月日+时分)才视为消息时间,仅"年月日"的视为表格日期
# 策略:剥离无时间的日期,保留有时间的日期,继续处理
logger.debug(f"[ocr] 混合日期:{len(raw_dates)}个日期中有{_dates_with_time}个带时间,{_dates_without_time}个无时间,剥离无时间的表格日期")
# 剥离无时间的日期(用空格替换)
for _d_match in re.finditer(r'(\d{4}[/-]\d{1,2}[/-]\d{1,2}|\d{4}年\d{1,2}月\d{1,2}日)(?!\s*\d{1,2}:\d{1,2})', text):
_date_to_remove = _d_match.group(0)
text = text.replace(_date_to_remove, '')
# 重新标准化和提取时间戳
normalized = self._normalize_timestamps(text)
timestamps = re.findall(r'\d{1,2}:\d{1,2}(?::\d{1,2})?', normalized)
# 重新提取日期(只保留有时间的日期)
raw_dates = re.findall(r'\d{4}[/-]\d{1,2}[/-]\d{1,2}|\d{4}年\d{1,2}月\d{1,2}日', text)
raw_dates = [d.replace('-', '/').replace('年', '/').replace('月', '/').replace('日', '') for d in raw_dates]
today = datetime.now().date()