feat: 增强SaaS后端功能与安全性
refactor: 重构数据库连接使用PostgreSQL替代SQLite feat(auth): 增加JWT验证的audience和issuer检查 feat(crypto): 添加AES-256-GCM字段加密支持 feat(api): 集成utoipa实现OpenAPI文档 fix(admin): 修复配置项表单验证逻辑 style: 统一代码格式与类型定义 docs: 更新技术栈文档说明PostgreSQL
This commit is contained in:
@@ -51,8 +51,8 @@ ZCLAW 当前是纯桌面单用户应用,缺少用户账号系统、API 服务
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ SQLite (WAL) │
|
||||
│ saas-data.db │
|
||||
│ PostgreSQL │
|
||||
│ zclaw 数据库 │
|
||||
└───────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
@@ -136,8 +136,8 @@ saas-admin/ # 独立 React 管理后台
|
||||
|
||||
### 3.1 概述
|
||||
|
||||
- 引擎: SQLite WAL 模式
|
||||
- 文件: 独立于桌面端 `~/.zclaw/data.db`,默认 `./saas-data.db`
|
||||
- 引擎: PostgreSQL 16
|
||||
- 连接: 通过 `DATABASE_URL` 环境变量配置 (推荐) 或 `saas-config.toml` 中指定
|
||||
- 迁移: 版本化 schema,启动时自动迁移
|
||||
|
||||
### 3.2 完整 Schema
|
||||
@@ -162,10 +162,10 @@ CREATE TABLE IF NOT EXISTS accounts (
|
||||
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
|
||||
totp_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_role ON accounts(role);
|
||||
@@ -177,10 +177,10 @@ CREATE TABLE IF NOT EXISTS api_tokens (
|
||||
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,
|
||||
last_used_at TIMESTAMPTZ,
|
||||
expires_at TIMESTAMPTZ, -- NULL = 永不过期
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
revoked_at TIMESTAMPTZ,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_api_tokens_account ON api_tokens(account_id);
|
||||
@@ -191,9 +191,9 @@ CREATE TABLE IF NOT EXISTS roles (
|
||||
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
|
||||
is_system BOOLEAN NOT NULL DEFAULT false, -- 系统角色不可删除
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permission_templates (
|
||||
@@ -201,19 +201,19 @@ CREATE TABLE IF NOT EXISTS permission_templates (
|
||||
name TEXT NOT NULL, -- e.g. "标准用户", "只读用户"
|
||||
description TEXT,
|
||||
permissions TEXT NOT NULL DEFAULT '[]', -- JSON 权限数组
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS operation_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
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
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
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);
|
||||
@@ -230,12 +230,12 @@ CREATE TABLE IF NOT EXISTS providers (
|
||||
api_key TEXT, -- 服务端提供商 API 密钥 (加密存储)
|
||||
base_url TEXT NOT NULL,
|
||||
api_protocol TEXT NOT NULL DEFAULT 'openai', -- 'openai' | 'anthropic'
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
rate_limit_rpm INTEGER, -- 每分钟请求数
|
||||
rate_limit_tpm INTEGER, -- 每分钟 token 数
|
||||
config_json TEXT DEFAULT '{}', -- 提供商特定配置
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS models (
|
||||
@@ -245,13 +245,13 @@ CREATE TABLE IF NOT EXISTS models (
|
||||
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,
|
||||
supports_streaming BOOLEAN NOT NULL DEFAULT true,
|
||||
supports_vision BOOLEAN NOT NULL DEFAULT false,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
pricing_input DOUBLE PRECISION DEFAULT 0, -- 每 1K token 价格
|
||||
pricing_output DOUBLE PRECISION DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(provider_id, model_id),
|
||||
FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -264,18 +264,18 @@ CREATE TABLE IF NOT EXISTS account_api_keys (
|
||||
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,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
last_used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
revoked_at TIMESTAMPTZ,
|
||||
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,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
account_id TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
model_id TEXT NOT NULL,
|
||||
@@ -284,7 +284,7 @@ CREATE TABLE IF NOT EXISTS usage_records (
|
||||
latency_ms INTEGER,
|
||||
status TEXT NOT NULL DEFAULT 'success', -- 'success' | 'error' | 'rate_limited'
|
||||
error_message TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
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);
|
||||
@@ -308,10 +308,10 @@ CREATE TABLE IF NOT EXISTS relay_tasks (
|
||||
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
|
||||
queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
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);
|
||||
@@ -330,15 +330,15 @@ CREATE TABLE IF NOT EXISTS config_items (
|
||||
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,
|
||||
requires_restart BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
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,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
account_id TEXT NOT NULL,
|
||||
client_fingerprint TEXT NOT NULL,
|
||||
action TEXT NOT NULL, -- 'push' | 'pull' | 'conflict'
|
||||
@@ -346,7 +346,7 @@ CREATE TABLE IF NOT EXISTS config_sync_log (
|
||||
client_values TEXT, -- JSON: 客户端值
|
||||
saas_values TEXT, -- JSON: SaaS 值
|
||||
resolution TEXT, -- 'client_wins' | 'saas_wins' | 'manual'
|
||||
created_at TEXT NOT NULL
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_sync_account ON config_sync_log(account_id);
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user