Files
Nexus/docs/design/specs/2026-05-17-retry-progress-design.md
T
Your Name c9a99f4fb3 fix: 全项目文档对齐 + 代码清理
代码修复:
- web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc)
- requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用)
- 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用)

连接池三层对齐 (config.py/.env.example/session.py):
- DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120
- 基于 MySQL max_connections=400, install.php 公式

文档修复 (21项):
- P0: 硬编码域名/IP替换为配置变量占位符
- P1: 10个过时设计文档加归档标注 (引用旧文件结构)
- P2: step-3-webssh报告 paramiko引用修正
- 6份审查报告连接池参数勘误 100/100→160/120
- ECC安全报告 EC5 datetime.utcnow 标记已修复
- docs/README.md 文档索引重写
- docs/memory/mem_nexus_overview.md 移除硬编码凭证
- docs/project/tech-stack-inventory.md paramiko标记已移除

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:19:56 +08:00

7.3 KiB
Raw Blame History

推送失败重试 + 实时进度 — 设计规格

⚠️ 已归档 — 2026-05-22 本文档引用的文件结构已过时:server/database.py 已拆分为 server/domain/models/PHP 前端 (retry.php/servers.php) 已迁移到 Tailwind CSS v4 + Alpine.jsdatetime.utcnow() 已弃用。功能已实现,保留本文档供历史参考。

背景

当前推送 asyncio.gather 等待全部完成后一次性返回,大数据量时前端无反馈。失败推送只记录 SyncLog,需人工回到服务器管理页重试。需:实时进度 + 自动重试 + 一致性验证后消失

核心设计

数据模型

新增 PushRetryJob 表:

class PushRetryJob(Base):
    __tablename__ = "push_retry_jobs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    server_id = Column(Integer, ForeignKey("servers.id"), nullable=False)
    server_name = Column(String(100))          # 冗余,方便列表显示
    source_path = Column(String(500))
    target_path = Column(String(500))
    status = Column(String(20), default="pending")  # pending/retrying/abandoned
    retry_count = Column(Integer, default=0)
    max_retries = Column(Integer, default=100)
    next_retry_at = Column(DateTime)           # 初始 NOW()+5min
    last_error = Column(Text)
    created_at = Column(DateTime, default=datetime.datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.datetime.utcnow)

生命周期

推送失败 ──→ INSERT job (status=pending, next_retry=NOW+5min)
                │
                ▼
后台 loop (60s) ──→ 扫描 next_retry_at <= NOW()
                │
         rsync -avz --stats   (重新推送)
                │
         rsync -avzn --delete (一致性 dry-run)
                │
    ┌───────────┴───────────┐
    一致                    不一致/错误
    │                       │
    DELETE job               retries < 100 ?
    (消失)                   │
                     ┌───────┴───────┐
                     是              否
                     │               │
                  next_retry     status=abandoned
                  =NOW+5min    (人工处理)

一致性验证命令

rsync -avzn --stats --delete --out-format='%i %n' source/ server:target/
  • -n dry-run
  • --delete 检测目标多余文件
  • 输出 Number of files: 0 → 完全一致

实时进度

sync_engine 改动:

push_to_server 新增 progress_callback 参数:

async def push_to_server(server, source, target, progress_callback=None):
    # rsync --out-format='%i %n' --info=progress2
    # 逐行解析:
    #   >f+++++++++ file.txt  → 文件新增
    #   .f...p..... file2.txt → 权限变更
    for line in iter(proc.stdout.readline, b''):
        if m := re.match(r'^\S+\s+(.+)$', line):
            file_name = m.group(1)
            transferred += 1
            if progress_callback:
                await progress_callback(server.id, {
                    "file": file_name, "transferred": transferred,
                    "total": total_files,  # 从 --stats 预取
                })

WebSocket 推送路径:

push_to_server  →  progress_callback  →  WebSocket manager  →  前端
                                       →  broadcast({server_id, progress})

API 端点

方法 路径 说明
GET /api/servers/retry-jobs 重试队列列表
POST /api/servers/retry-jobs/{id}/retry-now 立即重试单条
POST /api/servers/retry-jobs/retry-all 批量重试全部 pending
DELETE /api/servers/retry-jobs/{id} 删除/放弃单条
DELETE /api/servers/retry-jobs/clear-done 清除已完成/放弃的

前端

retry.php — 重试队列页:

┌─ 重试队列 (3) ───────────────────────────────────────┐
│ 服务器    源目录               目标目录      重试 操作  │
│ web-01  /www/wwwroot/proj   /var/www/site   2    🔄⏭  │
│ api-01  /www/wwwroot/api    /opt/api        0    🔄⏭  │
│ db-01   /data/backup        /backup         5    🔄⏭  │
│                                     [全部重试] [清除] │
└──────────────────────────────────────────────────────┘

servers.php 推送对话框改实时:

┌─ 推送中... ─────────────────────────────────┐
│ web-01  ████████████░░░░░░  120/350 文件   │
│         45MB · ETA 2 分钟                    │
│ web-02  ████████████████░░  200/350 文件   │
│         80MB · ETA 1 分钟                    │
│ 完成 1/3                                       │
│                                    [后台运行] │
└──────────────────────────────────────────────┘

sidebar 角标:重试队列 3

后台服务

新增 retry_engine.py

async def retry_loop():
    while True:
        db = SessionLocal()
        jobs = db.query(PushRetryJob).filter(
            PushRetryJob.status == "pending",
            PushRetryJob.next_retry_at <= datetime.utcnow(),
        ).all()
        for job in jobs:
            job.status = "retrying"
            db.commit()
            # rsync 推送
            result = await push_to_server(server, job.source_path, job.target_path)
            # dry-run 一致性
            consistent = await verify_consistency(server, job.source_path, job.target_path)
            if consistent:
                db.delete(job)
            else:
                job.retry_count += 1
                if job.retry_count >= job.max_retries:
                    job.status = "abandoned"
                else:
                    job.status = "pending"
                    job.next_retry_at = datetime.utcnow() + timedelta(minutes=5)
                job.last_error = result.get("message", "")
            db.commit()
        db.close()
        await asyncio.sleep(60)

main.py lifespan 中启动 retry_loop

文件统计

文件 改动 预估行数
server/database.py PushRetryJob 模型 +15
server/services/retry_engine.py 新建,retry loop + 一致性验证 +120
server/api/servers.py push 端点 WebSocket 进度 + 失败写 job +30
server/services/sync_engine.py 流式解析 rsync + progress_callback +40
server/main.py 启动 retry_loop + WS manager +15
web/retry.php 新建 +80
web/servers.php 推送对话框实时进度条 +60
各页面 sidebar 加重试队列链接 + 角标 +20

预估:8 文件,~380 行。

规格自检

  • 无占位符/TODO
  • 重试机制完整:失败 → INSERT → 定时 scan → rsync → verify → delete/retry/abandon
  • 进度即时:WebSocket + callback 逐文件推送
  • 一致性:rsync dry-run --delete0 个变化才消失
  • 前端独立页面 + sidebar 角标
  • 不在推送对话框卡住用户