abeab9d3e4
Store alert metrics without per-value Redis members to stop duplicate recovery pushes; format all operator-facing timestamps in Asia/Shanghai across Web UI and Telegram.
5.0 KiB
5.0 KiB
审计 — 北京时间展示统一 + 资源恢复 Telegram 去重(2026-06-08)
Changelog:
docs/changelog/2026-06-08-beijing-time-display-unified.mddocs/changelog/2026-06-08-telegram-recovery-dedup-timezone.md
触发原因: Bug 修复(恢复通知秒级重复、Telegram/UI 时间非北京时间)+ 展示层时区统一
审计范围
| 文件 | 行数 | 状态 |
|---|---|---|
server/utils/display_time.py |
27 | ☑ 已审 |
server/infrastructure/telegram/__init__.py |
~320 | ☑ 已审(_now_cn 段) |
server/background/ip_allowlist_refresh.py |
~100 | ☑ 已审(刷新时间) |
server/api/settings.py |
~1570 | ☑ 已审(Telegram 测试消息) |
server/api/agent.py |
~385 | ☑ 已审(恢复去重) |
frontend/src/utils/datetime.ts |
85 | ☑ 已审 |
frontend/src/utils/status.ts |
36 | ☑ 已审 |
frontend/src/composables/push/labels.ts |
68 | ☑ 已审 |
frontend/src/composables/useWebSocket.ts |
~190 | ☑ 已审 |
frontend/src/pages/AuditPage.vue |
~196 | ☑ 已审 |
frontend/src/pages/AlertsPage.vue |
~190 | ☑ 已审 |
frontend/src/pages/CommandsPage.vue |
~393 | ☑ 已审 |
frontend/src/pages/RetriesPage.vue |
~180 | ☑ 已审 |
frontend/src/pages/SchedulesPage.vue |
~533 | ☑ 已审 |
frontend/src/pages/ScriptsPage.vue |
~1315 | ☑ 已审(时间列) |
frontend/src/pages/ScriptRunsPage.vue |
~681 | ☑ 已审 |
frontend/src/pages/ServersPage.vue |
~1400 | ☑ 已审(导出文件名) |
frontend/src/components/credentials/CredentialsManager.vue |
~436 | ☑ 已审 |
frontend/src/components/servers/ServerInlineDetail.vue |
~158 | ☑ 已审 |
tests/test_display_time.py |
18 | ☑ 已审 |
tests/test_telegram_time_format.py |
20 | ☑ 已审 |
tests/test_agent_recovery_dedup.py |
42 | ☑ 已审 |
审计8步结果
Step 1: 登记 ✅
两项用户反馈:① Telegram 恢复时间显示 UTC;② 同一指标 1 秒内连发多条恢复通知。
Step 2: 全文 Read ✅
上述文件全文走读;broadcast_recovery / websocket.py 交叉确认恢复无 Telegram cooldown 设计意图(恢复应即时推送,根因在 Redis 成员重复)。
Step 3: 规则扫描 H
| ID | 规则 | 命中点 | 初判 |
|---|---|---|---|
| H1 | 静默吞错 | agent.py Redis 告警跟踪 except: debug |
SAFE — 已有模式,告警主路径仍广播 |
| H2 | 时区一致性 | parseApiDateTime 假定 naive = UTC |
SAFE — 与 sync_log_repo、ORM _utcnow 一致 |
| H3 | 去重完整性 | Redis legacy mem:92 多条 |
SAFE — _detect_recovery 按 metric 去重 + srem 全清 |
| H4 | XSS / 注入 | 前端 formatDateTimeBeijing 仅格式化 |
SAFE — Vue 文本插值,无 v-html |
| H5 | 密钥泄露 | display_time / datetime.ts |
SAFE — 无凭据 |
| H6 | CUD 审计 | 无新 CUD 路径 | N/A |
| H7 | 多 worker | 恢复 Telegram 仍无 Redis 锁 | LOW — 单心跳内重复已根治;跨 worker 同毫秒双 POST 概率极低,接受 |
Step 4: Closure表
| H | 判定 | 依据 |
|---|---|---|
| H1 | SAFE | 跟踪失败不影响 broadcast_alert;日志可见 |
| H2 | SAFE | DB/API 约定 UTC;文档与 changelog 已说明 |
| H3 | SAFE | 单测 test_detect_recovery_dedupes_legacy_metric_value_entries |
| H4 | SAFE | 展示层数值格式化 |
| H5 | SAFE | — |
| H6 | N/A | — |
| H7 | ACCEPT | 根因修复;若后续再现可加 recovery_sent:{id}:{metric} SETNX |
Step 5: 入口表
| 入口 | 变更 |
|---|---|
POST /api/agent/heartbeat |
Redis alerts:{id} 成员格式、恢复检测 |
| Telegram 出站 | _now_cn → format_beijing_now |
POST .../telegram/test |
测试消息时间 |
| 前端各列表页 | 只读展示,无新 API |
Step 6: 输入→Sink
- Agent
system_info→ 阈值比较 → Redis set /broadcast_recovery→ Telegram:无新 sink;去重降低 Telegram 频率。 - API
created_at字符串 →parseApiDateTime→Intl北京时间:只读展示。
Step 7: 归类
display_time.py 27行 2H 0 FINDING
agent.py (recovery) ~50行 3H 0 FINDING
datetime.ts 85行 2H 0 FINDING
前端 10 文件 ~40行 1H 0 FINDING
测试 3 文件 80行 0H 0 FINDING
──────────────────────────────────────────
总计 8H 0 FINDING(1 ACCEPT)
Step 8: DoD ✅
- 恢复通知同一指标单次心跳最多 1 条 Telegram
- Telegram / 审计页 / 告警页等绝对时间为北京时间
- DB 仍存 UTC,未改调度/漂移计算
pytest7 项通过(test_display_time/test_telegram_time_format/test_agent_recovery_dedup)npm run type-check通过- 生产部署与浏览器终验(待用户批准 deploy)
FINDING 列表
无。H7 为风险接受项,非 FINDING。
结论
☑ 审计通过,0 FINDING,可进入部署门控(需 commit + pre_deploy_check.sh + 前端 build)