From b3a31ec48bcc5dae42e70a7622012f3334df6d1d Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 27 Mar 2026 12:16:19 +0800 Subject: [PATCH] =?UTF-8?q?docs(saas):=20=E6=B7=BB=E5=8A=A0=20SaaS=20?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E7=B3=BB=E7=BB=9F=E8=AE=BE=E8=AE=A1=E8=A7=84?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 涵盖四大核心模块的完整设计: - 账号权限管理 (JWT/Argon2/TOTP/权限模板) - 模型与 API 配置中心 (提供商/模型/密钥/使用量) - 模型请求中转服务 (队列/流式转发/速率限制) - 系统配置迁移分析 (迁移优先级/同步协议) 技术栈: Rust + Axum 后端 + React 管理后台 目标: MVP <100 用户, 独立 SaaS 服务 --- .../specs/2026-03-27-saas-backend-design.md | 763 ++++++++++++++++++ 1 file changed, 763 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-27-saas-backend-design.md diff --git a/docs/superpowers/specs/2026-03-27-saas-backend-design.md b/docs/superpowers/specs/2026-03-27-saas-backend-design.md new file mode 100644 index 0000000..d8f10bf --- /dev/null +++ b/docs/superpowers/specs/2026-03-27-saas-backend-design.md @@ -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, + pub auth_method: AuthMethod, // Jwt | ApiToken +} + +// 中间件从 Authorization: Bearer 或 X-API-Key: 提取身份 +// 将 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, + pub role: AccountRole, + pub status: AccountStatus, + pub totp_enabled: bool, + pub last_login_at: Option>, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +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 或 X-API-Key: + 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>>, // 每提供商独立队列 + semaphores: Arc>>, // 并发控制 + clients: Arc>, // 每提供商 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 命令模式参考 |