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: 完善配置解析单元测试
- 新增环境变量插值测试用例
14 KiB
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 行,添加显式类型转换:
-- 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):
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:
{"database_pool":{"total":20,"usage_pct":95,"used":19},"status":"degraded"}
影响:
- Health 端点返回 503(usage_pct >= 80%)
- 服务标记为 degraded
- 仅剩 2 个连接可用,极易耗尽导致后续请求失败
推测原因:
- Admin V2 的 SWR React Query 默认配置导致大量并发请求
- Desktop 的心跳 + 遥测 + OTA 同时启动
- 连接池 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(阻塞联调)
- 修复连接池耗尽 (BUG-003) — 这是所有后续测试的前提
- 验证 BUG-001/002 修复 — 已做代码修复,需确认重启后稳定
优先级 P1(影响功能验证)
- 修复模型选择器 (BUG-004) — Desktop 核心功能
- 修复种子数据 (BUG-006~009) — Admin V2 多页面数据不可见
- 添加 Tauri IPC 降级 (BUG-005) — Dev 模式兼容
优先级 P2(优化)
- 调整 Rate Limit 策略 (BUG-010)
- 统一分类命名 — 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() 函数,在每次启动时自动修复旧种子数据:
- Config 分类迁移:
server→general,llm→model,agent→general,memory→general,security→rate_limit - Account API Keys 补种: 为每个 super_admin 账号插入 3 条演示 API Key
- 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