Phase 8: 完成多租户SaaS架构、订阅计费系统、企业级功能

- 任务1: 多租户SaaS架构 (tenant_manager.py)
- 任务2: 订阅与计费系统 (subscription_manager.py)
- 任务3: 企业级功能 (enterprise_manager.py)
- 更新 schema.sql 添加所有相关表
- 更新 main.py 添加所有API端点
- 更新 README.md 进度表
This commit is contained in:
OpenClaw Bot
2026-02-25 18:42:29 +08:00
parent 5743d05bb5
commit 911e891451
19 changed files with 6148 additions and 604 deletions

View File

@@ -1060,125 +1060,349 @@ CREATE INDEX IF NOT EXISTS idx_usage_tenant ON tenant_usage(tenant_id);
CREATE INDEX IF NOT EXISTS idx_usage_date ON tenant_usage(date);
-- ============================================
-- Phase 8: Multi-Tenant SaaS Architecture
-- Phase 8 Task 2: 订阅与计费系统
-- ============================================
-- 租户主
CREATE TABLE IF NOT EXISTS tenants (
-- 订阅计划
CREATE TABLE IF NOT EXISTS subscription_plans (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL, -- URL 友好的唯一标识
description TEXT DEFAULT '',
status TEXT DEFAULT 'active', -- active, suspended, trial, expired, pending
plan TEXT DEFAULT 'free', -- free, starter, professional, enterprise
max_projects INTEGER DEFAULT 5,
max_members INTEGER DEFAULT 10,
max_storage_gb REAL DEFAULT 1.0,
max_api_calls_per_day INTEGER DEFAULT 1000,
billing_email TEXT DEFAULT '',
subscription_start TEXT,
subscription_end TEXT,
tier TEXT UNIQUE NOT NULL, -- free/pro/enterprise
description TEXT,
price_monthly REAL DEFAULT 0,
price_yearly REAL DEFAULT 0,
currency TEXT DEFAULT 'CNY',
features TEXT DEFAULT '[]', -- JSON array
limits TEXT DEFAULT '{}', -- JSON object
is_active INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by TEXT DEFAULT '', -- 创建者用户ID
db_schema TEXT DEFAULT '', -- 数据库 schema 名称
table_prefix TEXT DEFAULT '' -- 表前缀
metadata TEXT DEFAULT '{}'
);
-- 租户域名绑定
CREATE TABLE IF NOT EXISTS tenant_domains (
-- 订阅
CREATE TABLE IF NOT EXISTS subscriptions (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
domain TEXT NOT NULL, -- 自定义域名
status TEXT DEFAULT 'pending', -- pending, verified, active, failed, expired
verification_record TEXT DEFAULT '', -- DNS TXT 记录值
verification_expires_at TEXT,
ssl_enabled INTEGER DEFAULT 0,
ssl_cert_path TEXT,
ssl_key_path TEXT,
ssl_expires_at TEXT,
plan_id TEXT NOT NULL,
status TEXT DEFAULT 'pending', -- active/cancelled/expired/past_due/trial/pending
current_period_start TIMESTAMP,
current_period_end TIMESTAMP,
cancel_at_period_end INTEGER DEFAULT 0,
canceled_at TIMESTAMP,
trial_start TIMESTAMP,
trial_end TIMESTAMP,
payment_provider TEXT, -- stripe/alipay/wechat/bank_transfer
provider_subscription_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
verified_at TEXT,
UNIQUE(tenant_id, domain),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata TEXT DEFAULT '{}',
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
FOREIGN KEY (plan_id) REFERENCES subscription_plans(id)
);
-- 用量记录表
CREATE TABLE IF NOT EXISTS usage_records (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
resource_type TEXT NOT NULL, -- transcription/storage/api_call/export
quantity REAL DEFAULT 0,
unit TEXT NOT NULL, -- minutes/mb/count/page
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
cost REAL DEFAULT 0,
description TEXT,
metadata TEXT DEFAULT '{}',
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 租户品牌配置表(白标)
CREATE TABLE IF NOT EXISTS tenant_branding (
-- 支付记录表
CREATE TABLE IF NOT EXISTS payments (
id TEXT PRIMARY KEY,
tenant_id TEXT UNIQUE NOT NULL,
logo_url TEXT,
logo_dark_url TEXT, -- 深色模式 Logo
favicon_url TEXT,
primary_color TEXT DEFAULT '#3B82F6',
secondary_color TEXT DEFAULT '#10B981',
accent_color TEXT DEFAULT '#F59E0B',
background_color TEXT DEFAULT '#FFFFFF',
text_color TEXT DEFAULT '#1F2937',
dark_primary_color TEXT DEFAULT '#60A5FA',
dark_background_color TEXT DEFAULT '#111827',
dark_text_color TEXT DEFAULT '#F9FAFB',
font_family TEXT DEFAULT 'Inter, system-ui, sans-serif',
heading_font_family TEXT,
custom_css TEXT DEFAULT '',
custom_js TEXT DEFAULT '',
app_name TEXT DEFAULT 'InsightFlow',
login_page_title TEXT DEFAULT '登录到 InsightFlow',
login_page_description TEXT DEFAULT '',
footer_text TEXT DEFAULT '© 2024 InsightFlow',
tenant_id TEXT NOT NULL,
subscription_id TEXT,
invoice_id TEXT,
amount REAL NOT NULL,
currency TEXT DEFAULT 'CNY',
provider TEXT NOT NULL, -- stripe/alipay/wechat/bank_transfer
provider_payment_id TEXT,
status TEXT DEFAULT 'pending', -- pending/processing/completed/failed/refunded/partial_refunded
payment_method TEXT,
payment_details TEXT DEFAULT '{}', -- JSON
paid_at TIMESTAMP,
failed_at TIMESTAMP,
failure_reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE SET NULL,
FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE SET NULL
);
-- 发票表
CREATE TABLE IF NOT EXISTS invoices (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
subscription_id TEXT,
invoice_number TEXT UNIQUE NOT NULL,
status TEXT DEFAULT 'draft', -- draft/issued/paid/overdue/void/credit_note
amount_due REAL DEFAULT 0,
amount_paid REAL DEFAULT 0,
currency TEXT DEFAULT 'CNY',
period_start TIMESTAMP,
period_end TIMESTAMP,
description TEXT,
line_items TEXT DEFAULT '[]', -- JSON array
due_date TIMESTAMP,
paid_at TIMESTAMP,
voided_at TIMESTAMP,
void_reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE SET NULL
);
-- 退款表
CREATE TABLE IF NOT EXISTS refunds (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
payment_id TEXT NOT NULL,
invoice_id TEXT,
amount REAL NOT NULL,
currency TEXT DEFAULT 'CNY',
reason TEXT,
status TEXT DEFAULT 'pending', -- pending/approved/rejected/completed/failed
requested_by TEXT NOT NULL,
requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
approved_by TEXT,
approved_at TIMESTAMP,
completed_at TIMESTAMP,
provider_refund_id TEXT,
metadata TEXT DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
FOREIGN KEY (payment_id) REFERENCES payments(id) ON DELETE CASCADE,
FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE SET NULL
);
-- 账单历史表
CREATE TABLE IF NOT EXISTS billing_history (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
type TEXT NOT NULL, -- subscription/usage/payment/refund
amount REAL NOT NULL,
currency TEXT DEFAULT 'CNY',
description TEXT,
reference_id TEXT, -- 关联的订阅/支付/退款ID
balance_after REAL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata TEXT DEFAULT '{}',
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 订阅相关索引
CREATE INDEX IF NOT EXISTS idx_subscriptions_tenant ON subscriptions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_subscriptions_plan ON subscriptions(plan_id);
CREATE INDEX IF NOT EXISTS idx_usage_records_tenant ON usage_records(tenant_id);
CREATE INDEX IF NOT EXISTS idx_usage_records_type ON usage_records(resource_type);
CREATE INDEX IF NOT EXISTS idx_usage_records_recorded ON usage_records(recorded_at);
CREATE INDEX IF NOT EXISTS idx_payments_tenant ON payments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status);
CREATE INDEX IF NOT EXISTS idx_payments_provider ON payments(provider);
CREATE INDEX IF NOT EXISTS idx_invoices_tenant ON invoices(tenant_id);
CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status);
CREATE INDEX IF NOT EXISTS idx_invoices_number ON invoices(invoice_number);
CREATE INDEX IF NOT EXISTS idx_refunds_tenant ON refunds(tenant_id);
CREATE INDEX IF NOT EXISTS idx_refunds_status ON refunds(status);
CREATE INDEX IF NOT EXISTS idx_refunds_payment ON refunds(payment_id);
CREATE INDEX IF NOT EXISTS idx_billing_history_tenant ON billing_history(tenant_id);
CREATE INDEX IF NOT EXISTS idx_billing_history_created ON billing_history(created_at);
CREATE INDEX IF NOT EXISTS idx_billing_history_type ON billing_history(type);
-- ============================================
-- Phase 8 Task 3: 企业级功能
-- ============================================
-- SSO 配置表
CREATE TABLE IF NOT EXISTS sso_configs (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
provider TEXT NOT NULL, -- wechat_work/dingtalk/feishu/okta/azure_ad/google/custom_saml
status TEXT DEFAULT 'disabled', -- disabled/pending/active/error
entity_id TEXT,
sso_url TEXT,
slo_url TEXT,
certificate TEXT, -- X.509 证书
metadata_url TEXT,
metadata_xml TEXT,
client_id TEXT,
client_secret TEXT,
authorization_url TEXT,
token_url TEXT,
userinfo_url TEXT,
scopes TEXT DEFAULT '["openid", "email", "profile"]',
attribute_mapping TEXT DEFAULT '{}', -- JSON: 属性映射
auto_provision INTEGER DEFAULT 1, -- 自动创建用户
default_role TEXT DEFAULT 'member',
domain_restriction TEXT DEFAULT '[]', -- JSON: 允许的邮箱域名
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_tested_at TIMESTAMP,
last_error TEXT,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- SAML 认证请求表
CREATE TABLE IF NOT EXISTS saml_auth_requests (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
sso_config_id TEXT NOT NULL,
request_id TEXT NOT NULL UNIQUE,
relay_state TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
used INTEGER DEFAULT 0,
used_at TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
FOREIGN KEY (sso_config_id) REFERENCES sso_configs(id) ON DELETE CASCADE
);
-- SAML 认证响应表
CREATE TABLE IF NOT EXISTS saml_auth_responses (
id TEXT PRIMARY KEY,
request_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
user_id TEXT,
email TEXT,
name TEXT,
attributes TEXT DEFAULT '{}', -- JSON: SAML 属性
session_index TEXT,
processed INTEGER DEFAULT 0,
processed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (request_id) REFERENCES saml_auth_requests(request_id) ON DELETE CASCADE,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- SCIM 配置表
CREATE TABLE IF NOT EXISTS scim_configs (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
provider TEXT NOT NULL,
status TEXT DEFAULT 'disabled',
scim_base_url TEXT,
scim_token TEXT,
sync_interval_minutes INTEGER DEFAULT 60,
last_sync_at TIMESTAMP,
last_sync_status TEXT,
last_sync_error TEXT,
last_sync_users_count INTEGER DEFAULT 0,
attribute_mapping TEXT DEFAULT '{}',
sync_rules TEXT DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 租户成员
CREATE TABLE IF NOT EXISTS tenant_members (
-- SCIM 用户
CREATE TABLE IF NOT EXISTS scim_users (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
external_id TEXT NOT NULL,
user_name TEXT NOT NULL,
email TEXT NOT NULL,
name TEXT DEFAULT '',
role TEXT DEFAULT 'viewer', -- owner, admin, editor, viewer, guest
status TEXT DEFAULT 'invited', -- active, invited, suspended, removed
invited_by TEXT,
invited_at TEXT,
invitation_token TEXT,
invitation_expires_at TEXT,
joined_at TEXT,
last_active_at TEXT,
custom_permissions TEXT DEFAULT '[]', -- JSON 数组
display_name TEXT,
given_name TEXT,
family_name TEXT,
active INTEGER DEFAULT 1,
groups TEXT DEFAULT '[]',
raw_data TEXT DEFAULT '{}',
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, user_id),
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
UNIQUE(tenant_id, external_id)
);
-- 审计日志导出表
CREATE TABLE IF NOT EXISTS audit_log_exports (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
export_format TEXT NOT NULL, -- json/csv/pdf/xlsx
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
filters TEXT DEFAULT '{}',
compliance_standard TEXT, -- soc2/iso27001/gdpr/hipaa/pci_dss
status TEXT DEFAULT 'pending', -- pending/processing/completed/failed
file_path TEXT,
file_size INTEGER,
record_count INTEGER,
checksum TEXT,
downloaded_by TEXT,
downloaded_at TIMESTAMP,
expires_at TIMESTAMP,
created_by TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP,
error_message TEXT,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 租户角色
CREATE TABLE IF NOT EXISTS tenant_roles (
-- 数据保留策略
CREATE TABLE IF NOT EXISTS data_retention_policies (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT DEFAULT '',
permissions TEXT DEFAULT '[]', -- JSON 数组
is_system INTEGER DEFAULT 0, -- 1=系统预设, 0=自定义
description TEXT,
resource_type TEXT NOT NULL, -- project/transcript/entity/audit_log/user_data
retention_days INTEGER NOT NULL,
action TEXT NOT NULL, -- archive/delete/anonymize
conditions TEXT DEFAULT '{}',
auto_execute INTEGER DEFAULT 0,
execute_at TEXT, -- cron 表达式
notify_before_days INTEGER DEFAULT 7,
archive_location TEXT,
archive_encryption INTEGER DEFAULT 1,
is_active INTEGER DEFAULT 1,
last_executed_at TIMESTAMP,
last_execution_result TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 租户相关索引
CREATE INDEX IF NOT EXISTS idx_tenants_slug ON tenants(slug);
CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status);
CREATE INDEX IF NOT EXISTS idx_domains_tenant ON tenant_domains(tenant_id);
CREATE INDEX IF NOT EXISTS idx_domains_domain ON tenant_domains(domain);
CREATE INDEX IF NOT EXISTS idx_domains_status ON tenant_domains(status);
CREATE INDEX IF NOT EXISTS idx_members_tenant ON tenant_members(tenant_id);
CREATE INDEX IF NOT EXISTS idx_members_user ON tenant_members(user_id);
CREATE INDEX IF NOT EXISTS idx_members_role ON tenant_members(role);
CREATE INDEX IF NOT EXISTS idx_members_status ON tenant_members(status);
CREATE INDEX IF NOT EXISTS idx_members_token ON tenant_members(invitation_token);
CREATE INDEX IF NOT EXISTS idx_roles_tenant ON tenant_roles(tenant_id);
-- 数据保留任务表
CREATE TABLE IF NOT EXISTS data_retention_jobs (
id TEXT PRIMARY KEY,
policy_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
status TEXT DEFAULT 'pending', -- pending/running/completed/failed
started_at TIMESTAMP,
completed_at TIMESTAMP,
affected_records INTEGER DEFAULT 0,
archived_records INTEGER DEFAULT 0,
deleted_records INTEGER DEFAULT 0,
error_count INTEGER DEFAULT 0,
details TEXT DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (policy_id) REFERENCES data_retention_policies(id) ON DELETE CASCADE,
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
-- 更新项目表,添加租户关联(可选,支持租户隔离)
ALTER TABLE projects ADD COLUMN tenant_id TEXT;
CREATE INDEX IF NOT EXISTS idx_projects_tenant ON projects(tenant_id);
-- 企业级功能相关索引
CREATE INDEX IF NOT EXISTS idx_sso_tenant ON sso_configs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_sso_provider ON sso_configs(provider);
CREATE INDEX IF NOT EXISTS idx_saml_requests_config ON saml_auth_requests(sso_config_id);
CREATE INDEX IF NOT EXISTS idx_saml_requests_expires ON saml_auth_requests(expires_at);
CREATE INDEX IF NOT EXISTS idx_saml_responses_request ON saml_auth_responses(request_id);
CREATE INDEX IF NOT EXISTS idx_scim_config_tenant ON scim_configs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_scim_users_tenant ON scim_users(tenant_id);
CREATE INDEX IF NOT EXISTS idx_scim_users_external ON scim_users(external_id);
CREATE INDEX IF NOT EXISTS idx_audit_export_tenant ON audit_log_exports(tenant_id);
CREATE INDEX IF NOT EXISTS idx_audit_export_status ON audit_log_exports(status);
CREATE INDEX IF NOT EXISTS idx_retention_tenant ON data_retention_policies(tenant_id);
CREATE INDEX IF NOT EXISTS idx_retention_type ON data_retention_policies(resource_type);
CREATE INDEX IF NOT EXISTS idx_retention_jobs_policy ON data_retention_jobs(policy_id);
CREATE INDEX IF NOT EXISTS idx_retention_jobs_status ON data_retention_jobs(status);