docs: 推送页面 Round 2 迭代设计文档与 changelog

- 设计文档: 5 项迭代功能(同步说明、在线标识、失败重试、Telegram通知、文件预览)
- 技术方案: 详细实现步骤与文件清单
- Changelog: 功能变更记录

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-29 18:54:12 +08:00
parent 152852e2c3
commit 02423cb803
3 changed files with 290 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
# 2026-05-29 推送页面迭代 Round 2 — 5 项功能
## 变更摘要
1. F1: 失败自动重试 — 推送失败自动创建重试任务,行内"🔄 重试"按钮
2. F2: 服务器在线状态标识 — 服务器列表显示🟢/🔴在线状态
3. F3: 推送完成 Telegram 通知 — 推送完成/部分失败发 Telegram 消息含详情
4. F4: 文件内容预览 — 文件管理器点击文件名预览内容
5. F5: 同步模式详细说明 — 每种模式下方显示说明文字
## 动机
推送页面 Round 1 完成后,实际使用中发现:推送失败需手动切页重试、无法识别服务器在线状态、推送完成无即时通知、文件管理器无法预览内容、同步模式含义不明确。
## 涉及文件
### 后端
- `server/application/services/sync_engine_v2.py` — 推送失败自动创建 PushRetryJob + Telegram 通知
- `_sync_one()`: 失败时创建 PushRetryJob,记录 retry_job_id
- `sync_files()`: 完成后调用 `send_telegram_sync_complete()`
- 返回 dict 新增 `retry_job_ids`
- `server/api/websocket.py``broadcast_sync_progress()` 新增 `retry_job_id` 参数
- `server/infrastructure/telegram/__init__.py` — 新增 `send_telegram_sync_complete()` 函数
- 参数: completed, failed, total, source_path, operator, failed_servers, duration_seconds
- 全部成功🟢/部分失败🟡/全部失败🔴 不同消息格式
- `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview` 端点
- 安全: `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 校验
- 限制: 仅文件 + 最多 4KB + base64 编码 + encoding_hint
- 审计日志
- `server/api/schemas.py` — 新增 `LocalFilePreview` 模型
### 前端
- `web/app/push.html` — 全面改造
- F1: WS 消息处理 `updateProgressFromWS()` 失败行显示"🔄 重试"按钮 + `retryServer()` 函数
- F1: HTTP 响应 `updateProgressFromResult()` 同样显示重试按钮 + `_retryJobMap` 映射
- F2: `filterServers()` 服务器选项加🟢/🔴在线状态标识
- F4: `renderFileManager()` 文件名可点击 + `previewLocalFile()` 预览函数 + 模态框
- F5: 同步模式下方 `<div id="syncModeDesc">` 说明文字 + `onSyncModeChange()` 更新
### 设计/技术文档
- `docs/design/specs/2026-05-29-push-round2-design.md` — 设计文档
- `docs/design/plans/2026-05-29-push-round2.md` — 技术文档
## 安全影响
- F1 (重试): 利用已有 `/api/retries/{id}/retry` 端点,JWT 认证 + 审计日志
- F3 (Telegram): `source_path``sanitize_external_message()` 脱敏
- F4 (预览): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验 + 4KB 限制
- 所有新端点需 JWT 认证 + 审计日志
## 需要迁移/重启
- 是:后端 Python 需重启(supervisorctl restart nexus
- 否:无数据库 schema 迁移
## 验证方式
1. F5: 切换同步模式 → 下方说明文字正确更新
2. F2: 服务器列表 → 在线🟢、离线🔴标识
3. F1: 推送失败 → 行内"🔄 重试"按钮 → 点击重试
4. F3: 推送完成 → Telegram 收到通知含详情
5. F4: 上传 ZIP → 点击文件名 → 弹出预览框
## 进度条
☑实现 ☐WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog
+134
View File
@@ -0,0 +1,134 @@
# 2026-05-29 推送页面迭代 Round 2 — 技术文档
## 涉及文件清单
| 文件 | 改动类型 | 涉及功能 |
|------|---------|---------|
| `server/infrastructure/telegram/__init__.py` | 新增函数 | F3 Telegram |
| `server/application/services/sync_engine_v2.py` | 修改 | F1 重试 + F3 Telegram |
| `server/api/websocket.py` | 修改参数 | F1 重试 |
| `server/api/sync_v2.py` | 新增端点 | F4 预览 |
| `server/api/schemas.py` | 新增模型 | F4 预览 |
| `web/app/push.html` | 修改 | F1+F2+F4+F5 前端 |
| `docs/changelog/2026-05-29-push-round2.md` | 新增 | changelog |
## 实现步骤
### Step 1: F5 同步模式详细说明(纯前端)
**文件**: `web/app/push.html`
1. 同步模式 `</div>` 后增加 `<div id="syncModeDesc">` 说明文字
2. `onSyncModeChange()` 函数末尾增加说明文字更新逻辑
3. 默认显示增量同步说明
说明内容:
- 增量同步:仅传输源目录中新增或修改的文件,目标已有且未变的文件跳过。安全快速,适合日常更新。
- 全量同步:使目标目录与源目录完全一致,会删除目标中不存在于源的文件。⚠️ 可能造成数据丢失,建议先预览。
- 校验和:通过文件内容校验和(而非修改时间+大小)判断是否需要传输。更精确但更慢,适合时钟不同步的环境。
### Step 2: F2 服务器在线状态标识(纯前端)
**文件**: `web/app/push.html`
1. `filterServers()` 中渲染 `<option>` 时,根据 `s.is_online` 加🟢/🔴前缀
2. 离线服务器选项加 `style="color: #666"` 提示
3. 数据来源:`/api/servers/` 已返回 `is_online`(从 Redis heartbeat 实时读取),无需改后端
### Step 3: F1 失败自动重试(后端+前端)
**后端**:
1. `server/application/services/sync_engine_v2.py``_sync_one()`:
-`sync_log.status == "failed"` 时,创建 `PushRetryJob`:
```python
retry_job = PushRetryJob(
server_id=server.id, server_name=server.name,
source_path=source_path, target_path=dest,
status="pending", retry_count=0, max_retries=3,
next_retry_at=datetime.now(timezone.utc),
last_error=sync_log.error_message[:500] if sync_log.error_message else None,
)
retry_job = await self.retry_repo.create(retry_job)
```
- 将 `retry_job.id` 存入变量,传给 WS 广播
2. `server/api/websocket.py` — `broadcast_sync_progress()` 增加参数:
- `retry_job_id: int = None`
- WS 消息中增加 `retry_job_id` 字段
**前端**:
1. `updateProgressFromWS()` — 失败行渲染"🔄 重试"按钮
2. `updateProgressFromResult()` — 同上
3. 新增 `retryServer(retryJobId)` — 调用 `POST /api/retries/{id}/retry`
4. 存储映射 `_retryJobMap = {}` — `server_id → retry_job_id`
### Step 4: F3 推送完成 Telegram 通知(后端)
1. `server/infrastructure/telegram/__init__.py` — 新增函数:
```python
async def send_telegram_sync_complete(
completed: int, failed: int, total: int,
source_path: str, operator: str,
failed_servers: list[str] = None,
duration_seconds: int = None,
) -> bool:
```
- 消息格式:HTML,含 emoji 标识状态
- 全部成功: 🟢 推送完成
- 部分失败: 🟡 推送完成(部分失败)+ 失败服务器名列表
- 全部失败: 🔴 推送失败
- `source_path` 经 `sanitize_external_message()` 脱敏
2. `server/application/services/sync_engine_v2.py` — `sync_files()`:
- `asyncio.gather()` 完成后,计算总耗时
- 收集失败服务器名列表
- 调用 `send_telegram_sync_complete()`
- try/except 包裹,Telegram 失败不影响推送结果
### Step 5: F4 文件内容预览(后端+前端)
**后端**:
1. `server/api/schemas.py` — 新增:
```python
class LocalFilePreview(BaseModel):
path: str = Field(..., min_length=1)
```
2. `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview`:
- 安全:`os.path.realpath()` + `startswith("/tmp/nexus_upload_")`
- 限制:仅文件(非目录)+ 最多读 4KB
- 返回:`{name, size, content_base64, truncated, encoding_hint}`
- `encoding_hint`: 尝试 UTF-8 解码,成功 `"text"` 否则 `"binary"`
- 审计日志
**前端**:
1. `renderFileManager()` — 文件名加 `cursor-pointer` + `onclick="previewLocalFile(path, name)"`
2. 新增 `previewLocalFile(path, name)`:
- 调用 `/api/sync/local-file-preview`
- 弹出模态框显示文件名、大小、内容
- 二进制文件提示"无法预览"
3. 新增模态框 HTML`#previewModal`
## 依赖与配置变更
无新依赖。无数据库 schema 迁移。
## 测试要点
1. F5: 切换同步模式 → 说明文字正确切换
2. F2: 在线服务器🟢、离线服务器🔴显示正确
3. F1: 推送失败 → 行内重试按钮 → 点击重试成功
4. F3: 推送完成 → Telegram 收到通知
5. F4: 点击文件 → 预览模态框显示内容
6. F4: 点击二进制文件 → 提示无法预览
7. F4 安全: 尝试预览 `/etc/passwd` → 403 拒绝
## 回滚方式
所有改动均为增量(新增函数/端点/前端元素),回滚只需:
1. `git revert` 提交
2. `supervisorctl restart nexus`
@@ -0,0 +1,96 @@
# 2026-05-29 推送页面迭代 Round 2 — 设计文档
## 背景与目标
推送页面 Round 1 已完成 5 项功能(WS 实时进度、分组选择、历史增强、文件操作、推送校验)。用户在实际使用中发现 5 项体验/功能缺陷:
1. **推送失败需手动切页重试** — 失败后需跳到"重试队列"页面,操作割裂
2. **无法识别服务器在线状态** — 选中离线服务器推送必然超时失败,浪费等待时间
3. **推送完成无即时通知** — 批量推送耗时长,用户离开页面后无法得知结果
4. **文件管理器无法预览内容** — 不确定推送的是否是正确文件,需下载验证
5. **同步模式含义不明确** — "增量/全量/校验和"对非运维用户不直观
## 方案对比
### F1 失败自动重试
| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 行内重试按钮 | 不离开页面,体验流畅 | 需要从推送结果中获取 retry_job_id |
| B: 自动重试无需按钮 | 全自动 | 可能对临时性错误造成无意义重试 |
| C: 弹窗显示失败列表+批量重试 | 可选择重试哪些 | 交互重 |
**选定方案 A** — 推送失败的行内"🔄 重试"按钮,利用现有 `PushRetryJob` + `/api/retries/` 体系
### F2 在线状态标识
| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 选项文字前加🟢/🔴 | 直观,零交互成本 | 无 |
| B: hover 显示状态详情 | 信息更多 | 需额外交互 |
**选定方案 A** — 最简方案,API 已返回 `is_online` 字段
### F3 Telegram 通知
| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 推送完成后发一条汇总通知 | 信息集中,不扰 | 延迟感知差 |
| B: 逐台通知 | 实时 | 消息太多 |
| C: 完成时汇总 + 部分失败时额外通知 | 覆盖全面 | 两条消息 |
**选定方案 A** — 推送完成后发一条汇总 Telegram 消息,含成功/失败数、失败服务器名、耗时
### F4 文件内容预览
| 方案 | 优点 | 缺点 |
|------|------|------|
| A: 点击文件名弹模态框预览 | 体验流畅 | 需新增后端端点 |
| B: 新 tab 打开 | 简单 | 跳离上下文 |
**选定方案 A** — 新增 `/api/sync/local-file-preview` 端点,前端模态框预览
### F5 同步模式说明
| 方案 | 优点 | 缺点 |
|------|------|------|
| A: radio 下方固定说明文字 | 简单直观 | 占空间 |
| B: hover tooltip | 不占空间 | 可能被忽略 |
| C: 模式旁❓图标hover显示 | 平衡 | 交互多一步 |
**选定方案 A** — radio 组下方增加动态说明文字,切换模式时更新
## 选定方案及理由
所有 5 项均选方案 A(最简方案),理由:
- 利用现有基础设施(重试体系、Telegram 模块、Redis heartbeat
- 改动范围最小,风险最低
- 用户需求明确,无需复杂设计
## 接口/数据模型
### F1: 新增 WS 字段
- `broadcast_sync_progress()` 增加可选参数 `retry_job_id: int = None`
- WS 消息 `{type: "sync_progress", ..., retry_job_id: 123}` (失败时有值)
### F3: 新增 Telegram 函数
- `send_telegram_sync_complete(completed, failed, total, source_path, operator, failed_servers, duration_seconds)`
### F4: 新增 API 端点
- `POST /api/sync/local-file-preview``{path: str}``{name, size, content_base64, truncated, encoding_hint}`
- 新增 Schema: `LocalFilePreview`
## 安全与性能约束
- F1: 重试按钮调已有 `/api/retries/{id}/retry`,已有 JWT 认证 + 审计
- F3: Telegram 消息经 `sanitize_external_message()` 脱敏
- F4: 文件预览限制:仅 `/tmp/nexus_upload_*` 目录 + 最多 4KB + realpath 校验
- F4: 预览端点需 JWT 认证 + 审计日志
## 验收标准
1. F5: 切换同步模式 → 下方说明文字正确更新
2. F2: 服务器列表在线🟢/离线🔴标识正确
3. F1: 推送失败 → 行内出现重试按钮 → 点击可重试
4. F3: 推送完成 → Telegram 收到通知含详情
5. F4: 点击文件名 → 弹出预览框