feat: 新增管理后台前端项目及安全加固
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: 完善配置解析单元测试
- 新增环境变量插值测试用例
309
docs/audit-2026-03-30.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# ZCLAW 全面项目审计报告
|
||||
|
||||
> **审计日期**: 2026-03-30
|
||||
> **审计范围**: 系统架构、代码质量、性能、安全、业务功能、用户体验
|
||||
> **审计基准**: v0.7.0,基于 F16/S2/S4/S8 完成后的代码状态
|
||||
> **审计方法**: 5 个并行子代理 + 直接代码分析
|
||||
|
||||
---
|
||||
|
||||
## 一、执行摘要
|
||||
|
||||
### 总体评级: **B+ (良好)**
|
||||
|
||||
| 维度 | 评级 | 关键发现 |
|
||||
|------|------|----------|
|
||||
| 系统架构 | A- | 10 Crate 分层清晰,SaaS 独立,feature gate 全覆盖 |
|
||||
| 代码质量 | B | 0 编译警告,453 测试,但多文件超限,53 处 Promise\<any\> |
|
||||
| 安全性 | B | Cookie 已实现,SQL 全参数化,但登出未撤 token,无 TLS |
|
||||
| 性能 | B | 连接池健康检查完备,但 SSE 不响应 shutdown,表无自动清理 |
|
||||
| 业务功能 | A- | 93 API,9 Hands,66 Skills,前后端对齐,SaaS 定时任务执行未闭环 |
|
||||
|
||||
### 关键数字
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| Rust 代码行数 | 54,380 |
|
||||
| SaaS API 端点 | 93 (10 模块) |
|
||||
| Tauri 命令 | 106 |
|
||||
| 测试数量 | 453 (254 unit + 199 async) |
|
||||
| Hands | 9 |
|
||||
| Skills | 66 SKILL.md |
|
||||
| Pipeline YAML | 7 |
|
||||
| 编译警告 | 0 (项目自有) |
|
||||
| TypeScript 错误 | 0 |
|
||||
|
||||
---
|
||||
|
||||
## 二、系统架构审计
|
||||
|
||||
### 2.1 Crate 分层 ✅
|
||||
|
||||
```
|
||||
L1: zclaw-types (无 zclaw 依赖) ✅
|
||||
L1.5: zclaw-growth (仅依赖 types) — 目录在 L5 但依赖在 L1
|
||||
L2: zclaw-memory (→ types)
|
||||
L3: zclaw-runtime (→ types, memory, growth)
|
||||
L4: zclaw-kernel (→ types, memory, runtime, protocols, hands, skills)
|
||||
L5: zclaw-skills (→ types), zclaw-hands (→ types, runtime), zclaw-protocols (→ types)
|
||||
zclaw-pipeline (→ types, runtime, kernel, skills, hands)
|
||||
独立: zclaw-saas (仅依赖 zclaw-types) ✅
|
||||
```
|
||||
|
||||
**架构代理验证结果**:
|
||||
- ✅ 无循环依赖(DAG)
|
||||
- ✅ SaaS 独立于 Kernel
|
||||
- ✅ workspace members 完整
|
||||
- ✅ multi-agent feature 传播链正确: desktop → kernel → protocols/a2a
|
||||
- ✅ wasm feature gate 完整
|
||||
- ✅ dev-server feature gate 完整
|
||||
- ℹ️ `zclaw-growth` 目录位置与依赖层级不一致(不影响功能)
|
||||
- ℹ️ `zclaw-pipeline` 声明了 `zclaw-kernel` 依赖但代码中未直接 import
|
||||
|
||||
### 2.2 数据流完整性 ✅
|
||||
|
||||
| 链路 | 状态 | 覆盖 |
|
||||
|------|------|------|
|
||||
| Desktop UI → Store → Tauri → Kernel → LLM/Hands/Skills | ✅ 完整 | 106 个 Tauri 命令 |
|
||||
| Admin V2 → Axios → SaaS API → PostgreSQL | ✅ 完整 | 12 个 service 文件 |
|
||||
| Pipeline YAML → Executor → LLM/Skill/Hand Actions | ✅ 完整 | 三种 Action driver 均已接通 |
|
||||
|
||||
---
|
||||
|
||||
## 三、代码质量审计
|
||||
|
||||
### 3.1 编译状态 ✅
|
||||
|
||||
- `cargo check --workspace`: **0 项目警告**
|
||||
- `admin-v2 tsc --noEmit`: **通过**
|
||||
- `desktop tsc --noEmit`: **通过**
|
||||
|
||||
### 3.2 大文件审计 ⚠️
|
||||
|
||||
**Rust 文件(>800 行)**:
|
||||
|
||||
| 文件 | 行数 | 建议 |
|
||||
|------|------|------|
|
||||
| `kernel_commands.rs` | **2185** | 按功能域拆分 |
|
||||
| `lib.rs` (desktop) | **1518** | 命令定义独立 |
|
||||
| `kernel.rs` | **1490** | 拆分 a2a/scheduler 子模块 |
|
||||
| `pipeline_commands.rs` | **1391** | 按 stage 拆分 |
|
||||
| `generation.rs` | 1080 | 逻辑连贯,可保持 |
|
||||
| `quiz.rs` | 1027 | 题目模板导致 |
|
||||
| `openai.rs` | 912 | 拆分 streaming 子模块 |
|
||||
| `director.rs` | 912 | 拆分 turn/submission |
|
||||
| `loop_runner.rs` | 896 | 拆分 tool execution |
|
||||
| `store.rs` | 804 | 拆分 query builder |
|
||||
|
||||
**TypeScript 文件(>800 行)**:
|
||||
|
||||
| 文件 | 行数 | 类型安全 |
|
||||
|------|------|----------|
|
||||
| `intelligence-client.ts` | 1471 | 低风险 |
|
||||
| `kernel-client.ts` | 1343 | 低风险 |
|
||||
| `saas-client.ts` | 1290 | 低风险 |
|
||||
| **`gateway-client.ts`** | **1227** | **53 处 Promise\<any\>** |
|
||||
| **`gateway-api.ts`** | **672** | **同上** |
|
||||
|
||||
### 3.3 unwrap() 使用 ⚠️
|
||||
|
||||
- 68 个 Rust 文件含 unwrap(),生产代码约 108 处
|
||||
- `store.rs` 独占 75 处(最高风险)
|
||||
- **P0 风险**: `extraction_adapter.rs:306-310` 链式 unwrap(LLM 返回异常格式时 panic)
|
||||
- **P1 风险**: `context_builder.rs` 多处 HashMap `get_mut().unwrap()`
|
||||
|
||||
### 3.4 TypeScript 类型安全
|
||||
|
||||
- 0 处 `@ts-ignore` / `@ts-nocheck`
|
||||
- **53 处 `Promise<any>`** 在 gateway-client.ts + gateway-api.ts(最大类型安全盲区)
|
||||
- 6 处静默 `catch(() => {})`(saasStore.ts 3 处应添加日志)
|
||||
- 33 处 `#[allow(dead_code)]`(半数标注 "reserved",需定期审视)
|
||||
|
||||
### 3.5 测试覆盖
|
||||
|
||||
| Crate | 测试数 | 评价 |
|
||||
|-------|--------|------|
|
||||
| zclaw-saas | 111 | 良好 |
|
||||
| zclaw-growth | 75 | 良好 |
|
||||
| zclaw-pipeline | 59 | 良好 |
|
||||
| zclaw-types | 57 | 良好 |
|
||||
| zclaw-runtime | 42 | 中等 |
|
||||
| zclaw-kernel | 41 | 需提升 |
|
||||
| zclaw-skills | 22 | 需提升 |
|
||||
| zclaw-hands | 21 | 需提升 |
|
||||
| zclaw-memory | 20 | 需提升 |
|
||||
| zclaw-protocols | **5** | **不足** |
|
||||
|
||||
---
|
||||
|
||||
## 四、安全审计
|
||||
|
||||
### 4.1 安全发现汇总
|
||||
|
||||
| 级别 | ID | 问题 | 文件 |
|
||||
|------|-----|------|------|
|
||||
| HIGH | SEC-02 | `saas-config.toml` 含明文 DB 密码 | saas-config.toml:16 |
|
||||
| HIGH | SEC-03 | ShellSkill SKILL.md 命令注入信任模型 | skills/runner.rs:125 |
|
||||
| HIGH | SEC-04 | 服务端无 TLS 终止 | saas/main.rs:72 |
|
||||
| HIGH | SEC-05 | JWT debug fallback 密钥硬编码 | saas/config.rs:238 |
|
||||
| MED | SEC-06 | 登出未撤销服务端 refresh token | saas/auth/handlers.rs:506 |
|
||||
| MED | SEC-07 | 注册端点无验证码/邀请机制 | saas/auth/handlers.rs:54 |
|
||||
| MED | SEC-08 | 角色权限缓存无 TTL/失效 | saas/auth/handlers.rs:397 |
|
||||
| MED | SEC-09 | 登录缺少专门暴力破解保护 | saas/auth/handlers.rs:160 |
|
||||
| MED | SEC-10 | 生产 CORS 配置含 localhost | saas-config.toml:12 |
|
||||
| MED | SEC-11 | Cookie Secure 在非 HTTPS 环境失效 | saas/auth/handlers.rs:29 |
|
||||
| MED | SEC-13 | TOTP 加密密钥从 JWT Secret 派生 | saas/config.rs:285 |
|
||||
| LOW | SEC-14 | PythonSkill 脚本路径信任模型 | skills/runner.rs:83 |
|
||||
| LOW | SEC-15 | Rate limit 信任 X-Forwarded-For | saas/middleware.rs:109 |
|
||||
|
||||
### 4.2 安全优势
|
||||
|
||||
| 项目 | 状态 |
|
||||
|------|------|
|
||||
| SQL 注入防护 | ✅ 100% 参数化,0 字符串拼接 |
|
||||
| 密码哈希 | ✅ Argon2id + spawn_blocking |
|
||||
| Refresh Token Rotation | ✅ 一次性使用 + SHA-256 hash 存储 |
|
||||
| API Key 加密 | ✅ AES-256-GCM + 随机 nonce |
|
||||
| 错误信息处理 | ✅ 500 不泄露内部细节 |
|
||||
| 审计日志 | ✅ 覆盖所有关键操作 + IP |
|
||||
| TOTP 暴力破解保护 | ✅ 5 次失败锁定 10 分钟 |
|
||||
| XSS 防护 | ✅ 唯一 innerHTML 用 DOMPurify |
|
||||
| 账号隔离 | ✅ 所有查询强制 account_id |
|
||||
| CORS 安全 | ✅ 生产强制白名单 |
|
||||
|
||||
---
|
||||
|
||||
## 五、性能审计
|
||||
|
||||
### 5.1 严重性能问题
|
||||
|
||||
| 问题 | 影响 | 文件 |
|
||||
|------|------|------|
|
||||
| SSE 流不检查 CancellationToken | graceful shutdown 等待最长 5 分钟 | relay/service.rs |
|
||||
| 数据表无自动清理 | operation_logs/usage_records/relay_tasks 无限增长 | scheduler.rs |
|
||||
| 连接池偏小 (max=20) | 高并发 SSE 场景不足 | db.rs:12 |
|
||||
| Scheduler 循环不监听 shutdown token | 后台 task 不会被取消 | scheduler.rs:55,78,111 |
|
||||
|
||||
### 5.2 中等性能问题
|
||||
|
||||
| 问题 | 文件 |
|
||||
|------|------|
|
||||
| relay_tasks 缺 (account_id, status) 复合索引 | relay/handlers.rs:31 |
|
||||
| SSE usage 跳过后 task 永久 "processing" | relay/service.rs:302 |
|
||||
| OTA 提示词 N+1 查询(每模板 2 次) | prompt/service.rs:242 |
|
||||
| totp_fail_counts 无清理机制 | state.rs:26 |
|
||||
| `operation_logs` COUNT(*) 无时间范围 | account/handlers.rs:144 |
|
||||
|
||||
### 5.3 性能优势
|
||||
|
||||
| 项目 | 设计 |
|
||||
|------|------|
|
||||
| 连接池健康检查 | 80% 水位 503 + 3s 超时 |
|
||||
| TCP keepalive | 60s/10s/3次 + SO_LINGER 1s |
|
||||
| SSE 背压 | bounded channel(128) |
|
||||
| SSE 并发限制 | Semaphore(16) |
|
||||
| DashMap 死锁 | RefMut 在 await 前释放 |
|
||||
| 前端无轮询 | TanStack Query 按需触发 |
|
||||
|
||||
---
|
||||
|
||||
## 六、业务功能审计
|
||||
|
||||
### 6.1 SaaS API 完整度 ✅
|
||||
|
||||
| 模块 | 端点数 | CRUD |
|
||||
|------|--------|------|
|
||||
| auth (含 TOTP) | 9 | 完整认证流程 |
|
||||
| account (含 tokens/devices/logs/stats) | 12 | ✅ |
|
||||
| provider | 6 | ✅ C/R/U/D |
|
||||
| model | 5 | ✅ C/R/U/D |
|
||||
| api_keys (含 rotate) | 5 | ✅ |
|
||||
| relay (含 key pool) | 9 | ✅ |
|
||||
| config/migration | 12 | ✅ + sync/diff/pull |
|
||||
| role + permission_template | 11 | ✅ |
|
||||
| prompt (含 versions/rollback) | 10 | ✅ |
|
||||
| agent_template | 5 | ✅ |
|
||||
| scheduled_task | 5 | ✅ |
|
||||
| telemetry | 4 | ✅ |
|
||||
| health | 1 | ✅ |
|
||||
| **总计** | **93** | **完整** |
|
||||
|
||||
### 6.2 前后端对齐 ✅
|
||||
|
||||
- Admin V2: 12 个 service 文件 → 后端路由全部匹配
|
||||
- baseURL `/api/v1` + Vite proxy → 无断链
|
||||
- `withCredentials: true` → Cookie 自动发送
|
||||
- **后端就绪但无 Admin UI**: Role 管理、TOTP、Prompt 版本、定时任务管理
|
||||
|
||||
### 6.3 功能缺口
|
||||
|
||||
| 功能 | 后端 | 前端 Admin | 状态 |
|
||||
|------|------|-----------|------|
|
||||
| Role CRUD | ✅ 4 端点 | ❌ | 后端就绪 |
|
||||
| Permission Template | ✅ 4 端点 | ❌ | 后端就绪 |
|
||||
| TOTP 2FA | ✅ 3 端点 | ❌ | 后端就绪 |
|
||||
| Prompt 版本管理 | ✅ 4 端点 | ❌ | 后端就绪 |
|
||||
| 定时任务管理 | ✅ 5 端点 | ❌ | 后端就绪 |
|
||||
| SaaS 定时任务执行 | ⚠️ 仅状态管理 | — | **未闭环** |
|
||||
| Kernel 定时任务执行 | ✅ TriggerManager | ✅ | 完整闭环 |
|
||||
|
||||
### 6.4 Approval 流程 ✅
|
||||
|
||||
完整闭环: 触发 → 检查 → 创建审批 → 前端通知 → 审批/拒绝 → 执行 → 结果通知
|
||||
|
||||
---
|
||||
|
||||
## 七、待改进项(按优先级)
|
||||
|
||||
### P0 — 必须修复
|
||||
|
||||
| ID | 问题 | 影响 |
|
||||
|----|------|------|
|
||||
| P0-1 | SSE 流不检查 CancellationToken | 停机等待最长 5 分钟 |
|
||||
| P0-2 | 数据表无自动清理 (logs/usage/relay_tasks) | 磁盘耗尽 + 查询退化 |
|
||||
| P0-3 | `store.rs` 75 处 unwrap | 数据库异常导致 panic |
|
||||
| P0-4 | `extraction_adapter.rs` 链式 unwrap | LLM 异常输出导致 panic |
|
||||
| P0-5 | 登出未撤销服务端 refresh token | XSS 后 token 仍可用 |
|
||||
|
||||
### P1 — 应该修复
|
||||
|
||||
| ID | 问题 | 影响 |
|
||||
|----|------|------|
|
||||
| P1-1 | `kernel_commands.rs` 2185 行 | 可维护性 |
|
||||
| P1-2 | `gateway-client.ts` 53 处 Promise\<any\> | 类型安全盲区 |
|
||||
| P1-3 | 连接池 max=20 偏小 | 高并发不足 |
|
||||
| P1-4 | relay_tasks 缺复合索引 | 查询性能 |
|
||||
| P1-5 | Scheduler 循环不响应 shutdown | 优雅停机不完整 |
|
||||
| P1-6 | `saas-config.toml` 含明文密码 | 安全风险 |
|
||||
| P1-7 | Cookie Secure 在 localhost 失效 | 开发环境认证失败 |
|
||||
| P1-8 | 登录缺暴力破解保护 | 安全风险 |
|
||||
| P1-9 | `zclaw-protocols` 仅 5 个测试 | 回归风险 |
|
||||
|
||||
### P2 — 可以改进
|
||||
|
||||
| ID | 问题 | 影响 |
|
||||
|----|------|------|
|
||||
| P2-1 | 多个文件 >800 行 | 代码组织 |
|
||||
| P2-2 | 角色权限缓存无失效机制 | 降权不即时生效 |
|
||||
| P2-3 | `sqlx-postgres 0.7.4` 未来兼容性 | 编译噪音 |
|
||||
| P2-4 | SaaS 定时任务执行未闭环 | 功能不完整 |
|
||||
| P2-5 | `scheduled_tasks` 路径缺 `/v1` 前缀 | API 一致性 |
|
||||
| P2-6 | OTA 提示词 N+1 查询 | 性能 |
|
||||
|
||||
---
|
||||
|
||||
## 八、架构优势
|
||||
|
||||
1. **SaaS 独立部署** — 不依赖 Kernel,可独立扩展
|
||||
2. **Feature gate 分离** — multi-agent 功能可选
|
||||
3. **Cookie + Header 双路径认证** — 向后兼容 + 安全
|
||||
4. **声明式 Scheduler** — TOML 配置驱动
|
||||
5. **参数化 SQL 全覆盖** — 零注入风险
|
||||
6. **连接池健康检查** — 80% 水位自动降级
|
||||
7. **Argon2id 密码哈希** — 业界最佳实践
|
||||
8. **审计日志全覆盖** — 所有关键操作 + IP
|
||||
9. **SSE 背压设计** — bounded channel 防内存溢出
|
||||
|
||||
---
|
||||
|
||||
*审计完成。5 个并行子代理深度审计,覆盖架构/代码/安全/性能/业务全维度。*
|
||||
@@ -1,8 +1,8 @@
|
||||
# ZCLAW 功能全景文档
|
||||
|
||||
> **版本**: v0.8.0
|
||||
> **更新日期**: 2026-03-29
|
||||
> **项目状态**: 完整 Rust Workspace 架构,11 个核心 Crates,70 技能,Pipeline DSL + Smart Presentation + Agent Growth System + SaaS 平台
|
||||
> **版本**: v0.8.1
|
||||
> **更新日期**: 2026-03-30
|
||||
> **项目状态**: 完整 Rust Workspace 架构,10 个核心 Crates,70 技能,Pipeline DSL + Smart Presentation + Agent Growth System + SaaS 平台
|
||||
> **整体完成度**: ~87% (核心功能完整,SaaS 平台全面上线,Worker + Scheduler 系统上线,记忆闭环接通)
|
||||
|
||||
---
|
||||
@@ -31,7 +31,7 @@
|
||||
|------|------|--------|---------|
|
||||
| [00-agent-memory.md](02-intelligence-layer/00-agent-memory.md) | Agent 记忆 | L4 (90%) | pre-hook (FTS5+TF-IDF+Embedding) |
|
||||
| [01-identity-evolution.md](02-intelligence-layer/01-identity-evolution.md) | 身份演化 | L3 (90%) | pre-hook (SOUL.md) |
|
||||
| [06-context-compaction.md](02-intelligence-layer/06-context-compaction.md) | 上下文压缩 | L3 (90%) | 内核 AgentLoop 集成 |
|
||||
| [06-context-compaction.md](02-intelligence-layer/06-context-compaction.md) | 上下文压缩 | L4 (90%) | 内核中间件链集成 (CompactionMiddleware) |
|
||||
| [03-reflection-engine.md](02-intelligence-layer/03-reflection-engine.md) | 自我反思 | L3 (85%) | post-hook (自动触发) |
|
||||
| [04-heartbeat-engine.md](02-intelligence-layer/04-heartbeat-engine.md) | 心跳巡检 | L3 (90%) | post-hook (持久化) |
|
||||
| [05-autonomy-manager.md](02-intelligence-layer/05-autonomy-manager.md) | 自主授权 | L3 (75%) | RightPanel UI |
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| **Rust Crates** | **11** (types, memory, runtime, kernel, skills, hands, protocols, pipeline, growth, channels, saas) |
|
||||
| **Rust Crates** | **10** (types, memory, runtime, kernel, skills, hands, protocols, pipeline, growth, saas) —
|
||||
| **SKILL.md 文件** | **70** |
|
||||
| **Hands 总数** | **11** (9 启用, 2 禁用: Predictor, Lead) |
|
||||
| **Pipeline 模板** | **5** |
|
||||
@@ -127,10 +127,10 @@ zclaw-kernel (L4: 核心协调, 11 Hands, 70 Skills) — 85%
|
||||
↑
|
||||
┌───┴───┬───────┬───────────┬──────────┬────────┐
|
||||
│ │ │ │ │ │
|
||||
skills hands protocols pipeline growth channels
|
||||
(80%) (85%) (75%) (90%) (95%) (规划中)
|
||||
skills hands protocols pipeline growth
|
||||
(80%) (85%) (75%) (90%) (95%) (已移除)
|
||||
|
||||
zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 95%
|
||||
zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 97%
|
||||
```
|
||||
|
||||
---
|
||||
@@ -167,6 +167,7 @@ zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 95%
|
||||
|
||||
| 日期 | 版本 | 变更内容 |
|
||||
|------|------|---------|
|
||||
| 2026-03-30 | v0.8.1 | Sprint 5 "稳定清扫": Axum CLOSE_WAIT 修复 (CancellationToken + TCP keepalive + SO_LINGER),E2E 测试重新启用 (去掉 test.skip),dead code 注解审计 (36→<10) |
|
||||
| 2026-03-29 | v0.8.0 | SaaS 后端架构重构完成:Worker 系统 (5 Worker + mpsc 异步调度),声明式 Scheduler (TOML 配置),SQL 迁移系统 (Schema v6 + TIMESTAMPTZ),多环境配置 (ZCLAW_ENV),连接池优化 (50 max/5 min),速率限制优化 (无锁 AtomicU32);记忆闭环修复:extraction_adapter.rs 实现 TauriExtractionDriver,BREAK-01 已修复 |
|
||||
| 2026-03-29 | v0.7.0 | 文档同步:SKILL 数量 70, Tauri 命令 130+ (含 Browser/Intelligence/Memory/CLI/SecureStorage), Hands 11 (9 启用+2 禁用), 智能层完成度修正 |
|
||||
| 2026-03-28 | v0.7.0 | 基于 2026-03-28 代码状态全面更新:SaaS 平台 76+ API 路由/9 模块/25 表,58+ Tauri 命令,8 LLM Provider,3 种连接模式 |
|
||||
|
||||
326
docs/integration-test/report.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# 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*
|
||||
BIN
docs/integration-test/screenshots/1.1.1-admin-login-page.png
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
docs/integration-test/screenshots/1.1.10-relay.png
Normal file
|
After Width: | Height: | Size: 410 KiB |
BIN
docs/integration-test/screenshots/1.1.11-usage.png
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
docs/integration-test/screenshots/1.1.13-agent-templates.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
docs/integration-test/screenshots/1.1.14-logs.png
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
docs/integration-test/screenshots/1.1.2-admin-dashboard.png
Normal file
|
After Width: | Height: | Size: 550 KiB |
BIN
docs/integration-test/screenshots/1.1.5-accounts.png
Normal file
|
After Width: | Height: | Size: 684 KiB |
BIN
docs/integration-test/screenshots/1.1.6-providers.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
docs/integration-test/screenshots/1.1.8-api-keys.png
Normal file
|
After Width: | Height: | Size: 384 KiB |
BIN
docs/integration-test/screenshots/1.1.9-prompts.png
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
docs/integration-test/screenshots/1.2.1-desktop-login.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
docs/integration-test/screenshots/1.2.1-desktop-main.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
docs/integration-test/screenshots/1.2.2-desktop-loggedin.png
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
docs/integration-test/screenshots/1.2.5-hands-crash.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
docs/integration-test/screenshots/1.2.6-desktop-settings.png
Normal file
|
After Width: | Height: | Size: 385 KiB |
BIN
docs/integration-test/screenshots/1.2.6b-desktop-usage.png
Normal file
|
After Width: | Height: | Size: 384 KiB |
173
docs/knowledge-base/axum-dashmap-deadlock.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Axum + DashMap 运行时死锁:根因分析与修复
|
||||
|
||||
> 2026-03-30 | 严重级别: P0 | 影响范围: SaaS 后端所有 protected route
|
||||
|
||||
---
|
||||
|
||||
## 问题描述
|
||||
|
||||
Admin V2 管理后台(Ant Design Pro SPA)浏览器访问后端时,protected route 的 handler 链进入后永不返回。后端冻结,health 端点也无响应,必须 kill 进程重启。
|
||||
|
||||
**关键特征**:
|
||||
- curl 单请求测试一切正常(包括 abort 模拟)
|
||||
- 浏览器访问必然触发冻结
|
||||
- TimeoutLayer(15s)无法触发超时
|
||||
- health 端点(public route,不经过 auth 中间件)正常返回
|
||||
- protected routes(经过 auth → rate_limit → request_id → api_version 中间件链)全部卡死
|
||||
|
||||
---
|
||||
|
||||
## 根本原因
|
||||
|
||||
### 直接原因:DashMap `RefMut` 跨 `.await` 持有
|
||||
|
||||
`rate_limit_middleware`([middleware.rs](../../crates/zclaw-saas/src/middleware.rs))中,`DashMap::entry().or_insert_with()` 返回的 `RefMut` 持有 `parking_lot::RwLockWriteGuard`,跨越了 `next.run(req).await`。
|
||||
|
||||
```rust
|
||||
// ❌ 原始代码(有 bug)
|
||||
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
|
||||
entries.retain(|&time| time > window_start);
|
||||
entries.push(now);
|
||||
|
||||
next.run(req).await // ← RefMut 仍持有 parking_lot shard 写锁!
|
||||
```
|
||||
|
||||
### 死锁机制
|
||||
|
||||
```
|
||||
时间线:
|
||||
T0: Worker Thread 1 → 请求 A 获取 DashMap shard X 写锁 → await DB 查询 → yield
|
||||
T1: Worker Thread 2 → 请求 B 尝试获取 shard X 写锁 → parking_lot 阻塞 → Thread 2 冻结
|
||||
T2: Worker Thread 3 → 请求 C 尝试获取 shard X 写锁 → parking_lot 阻塞 → Thread 3 冻结
|
||||
...
|
||||
TN: 所有 Worker Thread 都被 parking_lot 阻塞 → Tokio 运行时无法调度任何 task
|
||||
→ TimeoutLayer 的 timeout future 无法被 poll → 超时机制失效
|
||||
→ 运行时全局死锁
|
||||
```
|
||||
|
||||
### 为什么 curl 不触发
|
||||
|
||||
curl 每次发送单个请求,请求完成后连接关闭。没有并发争抢同一 DashMap shard 的场景。
|
||||
|
||||
### 为什么浏览器触发
|
||||
|
||||
浏览器对同一域名默认保持 6 个并发连接(HTTP/1.1 RFC 规范)。SPA 页面加载时同时发起多个 API 请求(dashboard stats、logs、models 等),这些请求都使用同一 JWT token → 同一 account_id → 同一 DashMap shard key。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 核心修复:作用域块限定 DashMap 锁
|
||||
|
||||
将 DashMap 操作限定在独立的作用域块 `{ ... }` 内,确保 `RefMut`(持有 parking_lot 锁)在 `next.run(req).await` 之前 drop:
|
||||
|
||||
```rust
|
||||
// ✅ 修复后代码
|
||||
let blocked = {
|
||||
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
|
||||
entries.retain(|&time| time > window_start);
|
||||
|
||||
if entries.len() >= rate_limit {
|
||||
true
|
||||
} else {
|
||||
entries.push(now);
|
||||
false
|
||||
}
|
||||
}; // ← RefMut 在此处 drop,释放 parking_lot shard 锁
|
||||
|
||||
if blocked {
|
||||
return SaasError::RateLimited(...).into_response();
|
||||
}
|
||||
|
||||
next.run(req).await // 安全:无同步锁持有
|
||||
```
|
||||
|
||||
### 附加修复
|
||||
|
||||
| 修复项 | 文件 | 说明 |
|
||||
|--------|------|------|
|
||||
| ConnectInfo 提取 | [main.rs](../../crates/zclaw-saas/src/main.rs) | `into_make_service()` → `into_make_service_with_connect_info::<SocketAddr>()`,修复 login handler 的 IP 提取 |
|
||||
| 移除诊断中间件 | [main.rs](../../crates/zclaw-saas/src/main.rs) | 移除 `close_connection_middleware` 和 `diag_middleware` |
|
||||
| AbortSignal 链路 | [admin-v2/src/services/](../../admin-v2/src/services/) | 44 个 service 方法 + 17 个 useQuery 传入 signal,页面切换时自动取消请求 |
|
||||
| Vite proxy 超时 | [admin-v2/vite.config.ts](../../admin-v2/vite.config.ts) | proxy timeout 30s + configure hook |
|
||||
| PG 连接池调优 | [db.rs](../../crates/zclaw-saas/src/db.rs) | max=20, min=2, acquire=5s, idle=180s |
|
||||
|
||||
---
|
||||
|
||||
## 验证结果
|
||||
|
||||
| 测试项 | 结果 |
|
||||
|--------|------|
|
||||
| Health check | 200, ~0.2s |
|
||||
| Login + JWT 获取 | 200, token 正常 |
|
||||
| Dashboard API | 200, ~0.23s |
|
||||
| Logs API | 200, ~0.22s |
|
||||
| 8 并发同端点 | 全部 200, <0.25s |
|
||||
| 10 并发混合端点 | 全部 200 |
|
||||
| 并发后 health check | 200, 正常 |
|
||||
|
||||
---
|
||||
|
||||
## 防范模式
|
||||
|
||||
### Rust async 中同步锁使用规则
|
||||
|
||||
**绝对禁止**:在 `.await` 点持有任何同步锁(`std::sync::Mutex`、`parking_lot::Mutex`、`parking_lot::RwLock`、`DashMap::Ref`/`RefMut`)。
|
||||
|
||||
```rust
|
||||
// ❌ 危险:同步锁跨 await
|
||||
let guard = some_sync_lock.lock();
|
||||
do_async_work().await; // guard 仍持有!
|
||||
|
||||
// ✅ 安全:作用域块确保锁释放
|
||||
let result = {
|
||||
let guard = some_sync_lock.lock();
|
||||
compute_something(&guard)
|
||||
}; // guard drop
|
||||
do_async_work(result).await; // 安全
|
||||
```
|
||||
|
||||
### 何时用 tokio::sync vs parking_lot
|
||||
|
||||
| 场景 | 推荐 | 原因 |
|
||||
|------|------|------|
|
||||
| 短暂持有,无 .await | `parking_lot` / `DashMap` | 性能更好,无 async 开销 |
|
||||
| 需要跨 .await 持有 | `tokio::sync::Mutex` / `RwLock` | guard 是 Send,yield 友好 |
|
||||
| 高并发读多写少 | `DashMap`(但注意作用域) | 分片锁,读不阻塞 |
|
||||
| 需要跨 .await 持有 + 高并发 | `tokio::sync::RwLock` 或消息传递 | 避免锁竞争 |
|
||||
|
||||
### 代码审计清单
|
||||
|
||||
审计 `crates/zclaw-saas/src/` 下所有 DashMap 使用:
|
||||
|
||||
| 文件 | 使用位置 | 状态 |
|
||||
|------|----------|------|
|
||||
| `middleware.rs` | `rate_limit_entries.entry()` | ✅ 已修复,作用域块限定 |
|
||||
| `state.rs` | `rate_limit_entries.retain()` (cleanup) | ✅ 安全,同步函数无 .await |
|
||||
| `state.rs` | `role_permissions_cache` 定义 | ✅ 安全,只在 handlers.rs 中使用 |
|
||||
| `auth/handlers.rs` | `get_role_permissions()` 中 `.get()` | ✅ 安全,guard 在 .await 前 drop(但模式脆弱,建议后续重构) |
|
||||
|
||||
---
|
||||
|
||||
## 外部参考
|
||||
|
||||
- [DashMap Issue #79: Deadlock in async threads](https://github.com/xacrimon/dashmap/issues/79) — DashMap 维护者确认的 canonical issue
|
||||
- [Beware of the DashMap Deadlock](https://www.gnunicorn.org/writings/beware-of-the-dashmap-deadlock/) — 深入分析 DashMap 死锁机制
|
||||
- [How to Prevent Async Deadlocks in Rust](https://savannahar68.medium.com/rust-deadlock-do-not-hold-blocking-locks-over-await-1628bf12c6d9) — 同步锁跨 await 的通用模式
|
||||
- [Turso: How to deadlock Tokio with just a single mutex](https://turso.tech/blog/how-to-deadlock-tokio-application-in-rust-with-just-a-single-mutex) — 单锁即可死锁运行时的最小复现
|
||||
- [Hyper Issue #2366: Server stops accepting connections](https://github.com/hyperium/hyper/issues/2366) — Hyper 层面的连接累积问题
|
||||
- [Axum Discussion #1094: Detect connection closed](https://github.com/tokio-rs/axum/discussions/1094) — handler 中检测客户端断开
|
||||
|
||||
---
|
||||
|
||||
## 相关文件
|
||||
|
||||
| 文件 | 角色 |
|
||||
|------|------|
|
||||
| [crates/zclaw-saas/src/middleware.rs](../../crates/zclaw-saas/src/middleware.rs) | rate_limit_middleware — 修复点 |
|
||||
| [crates/zclaw-saas/src/auth/mod.rs](../../crates/zclaw-saas/src/auth/mod.rs) | auth_middleware — 中间件链上层 |
|
||||
| [crates/zclaw-saas/src/state.rs](../../crates/zclaw-saas/src/state.rs) | AppState 定义,含 DashMap 字段 |
|
||||
| [crates/zclaw-saas/src/main.rs](../../crates/zclaw-saas/src/main.rs) | 路由构建、TimeoutLayer、ConnectInfo |
|
||||
| [crates/zclaw-saas/src/db.rs](../../crates/zclaw-saas/src/db.rs) | PG 连接池配置 |
|
||||
| [admin-v2/src/services/request.ts](../../admin-v2/src/services/request.ts) | AbortSignal 传递工具函数 |
|
||||
| [admin-v2/vite.config.ts](../../admin-v2/vite.config.ts) | Vite 代理超时配置 |
|
||||
@@ -141,9 +141,14 @@
|
||||
| API Key 管理 | `Settings/ModelsAPI.tsx` | ✅ 通过 | 环境变量插值 `${VAR_NAME}` |
|
||||
|
||||
> **统计**: 共 7 个中文提供商 + 3 个国际提供商 (OpenAI/Anthropic/Gemini) + 1 个本地驱动
|
||||
+ P0 安全加固 (2026-03-30)
|
||||
|
||||
### 7.3 其他设置
|
||||
|
||||
+ P0 安全加固
|
||||
|
||||
(2026-03-30)
|
||||
|
||||
| 功能 | 组件位置 | 验证状态 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| 技能目录 | `Settings/Skills.tsx` | ✅ 通过 | API 正常 |
|
||||
@@ -162,7 +167,23 @@
|
||||
| 反思日志 | `ReflectionLog` (RightPanel) | ✅ 通过 | 显示反思分析结果 |
|
||||
| 安全面板 | `SecurityLayersPanel` (RightPanel) | ⚠️ 前端模拟 | 使用 fallback 数据 |
|
||||
|
||||
## 9. 侧边栏
|
||||
## 9. P0 安全加固 (2026-03-30)
|
||||
|
||||
> 所有 P0 安全加固项已完成并通过 cargo check 编译验证。
|
||||
|
||||
| 功能 | 修改文件 | 验证状态 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| 密钥管理 (env 插值) | `config.rs` | ✅ 通过 | TOML 支持 `${DB_PASSWORD}` 环境变量插值 |
|
||||
| JWT fallback key 保护 | `config.rs` | ✅ 通过 | `#[cfg(debug_assertions)]` 保护, release 拒绝启动 |
|
||||
| Auth rate limiting (路径感知) | `middleware.rs` | ✅ 通过 | login 5次/分, register 3次/时, 其他 20次/分 |
|
||||
| Logout token 撤销 | `handlers.rs` | ✅ 通过 | logout 时 DB 标记 refresh_token used_at |
|
||||
| Cookie Secure 条件化 | `handlers.rs` | ✅ 通过 | `ZCLAW_SAAS_DEV=true` 时 false, 生产 true |
|
||||
| TLS 终止文档 | `docs/knowledge-base/security-hardening.md` | ✅ 通过 | 生产环境必须 nginx/caddy HTTPS |
|
||||
| 配置模板更新 | `saas-config.toml.example` | ✅ 通过 | 包含所有环境变量占位符说明 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 侧边栏
|
||||
|
||||
| 功能 | 组件位置 | 验证状态 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
|
||||
64
docs/knowledge-base/security-hardening.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# P0 安全加固清单
|
||||
|
||||
> 审计后文档同步 — 记录每个安全加固项完成的日期和操作。
|
||||
|
||||
以及配置说明。
|
||||
|
||||
## 1. 密钥管理 — 环境变量插值
|
||||
|
||||
配置解析
|
||||
|
||||
支持 `${ENV_VAR}` 语法(如 `${DB_PASSWORD}`) 的数据库密码)。
|
||||
TOMl 文件中。
|
||||
|
||||
无需在配置文件中明文存储密 password。
|
||||
|
||||
也可以通过 `ZCLAW_DATABASE_URL` 环境变量完整覆盖(优先级最高)。
|
||||
|
||||
### 2. JWT Fallback key 清理
|
||||
|
||||
配置:
|
||||
- **位置**: `crates/zclaw-saas/src/config.rs`
|
||||
(jwt_secret 方法)
|
||||
- **状态**: 仅 debug 枝建可用 fallback, 已 `#[cfg(debug_assertions)]` 保护
|
||||
(不进入 release)
|
||||
- **位置**: `crates/zclaw-saas/src/auth/jwt.rs` (`TEST_SECRET`) 仅用于测试
|
||||
(不暴露)
|
||||
- **评估**: JWT key 的使用安全且 已好。
|
||||
|
||||
但 `config.rs` 中新增了 `interpolate_env_vars` 函数,在 `SaaSConfig::load()` 中解析 TOMl 前调用环境变量插值。
|
||||
|
||||
支持 `${VAR}` 语法
|
||||
|
||||
### 3. Auth Rate limiting (配置:
|
||||
- **位置**: `crates/zclaw-saas/src/middleware.rs`
|
||||
(public_rate_limit_middleware)
|
||||
- **登录**: 5次/分钟/IP,注册 3次/小时/IP
|
||||
縆回 login 和 refresh 20次/分钟/IP)
|
||||
- **位置**: `crates/zclaw-saas/src/auth/handlers.rs` (logout handler)
|
||||
- 修改 `logout` handler:从仅清除 cookies 到先撤销 refresh token (DB UPDATE),再清除 cookies
|
||||
Cookie Secure 标记: **条件化** (dev 模式 false, 生产模式 true)
|
||||
- **位置**: `crates/zclaw-saas/src/state.rs` (cleanup_rate_limit_entries 窗口从 60s 攒大到 3600s)
|
||||
|
||||
- **位置**: `docs/knowledge-base/security-hardening.md` (新增)
|
||||
TLS 终止文档)
|
||||
|
||||
- **位置**: `saas-config.toml.example`
|
||||
|
||||
文件已更新为包含环境变量占位符说明
|
||||
|
||||
生成日期: 2026-03-30
|
||||
|
||||
---
|
||||
|
||||
## 窌证状态
|
||||
|
||||
改动 | 文件 | 行 | 说明 | |
|
||||
|------|------|----------|
|
||||
| saas-config.toml ${ DB_PASSWORD} 改为 `${DB_PASSWORD}` 引用 | config 解析支持 env 插值 | ✅ 通过 |
|
||||
| JWT fallback key | `config.rs` | debug 枝 不会进入 release; TEST-only ` ✅ 已安全 |
|
||||
| Auth rate limiting | `middleware.rs` | login 5次/分、注册 3次/时 | ✅ 猬化 |
|
||||
| Logout token 撤销 | `handlers.rs` | logout 时 DB 撤销 | ✅ 通过 |
|
||||
| Cookie Secure | `handlers.rs` | 开发环境 false/生产 true | ✅ 已安全 |
|
||||
| TLS 终止 | `docs/knowledge-base/security-hardening.md` | 新增文档 |
|
||||
| saas-config.toml.example | 更新 | ✅ 通过 |
|
||||
366
docs/superpowers/specs/2026-03-30-saas-positioning-design.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# ZCLAW Admin V2 与 Desktop 系统定位设计
|
||||
|
||||
> 日期: 2026-03-30
|
||||
> 状态: Draft
|
||||
> 范围: Admin V2 定位 + Desktop 集成 + Token Pool + Agent 模板
|
||||
|
||||
## 1. 系统定位
|
||||
|
||||
### 1.1 Admin V2 — 资源管理与配置中心
|
||||
|
||||
**目标用户**: 系统管理员、运营人员
|
||||
|
||||
**核心职责**:
|
||||
1. **账号管理** — 用户 CRUD、角色权限、LLM 路由模式配置
|
||||
2. **Token 池管理** — 多提供商多密钥的统一池化管理,解决 Coding Plan 套餐的时段限额和资源浪费问题
|
||||
3. **行业 Agent 模板** — 预调教好的行业 Agent 人设(含 soul.md、system_prompt、工具集),供 Desktop 首次使用时获取
|
||||
4. **系统监控** — 用量统计、日志审计、配置管理
|
||||
|
||||
### 1.2 Desktop (Agent 会话端) — 终端用户界面
|
||||
|
||||
**目标用户**: 终端用户(AI Agent 操作者)
|
||||
|
||||
**核心职责**:
|
||||
1. **智能对话** — 流式响应、多模型切换、上下文管理
|
||||
2. **Agent 管理** — 创建、配置、切换 Agent(支持从 SaaS 模板初始化)
|
||||
3. **自主能力** — Hands 触发、工作流编排、技能调用
|
||||
4. **智能成长** — Agent 通过对话逐步成长,记忆、反思、身份演化
|
||||
|
||||
### 1.3 数据流
|
||||
|
||||
```
|
||||
Admin V2 (配置) → SaaS API → PostgreSQL
|
||||
↓
|
||||
Desktop (登录) → SaaS API → 拉取 AccountProfile { llm_routing, available_templates }
|
||||
↓
|
||||
┌─────────┴──────────┐
|
||||
│ │
|
||||
relay 模式 local 模式
|
||||
│ │
|
||||
SaaS Relay Tauri Kernel
|
||||
│ │
|
||||
Key Pool 旋转 本地 API Key
|
||||
│ │
|
||||
LLM Provider LLM Provider
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 账号级 LLM 路由
|
||||
|
||||
### 2.1 背景
|
||||
|
||||
国内 LLM 厂商推出 Coding Plan 套餐,但套餐有时段使用量限额。每个 Desktop 客户端各自配置 API Key 会造成资源浪费。在 Admin 端搭建 Token 池,多账号共享密钥资源,成本更优。
|
||||
|
||||
### 2.2 数据层
|
||||
|
||||
`accounts` 表新增:
|
||||
|
||||
```sql
|
||||
ALTER TABLE accounts ADD COLUMN llm_routing TEXT NOT NULL DEFAULT 'local'
|
||||
CHECK (llm_routing IN ('relay', 'local'));
|
||||
COMMENT ON COLUMN accounts.llm_routing IS 'LLM路由模式: relay=SaaS中转, local=本地直连';
|
||||
```
|
||||
|
||||
默认值 `local`,保持向后兼容。
|
||||
|
||||
### 2.3 API 层
|
||||
|
||||
**登录响应扩展**:
|
||||
|
||||
`POST /api/v1/auth/login` 和 `GET /api/v1/auth/me` 返回值增加:
|
||||
|
||||
```typescript
|
||||
{
|
||||
token: string;
|
||||
refresh_token: string;
|
||||
account: {
|
||||
id: string;
|
||||
username: string;
|
||||
role: string;
|
||||
llm_routing: 'relay' | 'local'; // 新增
|
||||
// ... 现有字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Admin 端账号管理**:
|
||||
|
||||
`PATCH /api/v1/accounts/:id` 请求体支持 `llm_routing` 字段更新,仅 `account:admin` 权限可修改。
|
||||
|
||||
### 2.4 Desktop 行为
|
||||
|
||||
`saasStore` 登录成功后读取 `account.llm_routing`,写入 `connectionStore`:
|
||||
|
||||
- `'relay'` → 使用 `SaaSClient.chatCompletion()` 走 SaaS Relay,无需启动本地 Kernel
|
||||
- `'local'` → 使用 `KernelClient` 走本地 Tauri Kernel
|
||||
|
||||
路由模式在登录时确定,会话期间不变。切换需要管理员修改 + 用户重新登录。
|
||||
|
||||
---
|
||||
|
||||
## 3. Token Pool / Key Pool 管理
|
||||
|
||||
### 3.1 现状
|
||||
|
||||
后端 Key Pool 已完整实现:
|
||||
- `select_best_key()`: 优先级排序 + RPM/TPM 滑窗检查
|
||||
- `mark_key_429()`: 429 自动冷却 + `Retry-After` 支持
|
||||
- `record_key_usage()`: 分钟粒度用量记录
|
||||
- SSRF 防护、加密存储、降级回退
|
||||
|
||||
Admin UI 为只读(仅显示密钥列表),缺少添加/切换/删除操作。
|
||||
|
||||
### 3.2 Admin UI 补全
|
||||
|
||||
Providers 页面 Key Pool Modal 升级为完整管理:
|
||||
|
||||
| 操作 | 交互 | 说明 |
|
||||
|------|------|------|
|
||||
| 添加密钥 | 点击"添加"按钮 → 弹出表单 | 字段: label, key_value, priority, max_rpm(可选), max_tpm(可选) |
|
||||
| 启用/禁用 | 切换 Switch 组件 | 调用 `PUT /providers/:id/keys/:keyId/toggle` |
|
||||
| 删除密钥 | 点击删除 → Popconfirm 确认 | 调用 `DELETE /providers/:id/keys/:keyId` |
|
||||
| 状态展示 | 表格列 | 每个密钥显示: 状态、priority、RPM/TPM 配额、当前窗口用量、总用量 |
|
||||
|
||||
前端使用 `providerService.addKey()`, `providerService.toggleKey()`, `providerService.deleteKey()` — 这些 service 方法已定义但未使用,直接接入即可。
|
||||
|
||||
### 3.3 缺陷修复
|
||||
|
||||
| 缺陷 | 修复方案 | 优先级 |
|
||||
|------|---------|--------|
|
||||
| `key_usage_window` 无清理 | Scheduler 增加定时任务,清理 >24h 的窗口记录 | P1 |
|
||||
| 同优先级无负载分散 | `select_best_key()` 加入"最近最少使用"次排序 | P2 |
|
||||
| `quota_reset_interval` 未使用 | 移除字段或实现按 interval 重置配额逻辑 | P3 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 行业 Agent 模板系统
|
||||
|
||||
### 4.1 背景
|
||||
|
||||
通过预设行业 Agent 模板(如医疗行业),预先调教好完整的 Agent 人设。Desktop 端首次使用时从 SaaS 获取完整模板配置,之后 Agent 由用户对话驱动逐步成长,不再与模板同步。
|
||||
|
||||
### 4.2 数据层 — 扩展 agent_templates
|
||||
|
||||
```sql
|
||||
ALTER TABLE agent_templates ADD COLUMN soul_content TEXT;
|
||||
ALTER TABLE agent_templates ADD COLUMN scenarios TEXT NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE agent_templates ADD COLUMN welcome_message TEXT;
|
||||
ALTER TABLE agent_templates ADD COLUMN quick_commands TEXT NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE agent_templates ADD COLUMN personality TEXT;
|
||||
ALTER TABLE agent_templates ADD COLUMN communication_style TEXT;
|
||||
ALTER TABLE agent_templates ADD COLUMN emoji TEXT;
|
||||
ALTER TABLE agent_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
|
||||
ALTER TABLE agent_templates ADD COLUMN source_id TEXT UNIQUE;
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `soul_content` | TEXT | SOUL.md 人格配置全文 (Markdown) |
|
||||
| `scenarios` | JSON | 场景标签数组,如 `["临床辅助", "文献检索"]` |
|
||||
| `welcome_message` | TEXT | 首次问候语 |
|
||||
| `quick_commands` | JSON | 快捷指令数组 `[{label, prompt}]` |
|
||||
| `personality` | TEXT | 人格预设: professional/friendly/creative/concise |
|
||||
| `communication_style` | TEXT | 沟通风格描述文本 |
|
||||
| `emoji` | TEXT | Agent 图标 |
|
||||
| `version` | INT | 模板版本号,用于新用户获取最新版 |
|
||||
| `source_id` | TEXT | 模板唯一标识,用于 Desktop 去重 |
|
||||
|
||||
### 4.3 种子模板示例 — 医疗行业
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: "医疗助手",
|
||||
source_id: "medical-assistant-v1",
|
||||
category: "healthcare",
|
||||
description: "医疗行业专业 AI 助手,熟悉临床流程、医学文献和诊疗规范",
|
||||
emoji: "🏥",
|
||||
personality: "professional",
|
||||
system_prompt: "你是一位专业的医疗AI助手,具备以下能力:\n1. 临床辅助诊断建议\n2. 医学文献检索与解读\n3. 用药参考与相互作用检查\n4. 诊疗规范查询\n\n注意: 你的建议仅供参考,不能替代专业医生诊断。",
|
||||
soul_content: `# SOUL.md
|
||||
## 人格特质
|
||||
- 专业严谨,使用准确的医学术语
|
||||
- 耐心细致,对每个问题都给予充分解答
|
||||
- 保守审慎,涉及诊断和用药建议时始终提示"仅供参考"
|
||||
- 持续学习,关注最新医学研究进展
|
||||
|
||||
## 沟通风格
|
||||
- 使用专业但易懂的语言
|
||||
- 适当使用类比解释复杂医学概念
|
||||
- 对不确定的内容明确标注置信度
|
||||
- 主动提醒风险和注意事项
|
||||
|
||||
## 边界
|
||||
- 不做明确诊断,只提供参考意见
|
||||
- 不推荐处方药的具体用法用量
|
||||
- 遇到紧急情况建议立即就医
|
||||
`,
|
||||
scenarios: ["临床辅助", "文献检索", "诊断建议", "用药参考", "病历整理"],
|
||||
welcome_message: "你好!我是你的医疗AI助手。我可以帮你检索文献、解读检查报告、提供诊疗参考意见。请注意,我的建议仅供参考,不能替代专业医生的诊断。有什么我可以帮你的吗?",
|
||||
quick_commands: [
|
||||
{ label: "文献检索", prompt: "请帮我检索关于 {topic} 的最新医学文献" },
|
||||
{ label: "用药查询", prompt: "请查询 {drug} 的药理作用、适应症和注意事项" },
|
||||
{ label: "检查解读", prompt: "请帮我解读以下检查报告: {report}" },
|
||||
{ label: "诊疗参考", prompt: "关于 {symptom} 的诊疗参考方案" }
|
||||
],
|
||||
model: "claude-sonnet-4-6",
|
||||
tools: ["browser", "researcher", "collector"],
|
||||
temperature: 0.3,
|
||||
max_tokens: 4096,
|
||||
communication_style: "使用专业但易懂的语言,适当使用类比,对不确定内容标注置信度"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 API 层
|
||||
|
||||
**新增端点**:
|
||||
|
||||
| 端点 | 方法 | 权限 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `/api/v1/agent-templates/available` | GET | 任何已认证 | 返回可浏览模板列表 (id, name, category, emoji, description, source_id) |
|
||||
| `/api/v1/agent-templates/:id/full` | GET | 任何已认证 | 返回完整模板 (含 soul_content, welcome_message, quick_commands 等) |
|
||||
|
||||
**现有端点扩展**:
|
||||
|
||||
`POST /api/v1/agent-templates` 和 `POST /api/v1/agent-templates/:id` (update) 请求体支持所有新增字段。
|
||||
|
||||
### 4.5 Desktop 消费流程
|
||||
|
||||
```
|
||||
1. 登录成功
|
||||
→ saasStore 调用 GET /agent-templates/available
|
||||
→ 缓存模板列表到 saasStore.availableTemplates
|
||||
|
||||
2. 用户创建 Agent(首次 / 新建)
|
||||
→ AgentOnboardingWizard 增加 Step 0: 选择行业模板
|
||||
→ 展示 Grid 卡片(emoji + 名称 + 分类 + 描述)
|
||||
→ 两个入口: "空白 Agent" / "从模板创建"
|
||||
|
||||
3. 选择模板后
|
||||
→ 调用 GET /agent-templates/:id/full 获取完整配置
|
||||
→ 映射到 Desktop Clone + Kernel Agent:
|
||||
|
||||
| SaaS 字段 | Desktop 目标 |
|
||||
|---------------------|-------------------------------------|
|
||||
| name | Clone.name |
|
||||
| emoji | Clone.emoji |
|
||||
| personality | Clone.personality |
|
||||
| scenarios | Clone.scenarios |
|
||||
| communication_style | Clone.communicationStyle |
|
||||
| model | Clone.model |
|
||||
| system_prompt | → Kernel agent system_prompt |
|
||||
| soul_content | → identity.updateFile('soul', ...) |
|
||||
| welcome_message | → 首条消息展示 |
|
||||
| quick_commands | → 本地缓存,快捷指令面板 |
|
||||
| temperature | → Kernel agent config |
|
||||
| max_tokens | → Kernel agent config |
|
||||
| tools | → Kernel agent tools |
|
||||
|
||||
4. 创建完成
|
||||
→ Agent 在本地成长
|
||||
→ 不再与 SaaS 模板同步
|
||||
→ 模板更新仅影响新用户
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Admin V2 UI 变更汇总
|
||||
|
||||
### 5.1 Accounts 页面
|
||||
|
||||
- 编辑 Modal 增加 "LLM 路由模式" 字段
|
||||
- 下拉选项: "SaaS 中转 (Token 池)" / "本地直连"
|
||||
- 表格增加路由模式列
|
||||
|
||||
### 5.2 Providers 页面
|
||||
|
||||
- Key Pool Modal 从只读升级为完整管理
|
||||
- 增加 "添加密钥" 按钮 → 表单 (label, key, priority, RPM, TPM)
|
||||
- 密钥行增加启用/禁用 Switch 和删除按钮
|
||||
- 增加用量统计展示
|
||||
|
||||
### 5.3 Agent Templates 页面
|
||||
|
||||
- 创建/编辑 Modal 增加新字段: emoji, personality, soul_content (大文本编辑器), scenarios (多选标签), welcome_message, quick_commands (动态列表), communication_style
|
||||
- 列表增加 emoji 列和 category 筛选
|
||||
- 预览功能: 展示模板效果(模拟 Agent 外观)
|
||||
|
||||
---
|
||||
|
||||
## 6. Desktop 端变更汇总
|
||||
|
||||
### 6.1 saasStore
|
||||
|
||||
- 登录响应增加 `llm_routing` 解析
|
||||
- 新增 `fetchAvailableTemplates()` 方法
|
||||
- 新增 `availableTemplates` 状态
|
||||
|
||||
### 6.2 connectionStore
|
||||
|
||||
- `connect()` 根据账号 `llm_routing` 选择路径
|
||||
- relay 模式: 使用 `SaaSClient.chatCompletion()`
|
||||
- local 模式: 保持现有 `KernelClient` 逻辑
|
||||
|
||||
### 6.3 AgentOnboardingWizard
|
||||
|
||||
- 新增 Step 0: 行业模板选择
|
||||
- 模板 Grid 展示 (从 saasStore.availableTemplates 渲染)
|
||||
- 选择模板后预填后续步骤默认值
|
||||
|
||||
### 6.4 agentStore
|
||||
|
||||
- 新增 `createFromTemplate(template)` 方法
|
||||
- 映射模板字段到 Clone + Kernel Agent
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件变更清单
|
||||
|
||||
### SaaS 后端 (Rust)
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `crates/zclaw-saas/migrations/` | 新增 migration: accounts 加 llm_routing, agent_templates 加扩展字段 |
|
||||
| `crates/zclaw-saas/src/account/types.rs` | AccountInfo 增加 llm_routing 字段 |
|
||||
| `crates/zclaw-saas/src/account/handlers.rs` | update_account 支持 llm_routing |
|
||||
| `crates/zclaw-saas/src/auth/handlers.rs` | login/me 响应增加 llm_routing |
|
||||
| `crates/zclaw-saas/src/agent_template/types.rs` | 扩展 Create/Update/Info 类型 |
|
||||
| `crates/zclaw-saas/src/agent_template/service.rs` | 支持 soul_content 等新字段 |
|
||||
| `crates/zclaw-saas/src/agent_template/mod.rs` | 新增 /available 和 /:id/full 路由 |
|
||||
| `crates/zclaw-saas/src/relay/key_pool.rs` | 加入 LRU 次排序 |
|
||||
| `crates/zclaw-saas/src/scheduler.rs` | 新增 key_usage_window 清理任务 |
|
||||
| `crates/zclaw-saas/src/db.rs` | 扩展种子模板数据 |
|
||||
|
||||
### Admin V2 (TypeScript)
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `admin-v2/src/pages/Accounts.tsx` | 编辑 Modal 增加 LLM 路由模式字段 |
|
||||
| `admin-v2/src/pages/Providers.tsx` | Key Pool Modal 升级为完整管理 |
|
||||
| `admin-v2/src/pages/AgentTemplates.tsx` | 创建/编辑 Modal 增加扩展字段 |
|
||||
| `admin-v2/src/services/providers.ts` | 接入已有的 addKey/toggleKey/deleteKey |
|
||||
| `admin-v2/src/services/agent-templates.ts` | 新增 getFull 方法 |
|
||||
| `admin-v2/src/types/index.ts` | 扩展类型定义 |
|
||||
|
||||
### Desktop (TypeScript)
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `desktop/src/store/saasStore.ts` | 增加 llm_routing 解析 + fetchAvailableTemplates |
|
||||
| `desktop/src/store/connectionStore.ts` | connect() 按 routing 模式选择路径 |
|
||||
| `desktop/src/store/agentStore.ts` | 新增 createFromTemplate() |
|
||||
| `desktop/src/components/AgentOnboardingWizard.tsx` | 新增 Step 0: 模板选择 |
|
||||
| `desktop/src/lib/saas-client.ts` | 新增 fetchAvailableTemplates + fetchTemplateFull |
|
||||
|
||||
---
|
||||
|
||||
## 8. 不在范围内
|
||||
|
||||
以下事项本次设计不涉及:
|
||||
- AccountTier / 订阅等级体系
|
||||
- 实时配置推送 (WebSocket)
|
||||
- 模板市场 / 用户自定义模板上传
|
||||
- Token 池计费 / 用量配额限制
|
||||
- Desktop 端路由模式用户自选 (由 Admin 统一管控)
|
||||