Files

135 lines
5.1 KiB
Markdown
Raw Permalink Normal View History

# 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`