chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、 文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
@@ -391,8 +391,8 @@ pub mod permissions {
|
||||
pub const ACCOUNT_READ: &str = "account:read";
|
||||
pub const ACCOUNT_WRITE: &str = "account:write";
|
||||
pub const ACCOUNT_ADMIN: &str = "account:admin";
|
||||
pub const MODEL_READ: &str = "model:read";
|
||||
pub const MODEL_WRITE: &str = "model:write";
|
||||
pub const PROVIDER_MANAGE: &str = "provider:manage";
|
||||
pub const MODEL_MANAGE: &str = "model:manage";
|
||||
pub const RELAY_USE: &str = "relay:use";
|
||||
pub const RELAY_ADMIN: &str = "relay:admin";
|
||||
pub const CONFIG_READ: &str = "config:read";
|
||||
@@ -404,8 +404,8 @@ pub mod permissions {
|
||||
| 角色 | 权限 |
|
||||
|------|------|
|
||||
| `super_admin` | `admin:full` (授权检查时直接放行) |
|
||||
| `admin` | account:read/write, model:read/write, relay:use/admin, config:read/write |
|
||||
| `user` | model:read, relay:use, config:read |
|
||||
| `admin` | account:read/write/admin, provider:manage, model:manage, relay:use/admin, config:read/write |
|
||||
| `user` | relay:use, config:read |
|
||||
|
||||
### 4.4 API 端点
|
||||
|
||||
@@ -418,8 +418,8 @@ pub mod permissions {
|
||||
| POST | `/api/v1/auth/totp/verify` | JWT | 验证并启用 TOTP |
|
||||
| POST | `/api/v1/auth/totp/disable` | JWT | 禁用 TOTP |
|
||||
| GET | `/api/v1/accounts` | account:read | 账号列表 |
|
||||
| GET | `/api/v1/accounts/:id` | account:read | 账号详情 |
|
||||
| PUT | `/api/v1/accounts/:id` | account:write | 更新账号 |
|
||||
| GET | `/api/v1/accounts/:id` | 本人/account:read | 账号详情 |
|
||||
| PATCH | `/api/v1/accounts/:id` | 本人/account:write | 更新账号 |
|
||||
| PATCH | `/api/v1/accounts/:id/status` | account:admin | 启用/禁用 |
|
||||
| GET | `/api/v1/tokens` | - (本人) | 列出 API 令牌 |
|
||||
| POST | `/api/v1/tokens` | - (本人) | 创建令牌 |
|
||||
@@ -481,25 +481,22 @@ pub enum AccountStatus { Active, Disabled, Suspended }
|
||||
|
||||
| 方法 | 路径 | 权限 | 说明 |
|
||||
|------|------|------|------|
|
||||
| GET | `/api/v1/providers` | model:read | 提供商列表 |
|
||||
| POST | `/api/v1/providers` | model:write | 创建提供商 |
|
||||
| GET | `/api/v1/providers/:id` | model:read | 提供商详情 |
|
||||
| PUT | `/api/v1/providers/:id` | model:write | 更新提供商 |
|
||||
| PATCH | `/api/v1/providers/:id/status` | model:write | 启用/禁用 |
|
||||
| GET | `/api/v1/providers/:id/models` | model:read | 模型列表 |
|
||||
| POST | `/api/v1/providers/:id/models` | model:write | 添加模型 |
|
||||
| PUT | `/api/v1/models/:id` | model:write | 更新模型 |
|
||||
| PATCH | `/api/v1/models/:id/status` | model:write | 启用/禁用模型 |
|
||||
| GET | `/api/v1/account/api-keys` | - (本人) | 密钥列表 |
|
||||
| POST | `/api/v1/account/api-keys` | - (本人) | 创建密钥 |
|
||||
| PUT | `/api/v1/account/api-keys/:id` | - (本人) | 更新权限/标签 |
|
||||
| POST | `/api/v1/account/api-keys/:id/rotate` | - (本人) | 轮换密钥 |
|
||||
| PATCH | `/api/v1/account/api-keys/:id/status` | - (本人) | 启用/禁用 |
|
||||
| DELETE | `/api/v1/account/api-keys/:id` | - (本人) | 撤销密钥 |
|
||||
| GET | `/api/v1/usage/stats` | relay:admin | 总体统计 |
|
||||
| GET | `/api/v1/usage/daily` | - (本人) | 每日统计 |
|
||||
| GET | `/api/v1/usage/account/:id` | relay:admin | 账号统计 |
|
||||
| GET | `/api/v1/catalog/models` | model:read | 公开模型目录 |
|
||||
| GET | `/api/v1/providers` | 认证用户 | 提供商列表 |
|
||||
| POST | `/api/v1/providers` | provider:manage | 创建提供商 |
|
||||
| GET | `/api/v1/providers/:id` | 认证用户 | 提供商详情 |
|
||||
| PATCH | `/api/v1/providers/:id` | provider:manage | 更新提供商 |
|
||||
| DELETE | `/api/v1/providers/:id` | provider:manage | 删除提供商 |
|
||||
| GET | `/api/v1/providers/:id/models` | 认证用户 | 模型列表 |
|
||||
| GET | `/api/v1/models` | 认证用户 | 模型列表 |
|
||||
| POST | `/api/v1/models` | model:manage | 创建模型 |
|
||||
| GET | `/api/v1/models/:id` | 认证用户 | 模型详情 |
|
||||
| PATCH | `/api/v1/models/:id` | model:manage | 更新模型 |
|
||||
| DELETE | `/api/v1/models/:id` | model:manage | 删除模型 |
|
||||
| GET | `/api/v1/keys` | - (本人) | API Key 列表 |
|
||||
| POST | `/api/v1/keys` | - (本人) | 创建 API Key |
|
||||
| DELETE | `/api/v1/keys/:id` | - (本人) | 撤销 API Key |
|
||||
| POST | `/api/v1/keys/:id/rotate` | - (本人) | 轮换 API Key |
|
||||
| GET | `/api/v1/usage` | - (本人) | 使用量统计 |
|
||||
|
||||
### 5.5 初始种子数据
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
# 联合调试后修复行动计划
|
||||
|
||||
> **日期**: 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<Utc> |
|
||||
| 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 修复后添加基础的运维监控。
|
||||
Reference in New Issue
Block a user