docs(saas): 添加 SaaS 后台系统设计规格

涵盖四大核心模块的完整设计:
- 账号权限管理 (JWT/Argon2/TOTP/权限模板)
- 模型与 API 配置中心 (提供商/模型/密钥/使用量)
- 模型请求中转服务 (队列/流式转发/速率限制)
- 系统配置迁移分析 (迁移优先级/同步协议)

技术栈: Rust + Axum 后端 + React 管理后台
目标: MVP <100 用户, 独立 SaaS 服务
This commit is contained in:
iven
2026-03-27 12:16:19 +08:00
parent 256dba49db
commit b3a31ec48b

View File

@@ -0,0 +1,763 @@
# ZCLAW SaaS 后台系统设计规格
> 日期: 2026-03-27
> 状态: 待审阅
> 范围: 独立 SaaS 后端服务 (Rust + Axum) + 独立 Web 管理后台 (React + TypeScript)
## 1. 概述
### 1.1 问题陈述
ZCLAW 当前是纯桌面单用户应用缺少用户账号系统、API 服务端、多租户支持和请求中转能力。所有 LLM 调用直接从桌面端发起API 密钥存储在本地环境变量或 OS Keyring模型配置散落在 TOML 文件和 localStorage 中。这限制了多用户协作、集中管控和资源优化。
### 1.2 设计目标
1. 为 ZCLAW 添加独立 SaaS 后端,提供账号权限、模型配置、请求中转、配置迁移四大能力
2. 桌面端通过简单配置切换即可接入 SaaS`base_url` + 添加 API Token
3. MVP 阶段支持 <100 用户后续可扩展至更大规模
### 1.3 用户决策
| 决策点 | 选择 |
|--------|------|
| 部署形态 | 独立 SaaS 后端服务 |
| 技术栈 | Rust + Axum |
| 实施顺序 | 按依赖顺序: 账号API中转迁移 |
| 管理界面 | 独立 React Web 管理后台 |
| 用户规模 | MVP <100 用户 |
| 支持模型 | 智谱 GLM, 通义千问, Kimi, DeepSeek |
| 代码位置 | 同仓库 workspace 成员 `crates/zclaw-saas/` |
---
## 2. 架构
### 2.1 系统架构图
```
┌─────────────────────┐ ┌─────────────────────────────────────────┐
│ ZCLAW 桌面客户端 │ │ SaaS 后端服务 │
│ (Tauri + React) │ │ (Rust + Axum) │
│ │ │ │
│ OpenAiDriver ──────┼────►│ /api/v1/relay/chat/completions │
│ (改 base_url) │ │ → 权限检查 → 队列 → 转发 → 流式响应 │
│ │ │ │
│ SaaSConfigClient ──┼────►│ /api/v1/config/sync │
│ (配置同步) │ │ → 配置覆盖/冲突检测 │
│ │ │ │
│ Model Picker ──────┼────►│ /api/v1/catalog/models │
│ (模型目录) │ │ → 动态模型列表 │
└─────────────────────┘ └─────────────────────────────────────────┘
┌───────────────┐
│ SQLite (WAL) │
│ saas-data.db │
└───────────────┘
┌──────────────────────────────────────────────────────┐
│ 管理后台 (React + TypeScript) │
│ /admin/accounts /admin/providers /admin/relay │
│ /admin/roles /admin/usage /admin/migration │
└──────────────────────────────────────────────────────┘
```
### 2.2 Crate 依赖
```
zclaw-types (无依赖,复用 ID/错误/消息类型)
zclaw-saas (→ types + axum/sqlx/argon2/jsonwebtoken)
↑ (通过 API)
desktop/ (→ 通过 HTTP API 调用 SaaS)
saas-admin/ (→ 通过 HTTP API 调用 SaaS)
```
### 2.3 文件结构
```
crates/zclaw-saas/
Cargo.toml
src/
lib.rs # 模块声明
main.rs # 入口
error.rs # SaaS 错误类型
config.rs # SaaS 服务器配置 (TOML)
db.rs # 数据库初始化 + 迁移
state.rs # AppState
auth/
mod.rs
jwt.rs # JWT 创建/验证
password.rs # Argon2 哈希
totp.rs # TOTP 2FA
middleware.rs # Axum 认证中间件
account/
mod.rs
types.rs # Account/Role/Permission 类型
handlers.rs # HTTP 处理器
service.rs # 业务逻辑
model_config/
mod.rs
types.rs # Provider/Model/APIKey 类型
handlers.rs # HTTP 处理器
service.rs # 业务逻辑
relay/
mod.rs
types.rs # 请求/响应/队列类型
handlers.rs # HTTP 处理器 (代理端点)
service.rs # 队列管理、调度、批处理
provider_impl.rs # 提供商特定转发逻辑
migration/
mod.rs
types.rs # ConfigItem/SyncRecord 类型
handlers.rs # HTTP 处理器
service.rs # 配置分析和同步逻辑
saas-admin/ # 独立 React 管理后台
package.json
src/
App.tsx
pages/
Login.tsx
Dashboard.tsx
accounts/
providers/
api-keys/
usage/
relay/
migration/
roles/
logs/
```
---
## 3. 数据库设计
### 3.1 概述
- 引擎: SQLite WAL 模式
- 文件: 独立于桌面端 `~/.zclaw/data.db`默认 `./saas-data.db`
- 迁移: 版本化 schema启动时自动迁移
### 3.2 完整 Schema
```sql
-- Schema 版本控制
CREATE TABLE IF NOT EXISTS saas_schema_version (
version INTEGER PRIMARY KEY
);
-- ============================================================
-- 模块一: 账号权限
-- ============================================================
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY, -- UUID v4
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL, -- Argon2id
display_name TEXT NOT NULL DEFAULT '',
avatar_url TEXT,
role TEXT NOT NULL DEFAULT 'user', -- 'super_admin' | 'admin' | 'user'
status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'disabled' | 'suspended'
totp_secret TEXT, -- 加密存储
totp_enabled INTEGER NOT NULL DEFAULT 0,
last_login_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email);
CREATE INDEX IF NOT EXISTS idx_accounts_role ON accounts(role);
CREATE TABLE IF NOT EXISTS api_tokens (
id TEXT PRIMARY KEY, -- UUID
account_id TEXT NOT NULL,
name TEXT NOT NULL, -- e.g. "桌面客户端"
token_hash TEXT NOT NULL, -- SHA256(token)
token_prefix TEXT NOT NULL, -- 前 8 字符用于展示
permissions TEXT NOT NULL DEFAULT '[]', -- JSON 权限数组
last_used_at TEXT,
expires_at TEXT, -- NULL = 永不过期
created_at TEXT NOT NULL,
revoked_at TEXT,
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_api_tokens_account ON api_tokens(account_id);
CREATE INDEX IF NOT EXISTS idx_api_tokens_hash ON api_tokens(token_hash);
CREATE TABLE IF NOT EXISTS roles (
id TEXT PRIMARY KEY, -- 'super_admin' | 'admin' | 'user' | 自定义 UUID
name TEXT NOT NULL, -- 显示名称 (中文)
description TEXT,
permissions TEXT NOT NULL DEFAULT '[]', -- JSON 权限数组
is_system INTEGER NOT NULL DEFAULT 0, -- 系统角色不可删除
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS permission_templates (
id TEXT PRIMARY KEY, -- UUID
name TEXT NOT NULL, -- e.g. "标准用户", "只读用户"
description TEXT,
permissions TEXT NOT NULL DEFAULT '[]', -- JSON 权限数组
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS operation_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id TEXT, -- NULL = 系统操作
action TEXT NOT NULL, -- e.g. "account.create", "model.update"
target_type TEXT, -- e.g. "account", "api_key", "model"
target_id TEXT,
details TEXT, -- JSON 详情
ip_address TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_op_logs_account ON operation_logs(account_id);
CREATE INDEX IF NOT EXISTS idx_op_logs_action ON operation_logs(action);
CREATE INDEX IF NOT EXISTS idx_op_logs_time ON operation_logs(created_at);
-- ============================================================
-- 模块二: 模型配置
-- ============================================================
CREATE TABLE IF NOT EXISTS providers (
id TEXT PRIMARY KEY, -- UUID
name TEXT NOT NULL UNIQUE, -- e.g. 'zhipu', 'qwen', 'kimi', 'deepseek'
display_name TEXT NOT NULL, -- e.g. '智谱 AI'
api_key TEXT, -- 服务端提供商 API 密钥 (加密存储)
base_url TEXT NOT NULL,
api_protocol TEXT NOT NULL DEFAULT 'openai', -- 'openai' | 'anthropic'
enabled INTEGER NOT NULL DEFAULT 1,
rate_limit_rpm INTEGER, -- 每分钟请求数
rate_limit_tpm INTEGER, -- 每分钟 token 数
config_json TEXT DEFAULT '{}', -- 提供商特定配置
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS models (
id TEXT PRIMARY KEY, -- UUID
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL, -- 传给 API 的模型标识
alias TEXT NOT NULL, -- 显示名称
context_window INTEGER NOT NULL DEFAULT 8192,
max_output_tokens INTEGER NOT NULL DEFAULT 4096,
supports_streaming INTEGER NOT NULL DEFAULT 1,
supports_vision INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
pricing_input REAL DEFAULT 0, -- 每 1K token 价格
pricing_output REAL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(provider_id, model_id),
FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_models_provider ON models(provider_id);
CREATE TABLE IF NOT EXISTS account_api_keys (
id TEXT PRIMARY KEY, -- UUID
account_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
key_value TEXT NOT NULL, -- API 密钥 (加密存储)
key_label TEXT, -- e.g. "主密钥", "备用密钥"
permissions TEXT NOT NULL DEFAULT '[]', -- JSON: 可访问的模型 ID 列表
enabled INTEGER NOT NULL DEFAULT 1,
last_used_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
revoked_at TEXT,
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE,
FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_account_api_keys_account ON account_api_keys(account_id);
CREATE TABLE IF NOT EXISTS usage_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
latency_ms INTEGER,
status TEXT NOT NULL DEFAULT 'success', -- 'success' | 'error' | 'rate_limited'
error_message TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_usage_account ON usage_records(account_id);
CREATE INDEX IF NOT EXISTS idx_usage_time ON usage_records(created_at);
-- ============================================================
-- 模块三: 中转服务
-- ============================================================
CREATE TABLE IF NOT EXISTS relay_tasks (
id TEXT PRIMARY KEY, -- UUID
account_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL,
request_hash TEXT NOT NULL, -- 用于去重
status TEXT NOT NULL DEFAULT 'queued', -- 'queued' | 'processing' | 'completed' | 'failed'
priority INTEGER NOT NULL DEFAULT 0,
attempt_count INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
request_body TEXT NOT NULL, -- JSON: 转发的请求体
response_body TEXT, -- JSON: 响应体
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
error_message TEXT,
queued_at TEXT NOT NULL,
started_at TEXT,
completed_at TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_relay_status ON relay_tasks(status);
CREATE INDEX IF NOT EXISTS idx_relay_account ON relay_tasks(account_id);
CREATE INDEX IF NOT EXISTS idx_relay_provider ON relay_tasks(provider_id);
-- ============================================================
-- 模块四: 配置迁移
-- ============================================================
CREATE TABLE IF NOT EXISTS config_items (
id TEXT PRIMARY KEY, -- UUID
category TEXT NOT NULL, -- 'llm' | 'agent' | 'security' | 'hands' | 'skills' | 'tools' | 'logging' | 'desktop' | 'server'
key_path TEXT NOT NULL, -- e.g. 'llm.default_provider'
value_type TEXT NOT NULL, -- 'string' | 'integer' | 'float' | 'boolean' | 'array' | 'object'
current_value TEXT, -- JSON 编码的当前值
default_value TEXT, -- JSON 编码的默认值
source TEXT NOT NULL DEFAULT 'local', -- 'local' | 'saas' | 'override'
description TEXT, -- 中文描述
requires_restart INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(category, key_path)
);
CREATE INDEX IF NOT EXISTS idx_config_category ON config_items(category);
CREATE TABLE IF NOT EXISTS config_sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id TEXT NOT NULL,
client_fingerprint TEXT NOT NULL,
action TEXT NOT NULL, -- 'push' | 'pull' | 'conflict'
config_keys TEXT NOT NULL, -- JSON: 受影响的配置键
client_values TEXT, -- JSON: 客户端值
saas_values TEXT, -- JSON: SaaS 值
resolution TEXT, -- 'client_wins' | 'saas_wins' | 'manual'
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sync_account ON config_sync_log(account_id);
```
---
## 4. 模块一: 账号权限管理
### 4.1 认证机制
| 机制 | 实现 | 用途 |
|------|------|------|
| 密码 | Argon2id (argon2 crate) | 账号登录 |
| JWT | HMAC-SHA256 (jsonwebtoken crate) | 管理后台会话 |
| TOTP 2FA | totp-rs crate | 可选双因素认证 |
| API Token | SHA-256 哈希存储 | 桌面客户端/程序化访问 |
**JWT Payload**: `{ sub: account_id, role: "admin", exp: timestamp }`
**API Token 格式**: `zclaw_<48 random bytes>`仅创建时展示一次
### 4.2 认证中间件
```rust
// auth/middleware.rs
pub struct AuthContext {
pub account_id: Uuid,
pub role: AccountRole,
pub permissions: Vec<String>,
pub auth_method: AuthMethod, // Jwt | ApiToken
}
// 中间件从 Authorization: Bearer <jwt> 或 X-API-Key: <token> 提取身份
// 将 AuthContext 注入 request extensions
```
### 4.3 角色与权限
权限使用字符串常量检查时匹配字符串列表:
```rust
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 RELAY_USE: &str = "relay:use";
pub const RELAY_ADMIN: &str = "relay:admin";
pub const CONFIG_READ: &str = "config:read";
pub const CONFIG_WRITE: &str = "config:write";
pub const ADMIN_FULL: &str = "admin:full";
}
```
| 角色 | 权限 |
|------|------|
| `super_admin` | `admin:full` (授权检查时直接放行) |
| `admin` | account:read/write, model:read/write, relay:use/admin, config:read/write |
| `user` | model:read, relay:use, config:read |
### 4.4 API 端点
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | `/api/v1/auth/register` | admin:full | 创建账号 |
| POST | `/api/v1/auth/login` | 公开 | 登录返回 JWT |
| POST | `/api/v1/auth/refresh` | JWT | 刷新 JWT |
| POST | `/api/v1/auth/totp/setup` | JWT | 获取 TOTP 设置 URI |
| 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 | 更新账号 |
| PATCH | `/api/v1/accounts/:id/status` | account:admin | 启用/禁用 |
| GET | `/api/v1/tokens` | - (本人) | 列出 API 令牌 |
| POST | `/api/v1/tokens` | - (本人) | 创建令牌 |
| DELETE | `/api/v1/tokens/:id` | - (本人) | 撤销令牌 |
| GET | `/api/v1/roles` | account:read | 角色列表 |
| POST | `/api/v1/roles` | account:admin | 创建自定义角色 |
| PUT | `/api/v1/roles/:id` | account:admin | 更新角色权限 |
| DELETE | `/api/v1/roles/:id` | account:admin | 删除自定义角色 |
| GET | `/api/v1/permission-templates` | account:read | 模板列表 |
| POST | `/api/v1/permission-templates` | account:admin | 创建模板 |
| POST | `/api/v1/permission-templates/:id/apply` | account:admin | 批量应用 |
| GET | `/api/v1/logs/operations` | account:read | 操作日志 (分页) |
### 4.5 关键数据结构
```rust
pub struct Account {
pub id: Uuid,
pub username: String,
pub email: String,
pub password_hash: String, // skip_serializing
pub display_name: String,
pub avatar_url: Option<String>,
pub role: AccountRole,
pub status: AccountStatus,
pub totp_enabled: bool,
pub last_login_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub enum AccountRole { SuperAdmin, Admin, User }
pub enum AccountStatus { Active, Disabled, Suspended }
```
---
## 5. 模块二: 模型与 API 配置中心
### 5.1 提供商管理
提供商存储服务端 API 密钥 (加密) 和基础配置管理员创建提供商后可为每个提供商添加多个模型
**API 密钥加密**: AES-256-GCM密钥通过 HKDF-SHA256 从服务器 JWT 密钥派生
### 5.2 账号级 API 密钥
每个账号可以为每个提供商配置独立的 API 密钥密钥权限 (JSON 数组) 控制该密钥可访问的模型 ID 列表支持:
- 生成: 创建新密钥返回明文 (仅一次)
- 轮换: 生成新密钥替换旧密钥
- 禁用: 临时禁用
- 撤销: 永久删除
### 5.3 使用量统计
每次中转请求完成后记录 `usage_records`统计查询使用 SQL 聚合MVP 阶段不需要预计算视图
### 5.4 API 端点
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| 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 | 公开模型目录 |
### 5.5 初始种子数据
`config/chinese-providers.toml` 导入:
| 提供商 | 模型 |
|--------|------|
| 智谱 AI | GLM-4-Plus, GLM-4-Flash, GLM-4V-Plus, GLM-Z1-AirX |
| 通义千问 | Qwen-Max, Qwen-Plus, Qwen-Turbo, Qwen-VL-Max |
| Kimi | moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k |
| DeepSeek | deepseek-chat, deepseek-reasoner |
---
## 6. 模块三: 模型请求中转服务
### 6.1 请求流
```
桌面客户端
POST /api/v1/relay/chat/completions
Headers: Authorization: Bearer <api_token> 或 X-API-Key: <token>
Body: { model: "glm-4-plus", messages: [...], stream: true }
认证中间件 → 提取 account_id + permissions
权限检查 → relay:use
提供商解析 → models 表查找 provider_id → providers 表获取 base_url + api_protocol
API 密钥解析 → account_api_keys 表获取该账号的提供商密钥
速率限制检查 → 令牌桶算法
队列调度 → tokio::sync::mpsc (如并发已满则排队)
转发请求 → reqwest::Client POST 到上游提供商
流式响应 → SSE 转发给客户端 (stream: true)
记录使用量 → usage_records 表
```
### 6.2 队列与调度
```rust
pub struct RelayService {
queues: Arc<DashMap<Uuid, mpsc::Sender<RelayTask>>>, // 每提供商独立队列
semaphores: Arc<DashMap<Uuid, Arc<Semaphore>>>, // 并发控制
clients: Arc<DashMap<Uuid, reqwest::Client>>, // 每提供商 HTTP 客户端
config: RelayConfig,
}
```
- 每提供商一个 mpsc channel (容量 100)
- 每提供商一个 Semaphore 控制并发 (默认 5)
- Worker task channel 拉取请求并执行
### 6.3 速率限制
令牌桶算法:
- RPM (每分钟请求数): `tokio::sync::Semaphore` + 定时释放
- TPM (每分钟 token ): 基于最近一分钟 `usage_records` SQL 查询
### 6.4 错误处理与重试
- 瞬态错误 (超时, 5xx): 指数退避重试 (1s, 2s, 4s)最多 3
- 客户端错误 (4xx, 认证失败): 立即返回不重试
- 提供商不可用: 标记 provider disabled通知管理员
### 6.5 API 端点
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | `/api/v1/relay/chat/completions` | relay:use | 中转聊天补全 (支持流式) |
| GET | `/api/v1/relay/status` | relay:admin | 队列状态/提供商健康 |
| GET | `/api/v1/relay/tasks` | relay:admin | 任务列表 |
| GET | `/api/v1/relay/tasks/:id` | relay:admin | 任务详情 |
| DELETE | `/api/v1/relay/tasks/:id` | relay:admin | 取消排队任务 |
| POST | `/api/v1/relay/retry/:id` | relay:admin | 重试失败任务 |
### 6.6 桌面端集成
桌面端 `OpenAiDriver` `base_url` 指向 SaaS 中转:
```
原来: base_url = "https://open.bigmodel.cn/api/paas/v4"
改为: base_url = "https://saas.zclaw.example.com/api/v1/relay"
添加: X-API-Key: zclaw_xxxxx (账号的 API Token)
```
中转保持 OpenAI 兼容格式桌面端无需任何协议适配
---
## 7. 模块四: 系统配置迁移分析
### 7.1 迁移优先级分类
基于 `config/config.toml``config/chinese-providers.toml``config/security.toml` 分析:
**Critical (必须迁移)**:
- `llm.providers[].api_key` API 密钥应服务端管理
- `llm.providers[].base_url` 提供商端点集中管控
- `llm.providers[].models[]` 模型目录从 SaaS 下发
- `llm.default_provider` 默认提供商可按账号配置
- `llm.default_model` 默认模型可按账号配置
**High (重要)**:
- `llm.requests_per_minute` / `llm.tokens_per_minute` 按账号限流
- `llm.max_retries` / `llm.retry_delay` 重试策略
- `security.auth.token_expiration` 认证配置
- `security.rate_limit.*` 限流配置
**Medium (有益)**:
- `agent.defaults.max_sessions` 会话限制
- `hands.default_approval_mode` 默认审批模式
- `skills.execution_timeout` 技能超时
**Low (可选)**:
- `desktop.ui.*` UI 偏好
- `logging.level` 日志级别
**Local Only (不迁移)**:
- `server.host/port` 服务端特有
- `agent.defaults.workspace` 本地文件路径
- `security.shell_exec.*` 本地安全策略
- `tools.fs.allowed_paths` 本地文件路径
- `logging.file.path` 本地文件路径
### 7.2 同步协议
```
桌面客户端 SaaS 服务端
│ │
│ POST /api/v1/config/sync │
│ { action: "push", │
│ fingerprint: "machine_xxx", │
│ snapshot: { ... } } │
│ ──────────────────────────────► │
│ │ 比对 SaaS 存储
│ { updates: [...], │
│ conflicts: [...] } │
│ ◄────────────────────────────── │
│ │
│ 应用更新 / 提示冲突 │
│ │
```
**冲突解决策略**:
- `source: "saas"` / `"override"` 默认 saas_wins
- `source: "local"` 默认 client_wins
- 管理员可按配置项覆盖
### 7.3 API 端点
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/api/v1/migration/report` | config:read | 迁移分析报告 |
| POST | `/api/v1/migration/analyze` | config:read | 分析配置快照 |
| GET | `/api/v1/migration/items` | config:read | 配置项列表 |
| PUT | `/api/v1/migration/items/:id` | config:write | 更新配置项 |
| PUT | `/api/v1/migration/items/batch` | config:write | 批量更新 |
| POST | `/api/v1/config/sync` | - (本人) | 客户端SaaS 同步 |
| GET | `/api/v1/config/snapshot` | config:read | SaaS 配置快照 |
| GET | `/api/v1/config/diff` | config:read | 客户端与 SaaS 差异 |
---
## 8. 管理后台前端
### 8.1 技术栈
React 18 + TypeScript + Tailwind CSS + Recharts + React Router
### 8.2 页面清单
| 路径 | 模块 | 说明 |
|------|------|------|
| `/admin/login` | 通用 | 登录页 (JWT 认证) |
| `/admin/dashboard` | 通用 | 概览仪表板 (账号数/使用量/中转状态) |
| `/admin/accounts` | M1 | 账号列表 (搜索/角色筛选/状态筛选) |
| `/admin/accounts/:id` | M1 | 账号详情 (编辑/查看令牌/查看日志) |
| `/admin/roles` | M1 | 角色权限矩阵编辑 |
| `/admin/templates` | M1 | 权限模板 CRUD + 批量应用 |
| `/admin/logs` | M1 | 操作日志 (分页/筛选/时间范围) |
| `/admin/profile` | M1 | 个人设置 (密码/2FA/显示名) |
| `/admin/providers` | M2 | 提供商卡片 (状态/模型数) |
| `/admin/providers/:id` | M2 | 提供商详情/编辑 |
| `/admin/providers/:id/models` | M2 | 模型表格 (添加/编辑/禁用) |
| `/admin/api-keys` | M2 | API 密钥管理 (创建/轮换/撤销) |
| `/admin/usage` | M2 | 使用量仪表板 (图表/按提供商/按模型/按日期) |
| `/admin/relay/dashboard` | M3 | 中转状态 (队列/提供商健康/实时指标) |
| `/admin/relay/tasks` | M3 | 任务列表 (状态筛选/取消/重试) |
| `/admin/migration/report` | M4 | 迁移分析报告 (分类统计/优先级图表) |
| `/admin/migration/items` | M4 | 配置项管理 (分类/优先级/来源筛选) |
| `/admin/config/sync-log` | M4 | 同步日志 (冲突详情/解决记录) |
---
## 9. 实施阶段
### Phase 1: 基础 + 账号模块
1. `crates/zclaw-saas/` 基础结构 (Cargo.toml, db.rs, config.rs, state.rs, error.rs)
2. `auth/` 模块 (JWT, Argon2, TOTP, 中间件)
3. `account/` 模块 (CRUD, 角色, 模板, 操作日志)
4. `saas-admin/` 脚手架 (登录 + 账号管理页面)
5. 验证: 注册登录创建 API Token操作日志
### Phase 2: 模型配置模块
1. `model_config/` 模块 (提供商/模型 CRUD)
2. 种子数据导入 (`chinese-providers.toml`)
3. 账号 API 密钥管理 + 使用量统计
4. 管理后台: 提供商/模型/API 密钥/使用量
5. 验证: 创建提供商添加模型分配密钥查看使用量
### Phase 3: 中转服务模块
1. `relay/` 模块 (队列, 调度, 流式转发)
2. 速率限制 + 重试逻辑
3. 管理后台: 中转仪表板/任务列表
4. 桌面端集成 ( `base_url`)
5. 验证: 桌面端中转提供商流式响应使用量记录
### Phase 4: 配置迁移模块
1. `migration/` 模块 (配置分析, 同步协议)
2. 种子配置项 ( TOML 文件分析)
3. 桌面端 `SaaSConfigClient`
4. 更新 `KernelConfig::load()` 支持 SaaS 覆盖
5. 验证: 配置同步覆盖冲突检测解决
---
## 10. 验证方案
### 每阶段验证
1. `cargo build -p zclaw-saas` 编译通过
2. `cargo test -p zclaw-saas` 单元测试通过
3. `curl` 测试每个 API 端点
4. 管理后台操作 验证数据库状态
### 最终端到端验证
1. **完整流程**: 注册账号 登录 创建 API Token 配置提供商 桌面端通过中转调用模型 查看使用量 配置同步
2. **安全验证**: JWT 过期拒绝权限不足拒绝API Token 撤销后拒绝TOTP 强制验证
3. **错误处理**: 提供商不可用降级速率限制触发排队队列溢出错误配置冲突检测
---
## 11. 关键参考文件
| 文件 | 用途 |
|------|------|
| `crates/zclaw-kernel/src/config.rs` | LlmConfig/ApiProtocol 模式参考 |
| `crates/zclaw-memory/src/schema.rs` | SQL schema 模式参考 |
| `crates/zclaw-runtime/src/driver/openai.rs` | 中转转发实现参考 |
| `crates/zclaw-runtime/src/driver/mod.rs` | LlmDriver trait 参考 |
| `config/chinese-providers.toml` | 提供商种子数据源 |
| `config/config.toml` | 配置迁移分析源 |
| `config/security.toml` | 安全配置迁移分析源 |
| `desktop/src-tauri/src/secure_storage.rs` | 密钥加密存储参考 |
| `desktop/src-tauri/src/lib.rs` | Tauri 命令模式参考 |