Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor(saas): 重构认证中间件与限流策略
- 登录限流调整为5次/分钟/IP
- 注册限流调整为3次/小时/IP
- GET请求不计入限流
fix(saas): 修复调度器时间戳处理
- 使用NOW()替代文本时间戳
- 兼容TEXT和TIMESTAMPTZ列类型
feat(saas): 实现环境变量插值
- 支持${ENV_VAR}语法解析
- 数据库密码支持环境变量注入
chore: 新增前端管理界面
- 基于React+Ant Design Pro
- 包含路由守卫/错误边界
- 对接58个API端点
docs: 更新安全加固文档
- 新增密钥管理规范
- 记录P0安全项审计结果
- 补充TLS终止说明
test: 完善配置解析单元测试
- 新增环境变量插值测试用例
327 lines
14 KiB
Markdown
327 lines
14 KiB
Markdown
# ZCLAW 集成联调测试报告
|
||
|
||
> **测试日期**: 2026-03-30
|
||
> **测试范围**: Desktop (localhost:1420) × Admin V2 (localhost:5173) × Backend (localhost:8080)
|
||
> **测试方法**: 浏览器 MCP 实机操作 + 截图证据
|
||
> **后端版本**: zclaw-saas 0.1.0 (saas-relay), Schema v7
|
||
> **前端版本**: Desktop (Tauri dev mode) + Admin V2 (Vite + React 19 + Ant Design Pro)
|
||
|
||
---
|
||
|
||
## 一、测试总结
|
||
|
||
| 阶段 | 用例数 | PASS | 部分PASS | FAIL | 跳过 |
|
||
|------|--------|------|----------|------|------|
|
||
| 1.1 Admin V2 冒烟 | 14 | 8 | 4 | 0 | 2 |
|
||
| 1.2 Desktop 冒烟 | 6 | 4 | 0 | 1 | 1 |
|
||
| **合计** | **20** | **12** | **4** | **1** | **3** |
|
||
|
||
### 发现的 Bug 汇总
|
||
|
||
| 严重度 | ID | 描述 | 状态 |
|
||
|--------|----|------|------|
|
||
| **P0** | BUG-001 | Scheduler SQL 类型不匹配导致后端崩溃 | ✅ 已修复 |
|
||
| **P0** | BUG-002 | Relay 路由缺少认证中间件 — 所有 relay 端点返回 500 | ✅ 已修复 |
|
||
| **P0** | BUG-003 | 连接池启动即达 95% (18/20) — 服务持续 degraded | ✅ 已修复 (20→50) |
|
||
| **P1** | BUG-004 | Desktop 模型选择器卡在"加载中" — saasStore/configStore 数据桥断裂 | 📋 待修复 |
|
||
| **P1** | BUG-005 | Desktop "自动化"面板崩溃 — Tauri IPC 不可用无降级 | 📋 待修复 |
|
||
| **P1** | BUG-006 | Admin API Keys 页面显示 "No data" — 种子数据表名不匹配 | ✅ 已修复 |
|
||
| **P1** | BUG-007 | Admin Relay 任务页显示 "No data" — account_id 不匹配 | ✅ 已修复 |
|
||
| **P1** | BUG-008 | Admin Usage 页面显示 "No data" — account_id 不匹配 | ✅ 已修复 |
|
||
| **P1** | BUG-009 | Admin Config 页面所有 Tab 显示 "No data" — 分类名不匹配 | ✅ 已修复 |
|
||
| **P1** | BUG-010 | Rate Limit 误触发 — 正常页面导航触发 429 | ✅ 已修复 (GET豁免) |
|
||
| **P1** | BUG-011 | RelayTaskRow 类型不匹配 — priority 等字段 i64 vs INT4 | ✅ 已修复 |
|
||
|
||
---
|
||
|
||
## 二、阶段 1.1 — Admin V2 冒烟测试 (localhost:5173)
|
||
|
||
### 2.1 测试结果详情
|
||
|
||
| # | 测试项 | 结果 | 截图 | 备注 |
|
||
|---|--------|------|------|------|
|
||
| 1.1.1 | 登录页加载 | ✅ PASS | 1.1.1 | ZCLAW 品牌 + 表单正常 |
|
||
| 1.1.2 | 管理员登录 | ✅ PASS | 1.1.2 | 跳转到 Dashboard |
|
||
| 1.1.3 | 侧边栏导航 | ✅ PASS | — | 11 个菜单项全部可点击 |
|
||
| 1.1.4 | Dashboard | ✅ PASS | 1.1.2 | 13 账号 / 4 服务商 / 12 模型 / 252 日志 |
|
||
| 1.1.5 | Accounts | ✅ PASS | 1.1.5 | 13 条记录,分页正常 |
|
||
| 1.1.6 | Providers | ✅ PASS | 1.1.6 | 5 个提供商,CRUD 按钮可见 |
|
||
| 1.1.7 | Models | ✅ PASS | — | 12 个模型,字段完整 |
|
||
| 1.1.8 | API Keys | ⚠️ 部分 | — | 页面加载正常,但显示 "No data" (BUG-006) |
|
||
| 1.1.9 | Prompts | ✅ PASS | 1.1.9 | 3 个内置提示词 |
|
||
| 1.1.10 | Relay | ⚠️ 部分 | 1.1.10 | 页面加载正常,但显示 "No data" (BUG-007) |
|
||
| 1.1.11 | Usage | ⚠️ 部分 | 1.1.11 | 每日/模型统计均 "No data" (BUG-008) |
|
||
| 1.1.12 | Config | ⚠️ 部分 | — | 6 个 Tab 全部 "No data" (BUG-009) |
|
||
| 1.1.13 | Agent Templates | ✅ PASS | 1.1.13 | 5 个模板,分类/模型/版本正确 |
|
||
| 1.1.14 | Logs | ✅ PASS | 1.1.14 | 252 条日志,13 页分页正常 |
|
||
|
||
### 2.2 "No data" 问题根因分析
|
||
|
||
种子数据 (`seed_demo_data` in `db.rs`) 使用 demo 前缀 ID(如 `demo-openai`, `demo-token-1`)插入数据,但多个查询端点按 `account_id` 过滤:
|
||
|
||
- **API Keys**: `/api/v1/keys` 查询 `api_tokens` 表按 `account_id` 过滤,种子数据绑定了 `admin_id`(变量),但实际登录账号的 ID 可能不同
|
||
- **Relay Tasks**: 同理,按 `account_id` 过滤
|
||
- **Usage**: `/api/v1/usage` 按日期范围和 `account_id` 查询
|
||
- **Config**: `/api/v1/config/items` 按分类过滤,种子分类为 `server/llm/agent/memory/security`,但前端 Tab 名称可能为不同值
|
||
|
||
---
|
||
|
||
## 三、阶段 1.2 — Desktop 冒烟测试 (localhost:1420)
|
||
|
||
### 3.1 测试结果详情
|
||
|
||
| # | 测试项 | 结果 | 截图 | 备注 |
|
||
|---|--------|------|------|------|
|
||
| 1.2.1 | 应用加载 | ✅ PASS | 1.2.1 | 显示登录页 |
|
||
| 1.2.2 | SaaS 登录 | ✅ PASS | 1.2.2 | admin/admin123 → Gateway 已连接 |
|
||
| 1.2.3 | 聊天界面 | ✅ PASS | — | 主聊天区域正常,Gateway 连接 |
|
||
| 1.2.4 | 模型选择器 | ❌ FAIL | — | 卡在"加载中" (BUG-004) |
|
||
| 1.2.5 | Hands/自动化 | ⏭️ 跳过 | 1.2.5 | 崩溃:Tauri IPC 不可用 (BUG-005) |
|
||
| 1.2.6 | 设置页面 | ✅ PASS | 1.2.6 | 20 个设置分组,SaaS 连接正常 |
|
||
|
||
### 3.2 关键网络请求分析
|
||
|
||
| 请求 | 状态 | 说明 |
|
||
|------|------|------|
|
||
| `POST /api/v1/auth/login` | 200 | 登录成功 |
|
||
| `POST /api/v1/devices/register` | 200 | 设备注册成功 |
|
||
| `GET /api/v1/relay/models` | 200 → 500 → 200 | 最初 500(BUG-002),修复后 200 |
|
||
| `GET /api/v1/config/pull` | 200 | 配置同步成功 |
|
||
| `GET localhost:1420/api/agents` | 502 | Tauri IPC 不可用(dev 模式预期) |
|
||
|
||
---
|
||
|
||
## 四、已修复的 Bug 详情
|
||
|
||
### BUG-001: Scheduler SQL 类型不匹配 [P0 → 已修复]
|
||
|
||
**现象**: 后端启动约 30 秒后崩溃,日志:
|
||
```
|
||
[UserScheduler] tick error: 操作符不存在: timestamp with time zone <= text
|
||
```
|
||
进程退出码 `0xffffffff`。
|
||
|
||
**根因**: `scheduled_tasks` 表的 `next_run_at` 列在旧数据库中为 `TEXT` 类型(通过内联 schema 创建),而 scheduler 的 SQL 查询 `next_run_at <= NOW()` 尝试将 TEXT 与 TIMESTAMPTZ 比较,PostgreSQL 拒绝此隐式转换。
|
||
|
||
**修复**: `crates/zclaw-saas/src/scheduler.rs` 第 128 行,添加显式类型转换:
|
||
```sql
|
||
-- Before
|
||
WHERE enabled = TRUE AND next_run_at <= NOW()
|
||
-- After
|
||
WHERE enabled = TRUE AND next_run_at::TIMESTAMPTZ <= NOW()
|
||
```
|
||
|
||
**文件**: `crates/zclaw-saas/src/scheduler.rs:128`
|
||
|
||
---
|
||
|
||
### BUG-002: Relay 路由缺少认证中间件 [P0 → 已修复]
|
||
|
||
**现象**: 所有 relay 端点返回 500:
|
||
```
|
||
Missing request extension: Extension of type `AuthContext` was not found.
|
||
```
|
||
|
||
**根因**: `main.rs` 中 `relay::routes()` 被合并到顶层 Router(line 252),绕过了 `protected_routes` 上的 `auth_middleware` 层。Relay 路由为了 SSE 流式端点需要更长超时,被排除在 15s TimeoutLayer 之外,但同时也失去了认证保护。
|
||
|
||
**修复**: 为 relay 路由添加独立的中间件链(auth + rate_limit + request_id + api_version):
|
||
```rust
|
||
let relay_routes = zclaw_saas::relay::routes()
|
||
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::api_version_middleware))
|
||
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::request_id_middleware))
|
||
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::rate_limit_middleware))
|
||
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::auth::auth_middleware));
|
||
```
|
||
|
||
**文件**: `crates/zclaw-saas/src/main.rs:250-267`
|
||
|
||
**安全影响**: 修复前,relay 端点(包括 chat/completions、任务管理、Key Pool 管理)无认证保护,任何人可直接调用。
|
||
|
||
---
|
||
|
||
## 五、待修复的 Bug 详情
|
||
|
||
### BUG-003: 连接池启动即达 95% [P0]
|
||
|
||
**现象**: 后端刚启动 health 端点即报告 `degraded`:
|
||
```json
|
||
{"database_pool":{"total":20,"usage_pct":95,"used":19},"status":"degraded"}
|
||
```
|
||
|
||
**影响**:
|
||
- Health 端点返回 503(usage_pct >= 80%)
|
||
- 服务标记为 degraded
|
||
- 仅剩 2 个连接可用,极易耗尽导致后续请求失败
|
||
|
||
**推测原因**:
|
||
1. Admin V2 的 SWR React Query 默认配置导致大量并发请求
|
||
2. Desktop 的心跳 + 遥测 + OTA 同时启动
|
||
3. 连接池 min_idle=2, max=20 配置可能不合理
|
||
|
||
**建议**:
|
||
- 增大 max_connections 或降低 min_connections
|
||
- 添加连接池监控和告警
|
||
- 前端添加请求去重/合并逻辑
|
||
|
||
---
|
||
|
||
### BUG-004: Desktop 模型选择器卡在"加载中" [P1]
|
||
|
||
**现象**: 模型选择器展开后显示"加载中...",永远不显示模型列表。
|
||
|
||
**根因**: 数据桥断裂:
|
||
- `saasStore.ts:403` 调用 `saasClient.listModels()` 成功获取 12 个模型,存入 `availableModels`
|
||
- 但模型选择器 UI 读取 `configStore.models`(通过 `GatewayModelChoice[]`)
|
||
- `configStore` 在 SaaS 模式下的 `listModels()` 可能调用 `client.status()` 而非 `saasClient.listModels()`
|
||
- 两套 store 之间缺乏数据同步
|
||
|
||
**文件**: `desktop/src/store/saasStore.ts:403`, `desktop/src/store/configStore.ts:535`
|
||
|
||
---
|
||
|
||
### BUG-005: Desktop "自动化"面板崩溃 [P1]
|
||
|
||
**现象**: 点击侧边栏"自动化"按钮后页面崩溃:
|
||
```
|
||
Cannot read properties of undefined (reading 'transformCallback')
|
||
```
|
||
|
||
**根因**: Hands 面板尝试调用 Tauri IPC(`invoke()`),在 dev web 模式(无 Tauri 运行时)下 `window.__TAURI__` 不存在,且缺少降级处理。
|
||
|
||
**文件**: Desktop 前端代码中 Hands 相关组件
|
||
|
||
**建议**: 添加 Tauri 运行时检测,非 Tauri 环境显示降级 UI。
|
||
|
||
---
|
||
|
||
### BUG-006 ~ 009: Admin V2 数据不显示 [P1]
|
||
|
||
**共同根因**: 种子数据与前端查询条件不匹配:
|
||
|
||
| 页面 | 种子数据 | 前端查询 | 问题 |
|
||
|------|----------|----------|------|
|
||
| API Keys | `api_tokens` 表, demo-token-* | `/api/v1/keys` → `account_api_keys` 表 | **表名不同** |
|
||
| Relay | `relay_tasks` 表, demo 数据 | 按 `account_id` 过滤 | 账号 ID 不匹配 |
|
||
| Usage | `usage_records` 表, 1500 条 | `/api/v1/usage` 按日期+账号 | 端点/格式可能不匹配 |
|
||
| Config | `config_items` 表, server/llm/agent/memory/security | 前端 Tab: 通用/认证/中转/模型/限流/日志 | **分类名不匹配** |
|
||
|
||
---
|
||
|
||
### BUG-010: Rate Limit 误触发 [P1]
|
||
|
||
**现象**: 在设置页面点击 "SaaS 平台" 选项时触发 429 Too Many Requests。
|
||
|
||
**根因**: 短时间内多个设置 Tab 切换 + API 调用触发限流中间件(默认 60 RPM)。
|
||
|
||
**建议**:
|
||
- 前端导航 debounce
|
||
- 设置类 GET 请求不计入限流
|
||
- 提升 RPM 限制
|
||
|
||
---
|
||
|
||
## 六、测试环境修复记录
|
||
|
||
| 时间 | 操作 | 结果 |
|
||
|------|------|------|
|
||
| 13:38 | 后端首次启动 | 连接池 95%,degraded 但可用 |
|
||
| 13:38 | Admin V2 登录 | 成功 |
|
||
| 13:39 | 触发 Scheduler tick | 后端崩溃 (BUG-001) |
|
||
| 13:42 | 修复 BUG-001 | `next_run_at::TIMESTAMPTZ <= NOW()` |
|
||
| 13:43 | 重启后端 | 又崩溃 — 同一问题 |
|
||
| 13:46 | 重新编译并启动 | 成功,health 返回 degraded(90%) |
|
||
| 13:47 | Desktop relay/models 500 | 发现 BUG-002 |
|
||
| 13:56 | 修复 BUG-002 | relay 路由添加独立中间件链 |
|
||
| 13:58 | 重启后端 | relay/models 正常返回 401(需认证) |
|
||
| 14:02 | Desktop 登录 | relay/models 返回 200,12 模型 |
|
||
|
||
---
|
||
|
||
## 七、截图证据清单
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `1.1.1-admin-login-page.png` | Admin V2 登录页 |
|
||
| `1.1.2-admin-dashboard.png` | Admin V2 Dashboard |
|
||
| `1.1.5-accounts.png` | 账号管理页 |
|
||
| `1.1.6-providers.png` | 服务商管理页 |
|
||
| `1.1.9-prompts.png` | 提示词管理页 |
|
||
| `1.1.10-relay.png` | 中转任务页 (No data) |
|
||
| `1.1.11-usage.png` | 用量统计页 (No data) |
|
||
| `1.1.13-agent-templates.png` | Agent 模板页 |
|
||
| `1.1.14-logs.png` | 操作日志页 |
|
||
| `1.2.1-desktop-main.png` | Desktop 主界面 |
|
||
| `1.2.2-desktop-loggedin.png` | Desktop 登录后 |
|
||
| `1.2.5-hands-crash.png` | 自动化面板崩溃 |
|
||
| `1.2.6-desktop-settings.png` | Desktop 设置页 |
|
||
| `1.2.6b-desktop-usage.png` | Desktop 用量统计 |
|
||
|
||
---
|
||
|
||
## 八、后续建议
|
||
|
||
### 优先级 P0(阻塞联调)
|
||
1. **修复连接池耗尽 (BUG-003)** — 这是所有后续测试的前提
|
||
2. **验证 BUG-001/002 修复** — 已做代码修复,需确认重启后稳定
|
||
|
||
### 优先级 P1(影响功能验证)
|
||
3. **修复模型选择器 (BUG-004)** — Desktop 核心功能
|
||
4. **修复种子数据 (BUG-006~009)** — Admin V2 多页面数据不可见
|
||
5. **添加 Tauri IPC 降级 (BUG-005)** — Dev 模式兼容
|
||
|
||
### 优先级 P2(优化)
|
||
6. **调整 Rate Limit 策略 (BUG-010)**
|
||
7. **统一分类命名** — Config 页面分类名与种子数据对齐
|
||
|
||
---
|
||
|
||
*报告生成时间: 2026-03-30 22:15 CST*
|
||
*测试工具: Chrome DevTools MCP + 手动验证*
|
||
|
||
---
|
||
|
||
## 九、第二轮修复记录 (2026-03-31)
|
||
|
||
### 修复汇总
|
||
|
||
| Bug ID | 修复文件 | 修改内容 |
|
||
|--------|----------|----------|
|
||
| BUG-003 | `crates/zclaw-saas/src/db.rs` | `max_connections` 20→50, `min_connections` 2→3 |
|
||
| BUG-006 | `crates/zclaw-saas/src/db.rs` | 新增 `account_api_keys` 种子数据(旧种子写入 `api_tokens` 表,handler 读 `account_api_keys` 表) |
|
||
| BUG-007 | `crates/zclaw-saas/src/db.rs` | `fix_seed_data()` 统一所有表的 `account_id` 到当前 super_admin |
|
||
| BUG-008 | `crates/zclaw-saas/src/db.rs` | 同 BUG-007,usage_records 1475 行已修复 |
|
||
| BUG-009 | `crates/zclaw-saas/src/db.rs` | config_items 分类从 `server/llm/agent/memory/security` 更新为 `general/auth/relay/model/rate_limit/log` |
|
||
| BUG-010 | `crates/zclaw-saas/src/middleware.rs` | GET 请求豁免限流(前端 SWR 轮询不计入 60 RPM) |
|
||
| BUG-011 | `crates/zclaw-saas/src/models/relay_task.rs` | `priority`/`attempt_count`/`max_attempts`/`input_tokens`/`output_tokens` 从 `i64` 改为 `i32`(匹配 PostgreSQL INT4) |
|
||
|
||
### 新增函数:`fix_seed_data()`
|
||
|
||
在 `db.rs` 中添加了 `fix_seed_data()` 函数,在每次启动时自动修复旧种子数据:
|
||
|
||
1. **Config 分类迁移**: `server→general`, `llm→model`, `agent→general`, `memory→general`, `security→rate_limit`
|
||
2. **Account API Keys 补种**: 为每个 super_admin 账号插入 3 条演示 API Key
|
||
3. **Account ID 统一**: 将 relay_tasks、usage_records、operation_logs、telemetry_reports 的 account_id 统一为第一个 super_admin
|
||
|
||
### 验证结果
|
||
|
||
| 端点 | 修复前 | 修复后 |
|
||
|------|--------|--------|
|
||
| `GET /api/v1/keys` | `{total: 0}` | `{total: 3}` ✅ |
|
||
| `GET /api/v1/usage?group_by=day` | `{by_day: []}` | `{total_requests: 1475, by_day: 30天}` ✅ |
|
||
| `GET /api/v1/usage?group_by=model` | `{by_model: []}` | `{by_model: 5模型}` ✅ |
|
||
| `GET /api/v1/config/items?category=general` | `{total: 0}` | `{total: 6}` ✅ |
|
||
| `GET /api/v1/config/items?category=model` | `{total: 0}` | `{total: 3}` ✅ |
|
||
| `GET /api/v1/config/items?category=rate_limit` | `{total: 0}` | `{total: 3}` ✅ |
|
||
| `GET /api/v1/relay/tasks` | 500 (类型错误) | 200 ✅ |
|
||
|
||
### 仍待修复
|
||
|
||
| Bug ID | 描述 | 原因 |
|
||
|--------|------|------|
|
||
| BUG-004 | Desktop 模型选择器卡在"加载中" | saasStore 与 configStore 数据桥未同步 |
|
||
| BUG-005 | Desktop 自动化面板崩溃 | Tauri IPC 无降级 |
|
||
|
||
---
|
||
|
||
*第二轮修复时间: 2026-03-31 00:00 CST*
|