From f11096f30afba696313929d5727b20a8a8b90915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=BB=8D=E5=BA=B7?= <3052147989@qq.com> Date: Thu, 5 Mar 2026 17:49:03 +0800 Subject: [PATCH] docs: add BUG-00009 scroll reset and push interruption analysis Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/BUG/BUG-00009-推送交互与推送中断分析.md | 165 +++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/BUG/BUG-00009-推送交互与推送中断分析.md diff --git a/docs/BUG/BUG-00009-推送交互与推送中断分析.md b/docs/BUG/BUG-00009-推送交互与推送中断分析.md new file mode 100644 index 0000000..9eb973a --- /dev/null +++ b/docs/BUG/BUG-00009-推送交互与推送中断分析.md @@ -0,0 +1,165 @@ +# BUG-00009 Telegram 推送交互与推送中断问题分析 + +> 关联功能: TODO-00007 / FD-00007 +> 前置修复: BUG-00007, BUG-00008 +> 分析时间: 2026-03-05 + +--- + +## 一、问题概述 + +| 编号 | 标题 | 严重程度 | 类型 | +|------|------|---------|------| +| **D-1** | 开关推送后账号列表滚动到顶部,体验差 | 🟡 中 | 前端交互 | +| **D-2** | 收到一次推送后不再推送新邮件 | 🔴 高 | 推送逻辑 | + +--- + +## 二、D-1:开关推送后账号列表自动回到顶部 + +### 2.1 现象 + +用户在某个分组中滚动到第 N 个账号,点击 🔔 开启/关闭推送后,列表瞬间回到顶部,用户丢失当前浏览位置。 + +### 2.2 根因分析 + +`toggleTelegramPush` 成功后调用 `loadAccountsByGroup(currentGroupId, true)`,该函数的核心逻辑: + +```javascript +// groups.js:166 — 立即替换为加载动画 +container.innerHTML = '
'; + +// groups.js:175 — API 返回后完整替换 +renderAccountList(data.accounts); +// → container.innerHTML = accounts.map(...).join(''); // 第230行 +``` + +**`innerHTML` 赋值会销毁所有 DOM 子元素并重建**,浏览器滚动位置随之清零。 + +### 2.3 影响范围 + +**不仅限于推送切换**,以下操作都有同样问题: + +| 操作 | 调用位置 | 同样回滚? | +|------|---------|-----------| +| `toggleTelegramPush` | accounts.js:557 | ✅ | +| `deleteAccount` | accounts.js:534 | ✅ | +| `toggleAccountStatus` | accounts.js:486 | ✅ | +| `batchDeleteAccounts` | main.js:2280 | ✅ | +| `confirmBatchMoveGroup` | main.js:2437 | ✅ | +| `confirmBatchTag` | main.js:2361 | ✅ | + +### 2.4 修复方案对比 + +| 方案 | 优点 | 缺点 | 推荐 | +|------|------|------|------| +| **A: 保存/恢复滚动位置** | 简单,改动小 | 列表重建时会闪烁 | ✅ 推荐 | +| B: 局部 DOM 更新 | 无闪烁,最佳体验 | 改动大,需重构渲染逻辑 | P2 | +| C: 虚拟滚动 | 大列表性能好 | 过度设计 | 不推荐 | + +#### 方案 A 实现(推荐) + +在 `loadAccountsByGroup` 中保存/恢复滚动位置: + +```javascript +async function loadAccountsByGroup(groupId, forceRefresh = false) { + const container = document.getElementById('accountList'); + const scrollTop = container.scrollTop; // ← 保存滚动位置 + + // ... 原有加载逻辑 ... + + renderAccountList(data.accounts); + container.scrollTop = scrollTop; // ← 恢复滚动位置 +} +``` + +**注意**:需要去掉加载时的 `container.innerHTML = '加载中…'` 占位(forceRefresh 时直接原地刷新,避免闪烁)。 + +更精细的实现:仅在"单账号更新"场景(toggle、状态切换等)保留滚动,在"整组刷新"场景(切换分组)允许重置。 + +--- + +## 三、D-2:收到一次推送后不再推送新邮件 + +### 3.1 现象 + +用户在 Telegram 收到一次邮件推送通知后,后续即使有新邮件到达,也不再收到推送。 + +### 3.2 时间线还原 + +| 时间 (CST) | 事件 | `sent` | +|------------|------|--------| +| 17:04 | 修复前 Job 运行 | **sent=1**(时区 Bug,重复推送旧邮件) | +| 17:06 | 修复前 Job | sent=1 | +| 17:08 | 修复前 Job | sent=1 | +| 17:16 | **部署时区修复** | - | +| 17:18 | 修复后首次 Job | **sent=0** ✅(旧邮件被正确过滤) | + +### 3.3 根因分析 + +**这是"看起来像 Bug 但其实是修复生效"的场景。** + +修复前: +- Gmail 邮箱的一封旧邮件(CST 某时间)的 `received_iso` 为本地时间 `17:xx:xx` +- 游标为 UTC `09:xx:xx` +- 本地 17:xx > UTC 09:xx → 每次都通过过滤 → **每轮都推送同一封邮件** +- 用户误以为"推送在工作" + +修复后: +- 同一封邮件转换为 UTC 后 `received_iso = 09:xx:xx` +- 游标已推进到 `09:26` → `09:xx <= 09:26` → **正确过滤** +- `sent=0` → 用户以为"推送不工作了" + +**实际上推送功能正常,只是当前环境的网络问题导致无法测试:** +1. Outlook 账号 → Graph API 超时(无代理) +2. QQ 账号 → IMAP 超时(无法连接 imap.qq.com) +3. Gmail 账号 → IMAP 成功,但无新邮件到达 + +### 3.4 验证方法 + +要确认推送功能是否正常,可以: + +1. **发送测试邮件**:向 Gmail 账号 `h15002710886@gmail.com` 发送一封新邮件,等待 1-2 分钟观察 Telegram 是否收到推送 +2. **检查日志**:重新启动后监控日志,等待 `sent=1` 出现 +3. **手动测试**:使用已有的 "📨 发送测试消息" 按钮确认 Telegram Bot API 仍然可用 + +### 3.5 潜在的隐藏问题 + +即使推送逻辑正确,以下情况仍会导致"收不到推送": + +| 场景 | 原因 | 状态 | +|------|------|------| +| 所有 Graph API 超时 | 无代理,无法连接 login.microsoftonline.com | 已知(BUG-TG-004) | +| QQ IMAP 超时 | 无法连接 imap.qq.com | 已知 | +| Job 被跳过 | 上一轮还在运行(max_instances=1) | 已知 | +| Job 耗时过长 | 6 个账号×30s 超时 = 最长 180s | 可优化 | +| 错误时游标仍推进 | `finally` 块无条件推进游标 | **待讨论** | + +#### 关于"错误时游标是否应推进" + +**当前行为**: 无论拉取成功或失败,游标都推进到 `job_start_time`(在 `finally` 中)。 + +**优缺点**: +- ✅ 优点:防止失败账号积累大量待推送邮件(如网络恢复后突然推送几百封) +- ❌ 缺点:拉取失败期间的邮件永远不会被推送(被游标跳过了) + +**MailAggregator_Pro 的做法**: 基于 Message-ID 去重,不依赖游标推进。失败后重试时,已推送的不会重复。 + +**建议**: +- 短期:保持当前行为(防止轰炸) +- 中期:仅在成功拉取后推进游标(失败时保留旧游标,但加 `PUSH_RECENCY_HOURS` 兜底) + +--- + +## 四、修复建议 + +### 立即可做(本轮) + +- [ ] D-1: `loadAccountsByGroup` 增加滚动位置保存/恢复 +- [ ] 确认 D-2: 向 Gmail 发送测试邮件验证推送 + +### 后续优化(P1) + +- 仅成功时推进游标 + `PUSH_RECENCY_HOURS = 12` 兜底 +- 消息间延迟 `TELEGRAM_PUSH_DELAY_SEC = 1.5` +- Job 执行日志中增加 per-account 结果(成功/失败/跳过)