8.4 KiB
联合调试后修复行动计划
日期: 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 传递状态。
// 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 的签名。
// 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 排序等价于时间排序)。
// 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 修复后添加基础的运维监控。