# 联合调试后修复行动计划 > **日期**: 2026-03-28 > **来源**: 联合调试报告 V1 + 专家代码审查 > **状态**: 执行中 --- ## 一、修复优先级矩阵 ### P0 — 阻塞级(必须立即修复) | # | 问题 | 模块 | 影响 | 修复方案 | |---|------|------|------|----------| | P0-1 | SSE Relay 死锁 | relay/service.rs | 流式中转完全不可用 | `blocking_lock()` → `tokio::sync::Mutex` 或 channel | | P0-2 | Token 过期无法刷新 | auth/mod.rs | 自动续期失效,用户被迫重新登录 | middleware 对 `/auth/refresh` 路径跳过过期检查 | | P0-3 | 设备清理 SQL 崩溃 | main.rs:58 | stale devices 永远不清理 | TEXT 比较: 绑定 rfc3339 字符串替代 NOW() | ### P1 — 功能缺陷(本周修复) | # | 问题 | 模块 | 影响 | 修复方案 | |---|------|------|------|----------| | P1-1 | 注册返回类型不匹配 | auth/handlers.rs | 桌面端注册后无法自动登录 | register handler 返回含 token 的响应 | | P1-2 | account_api_keys 未被 Relay 消费 | relay/handlers.rs | 用户级 API Key 无效 | relay 优先查 account_api_keys,回退到 provider key | | P1-3 | TOTP nonce 硬编码 | auth/totp.rs | 相同明文+相同密钥产生相同密文 | 使用随机 nonce | | P1-4 | Config 5 端点缺审计日志 | migration/handlers.rs | 配置变更无 audit trail | 添加 log_operation() 调用 | | P1-5 | Admin 缺路由守卫 | admin/src | URL 直通可绕过侧边栏 | 添加 AuthGuard 组件 | | P1-6 | SSE usage 计数竞态 | relay/service.rs | token 使用量记录为 0 | stream 结束后同步记录 usage | ### P2 — 代码质量(下周修复) | # | 问题 | 模块 | 修复方案 | |---|------|------|----------| | P2-1 | 35 个时间戳字段为 TEXT | db.rs + 全部 service | ALTER TABLE → TIMESTAMPTZ + chrono::DateTime | | P2-2 | dashboard_stats 7 次串行查询 | account/service.rs | 合并为 1-2 次 SQL | | P2-3 | 6 个端点缺 OpenAPI 文档 | 各 handlers | 添加 #[utoipa::path] | | P2-4 | computeConfigDiff/syncConfig 未调用 | desktop | 迁移向导统一调用路径 | | P2-5 | list_providers 返回禁用项 | model_config/service.rs | 添加 enabled=true 过滤 | ### P3 — 长期改进 | # | 问题 | 方案 | |---|------|------| | P3-1 | Token 创建/撤销无权限控制 | 添加 token:manage / key:manage 权限 | | P3-2 | 12 个 self-scoped handler 无显式权限 | 评估是否需要细粒度权限 | | P3-3 | SSRF DNS rebinding 风险 | DNS 解析后二次检查 | | P3-4 | 邮箱验证过于简单 | 使用 email 格式正则或 validator crate | | P3-5 | 限流内存状态不共享 | Redis/分布式限流(多实例时) | --- ## 二、P0 修复详细方案 ### P0-1: SSE Relay 死锁 **根因**: `relay/service.rs` 中 SSE 流式响应使用 `std::sync::Mutex::blocking_lock()` 在 `inspect()` 回调内。Tokio 运行时在等待锁时阻塞 worker thread,导致死锁。 **修复**: 替换为 `tokio::sync::Mutex` 或使用 `tokio::sync::watch` channel 传递状态。 ```rust // Before (deadlock): let state = Arc::new(std::sync::Mutex::new(RelayState::new())); stream.inspect(move |chunk| { let mut s = state.blocking_lock(); // DEADLOCK in async context ... }) // After (async-safe): let state = Arc::new(tokio::sync::Mutex::new(RelayState::new())); // Use tokio::spawn for async lock acquisition ``` ### P0-2: Token 过期无法刷新 **根因**: auth middleware 在请求到达 handler 前验证 JWT 有效性。过期的 JWT 被中间件拦截返回 401,永远到不了 `/auth/refresh` handler。 **修复**: 中间件对 refresh 路径放行,或在 refresh handler 中独立验证 expired token 的签名。 ```rust // middleware.rs - 在 JWT 验证前添加路径检查 if request.path() == "/api/v1/auth/refresh" { // 跳过过期检查,仅验证签名 return verify_signature_only(token); } ``` ### P0-3: 设备清理 SQL 类型不匹配 **根因**: `last_seen_at` 为 TEXT 类型,SQL `NOW() - INTERVAL '90 days'` 返回 TIMESTAMPTZ。 **修复**: 使用 rfc3339 字符串比较(TEXT 排序等价于时间排序)。 ```rust // Before: sqlx::query("DELETE FROM devices WHERE last_seen_at < NOW() - INTERVAL '90 days'") // After: let cutoff = (chrono::Utc::now() - chrono::Duration::days(90)).to_rfc3339(); sqlx::query("DELETE FROM devices WHERE last_seen_at < $1").bind(&cutoff) ``` --- ## 三、P1 修复详细方案 ### P1-1: 注册返回类型不匹配 **问题**: 桌面端 `saasStore.register()` 期望响应包含 `token` 字段(`SaaSLoginResponse`),但后端 `register` handler 返回 `AccountPublic`(无 token)。 **修复**: register handler 在创建账号后自动签发 JWT,返回与 login 一致的响应格式。 ### P1-2: account_api_keys 未被 Relay 消费 **问题**: relay handler 仅查找 provider 级 API key,忽略了 `account_api_keys` 表。 **修复**: relay 查找顺序: account_api_keys → provider.api_key → 401 ### P1-3: TOTP nonce 硬编码 **问题**: AES-256-GCM 使用固定 nonce `"zclaw_totp_nce"`,违反加密语义安全性。 **修复**: 每次加密生成随机 12-byte nonce,存储在密文前缀中。 ### P1-4: Config 端点审计日志 **问题**: create/update/delete/seed/sync 5 个 config 端点无 operation_logs 记录。 **修复**: 每个 handler 在执行后调用 `log_operation()`。 ### P1-5: Admin 路由守卫 **问题**: 侧边栏权限已修复,但 URL 直通仍可访问页面。 **修复**: 添加 `AuthGuard` 组件包裹 dashboard layout。 ### P1-6: SSE usage 计数竞态 **问题**: usage 记录通过 `tokio::spawn` + 1s sleep 异步写入,与 stream 消费者竞争,导致 token 计数为 0。 **修复**: stream 结束后同步记录 usage(使用 stream `on_complete` 回调)。 --- ## 四、执行计划 ### Week 1: P0 修复 (3 项) | 天 | 任务 | 文件 | 验证 | |----|------|------|------| | Day 1 | P0-1 SSE 死锁修复 | relay/service.rs | 流式请求不死锁 | | Day 2 | P0-2 Token 刷新修复 | auth/mod.rs + middleware.rs | 过期 token 可刷新 | | Day 3 | P0-3 设备清理修复 + 集成测试 | main.rs | 清理 SQL 正常执行 | ### Week 1-2: P1 修复 (6 项) | 天 | 任务 | 文件 | |----|------|------| | Day 4 | P1-1 注册返回类型 | auth/handlers.rs | | Day 5 | P1-2 Relay API Key 消费 | relay/handlers.rs + service.rs | | Day 6 | P1-3 TOTP nonce 随机化 | auth/totp.rs | | Day 7 | P1-4 Config 审计日志 | migration/handlers.rs | | Day 8 | P1-5 Admin 路由守卫 | admin/src | | Day 9 | P1-6 SSE usage 同步记录 | relay/service.rs | ### Week 3: P2 修复 (5 项) | 天 | 任务 | |----|------| | Day 10-11 | P2-1 时间戳类型迁移 (35 字段) | | Day 12 | P2-2 dashboard 查询优化 | | Day 13 | P2-3~P2-5 OpenAPI + 未调用方法 + 过滤 | --- ## 五、验证策略 ### 每个 P0 修复的验证标准 | P0 | 验证方法 | 通过标准 | |----|----------|----------| | P0-1 | 发送 SSE 流式请求 | 响应流完整传输,无死锁,无超时 | | P0-2 | 等待 token 过期后 refresh | 新 token 有效,后续请求正常 | | P0-3 | 执行设备清理任务 | SQL 正常执行,删除 >90天设备 | ### 回归测试 每次 P0/P1 修复后,重新执行联合调试报告中已通过的 20 个测试用例,确保不回退。 --- ## 六、专家结论 ### 安全工程师结论 > P0-1 (SSE 死锁) 是最高优先级 — 它意味着 Relay 核心功能完全不可用。P1-3 (TOTP nonce) 虽然不会直接被利用,但违反加密基本原则,应在 P1 中修复。P3 级的权限细粒度改进是长期安全加固方向。 ### 后端架构师结论 > 35 个 TEXT 时间戳字段是系统性技术债。短期用 rfc3339 字符串比较可以工作(ISO 8601 文本排序等价时间排序),但中长期必须迁移到 TIMESTAMPTZ。SEED_ROLES 中 NOW() 与 to_rfc3339() 格式不一致也需要在迁移时解决。 ### 前端负责人结论 > P1-1 (注册返回类型不匹配) 是前端最迫切的修复 — 它阻塞了新用户注册流程。Admin 路由守卫 (P1-5) 是用户体验和安全的双重需求。 ### QA 主管结论 > 12 项未执行测试中,B-03 (Relay SSE)、C-01 (新用户旅程)、C-03 (TOTP) 风险最高。建议在 P0 修复后立即补充执行,覆盖率为先。 ### DevOps 结论 > 服务在测试期间中断说明需要:1) 进程管理(systemd/windows service)2) 健康检查告警 3) 日志持久化。建议在 P0 修复后添加基础的运维监控。