跳转至

🏗 ExitVideo-Bot · SaaS 多租户架构

版本:v1.0 · 作者:MiniMax · 2026-09-27

从"开源工具"转型"多租户 SaaS"的核心架构决策


目录


一、设计原则

商业 SaaS 的 7 条铁律

1. 多租户隔离是底线:租户数据绝不混
2. 计费透明可审计:每一笔消费都对得上
3. API 限流是基础:保护自己不被滥用
4. 异步任务可重入:任务失败必能重做
5. 数据保留有期限:不存 100 年
6. 权限细化到资源:RBAC 走到底
7. 监控覆盖每跳:N 个微服务 = N 个 trace

二、单租户 vs 多租户:决策

2.1 三种模式对比

模式 隔离强度 工程复杂度 适用规模
共享数据库 + tenant_id 列 🟡 软隔离 🟢 低 50-500 客户
共享数据库 + Row-Level Security 🟢 强隔离 🟡 中 500+
独立数据库 per 客户 🟢🟢 极强隔离 🔴 高 Enterprise

2.2 推荐方案:RLS PostgreSQL

为什么不用"列加 tenant_id"? - 开发阶段易漏写过滤条件 - 安全风险 = 数据库直连即泄露

为什么不用"独立数据库 per 客户"? - 1 个客户 1 个数据库,运维成本爆炸(> 100 客户就崩溃) - 迁移、备份、连接池全是问题

推荐:PostgreSQL + Row-Level Security + 应用层 tenant context

-- 创建表时启用 RLS
CREATE TABLE accounts (
    id BIGINT PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    platform TEXT,
    ...
);

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON accounts
    USING (tenant_id = current_setting('app.tenant_id')::UUID);

-- 每个请求开始时设置
SET LOCAL app.tenant_id = '<tenant_uuid>';

应用层:

# api/middleware/auth.py
async def set_tenant_context(db: AsyncSession, tenant_id: str):
    await db.execute(f"SET LOCAL app.tenant_id = '{tenant_id}'")
    yield  # 请求周期内有效
    # 自动结束,无需 tearDown


三、多租户数据隔离

3.1 关键实体 ER 图

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│   tenants    │ 1─────* │    users     │ 1─────* │   api_keys   │
└──────┬───────┘         └──────┬───────┘         └──────────────┘
       │                        │
       │ 1                      │ 1
       │                        │
       │ *                      │ *
┌──────▼───────┐         ┌──────▼───────┐
│  accounts    │ 1─────* │  actions     │
│  (视频账号)  │         │  (审计日志)  │
└──────┬───────┘         └──────────────┘
       │ *
       │
┌──────▼───────┐         ┌──────────────┐
│  devices     │         │  invoices    │
│ (注册的设备) │         │  (账单)     │
└──────────────┘         └──────────────┘

3.2 表设计关键点

-- 租户(每个客户 1 个)
CREATE TABLE tenants (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    plan TEXT NOT NULL DEFAULT 'starter',  -- starter/growth/scale/enterprise
    stripe_customer_id TEXT,
    status TEXT NOT NULL DEFAULT 'active',  -- active/trial/suspended/cancelled
    trial_ends_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 用户(每个租户内 N 个)
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    email TEXT UNIQUE NOT NULL,
    password_hash TEXT,
    role TEXT NOT NULL DEFAULT 'member',  -- owner/admin/member/viewer
    mfa_enabled BOOLEAN DEFAULT false,
    last_login_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- API Key(用于服务端调用)
CREATE TABLE api_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    name TEXT NOT NULL,
    key_hash TEXT UNIQUE NOT NULL,  -- bcrypt
    scopes TEXT[],  -- ['accounts:read', 'accounts:write', ...]
    expires_at TIMESTAMPTZ,
    last_used_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 视频账号(核心资源)
CREATE TABLE accounts (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    platform TEXT NOT NULL,
    country TEXT NOT NULL,
    identifier TEXT NOT NULL,  -- 用户名/Gmail
    status TEXT NOT NULL DEFAULT 'active',
    profile JSONB NOT NULL DEFAULT '{}',  -- 设备指纹等敏感信息
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON accounts
    USING (tenant_id = current_setting('app.tenant_id')::UUID);

-- 设备(每台真机)
CREATE TABLE devices (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    serial TEXT NOT NULL,
    profile_index INT,
    status TEXT NOT NULL,  -- available/in_use/offline
    last_heartbeat_at TIMESTAMPTZ,
    UNIQUE(tenant_id, serial)
);

-- 审计日志(合规核心)
CREATE TABLE actions (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    user_id UUID,
    api_key_id UUID,
    resource_type TEXT NOT NULL,  -- 'account' / 'device' / 'subscription'
    resource_id BIGINT,
    action TEXT NOT NULL,  -- 'create'/'update'/'delete'/'login'
    ip INET,
    user_agent TEXT,
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_actions_tenant_created ON actions(tenant_id, created_at DESC);

-- 账单(财务核心)
CREATE TABLE invoices (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    stripe_invoice_id TEXT UNIQUE,
    amount_cents INT NOT NULL,
    currency TEXT NOT NULL DEFAULT 'USD',
    status TEXT NOT NULL,  -- 'paid'/'open'/'void'
    period_start TIMESTAMPTZ,
    period_end TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

四、API Gateway 设计

4.1 端点设计

POST   /v1/auth/register           # 用户注册
POST   /v1/auth/login              # 登录,返回 JWT
POST   /v1/auth/logout
POST   /v1/auth/refresh             # refresh token

GET    /v1/me                      # 当前用户信息
PATCH  /v1/me
POST   /v1/me/mfa                  # 开启 2FA

# ===== 账号管理 =====
GET    /v1/accounts                # 列出账号
POST   /v1/accounts                # 创建账号(注册)
GET    /v1/accounts/{id}
PATCH  /v1/accounts/{id}
DELETE /v1/accounts/{id}

# ===== 设备管理 =====
GET    /v1/devices
POST   /v1/devices/register        # 注册新设备
DELETE /v1/devices/{id}

# ===== 任务管理 =====
GET    /v1/tasks                   # 任务队列状态
POST   /v1/tasks/register          # 注册任务
GET    /v1/tasks/{id}              # 任务详情 + 进度
POST   /v1/tasks/{id}/cancel       # 取消

# ===== 内容管理 =====
POST   /v1/content/generate        # AI 内容生成
POST   /v1/content/publish         # 发布任务

# ===== 计费 =====
GET    /v1/billing/subscription
GET    /v1/billing/usage
GET    /v1/billing/invoices

# ===== 监控 =====
GET    /v1/health/accounts         # 账号健康
GET    /v1/health/risk             # 风控事件

# ===== Webhook =====
POST   /v1/webhooks                # 注册回调
GET    /v1/webhooks
DELETE /v1/webhooks/{id}

4.2 中间件链

# api/middleware/chain.py
async def api_pipeline(request):
    # 1. 签名验证(API Key 模式)
    await verify_signature(request)

    # 2. JWT 验证(用户模式)
    user = await verify_jwt(request)

    # 3. 租户上下文设置
    tenant_id = user.tenant_id
    async with db.session() as db:
        await db.execute(f"SET LOCAL app.tenant_id = '{tenant_id}'")
        # 设置后该连接的所有查询自动加 RLS 过滤

        # 4. RBAC 权限检查
        await check_scope(request, user, required_scopes)

        # 5. 速率限制
        await rate_limit(tenant_id, endpoint=request.url.path)

        # 6. 审计日志
        async with audit_action(request, user):
            # 7. 业务路由
            response = await route(request)

        return response

4.3 API 鉴权三模式

模式 适用 Header
JWT 终端用户登录 Authorization: Bearer <jwt>
API Key 服务端集成 X-ExitVideo-Key: ev_live_xxx
Webhook 内部调用 X-ExitVideo-Auth: <hmac_sha256>

五、计费与配额

5.1 配额分类

维度 Starter Growth Scale Enterprise
账号数 5 25 100 自定义
设备数 1 3 不限 不限
API 调用/月 100 1,000 10,000 自定义
虚拟号采购/月 0(自带) 5 30 自定义
Webhook 事件/月 1,000 10,000 100,000 自定义
审计日志保留 7 天 30 天 365 天 永久
支持响应时间 24h 4h 1h 15min
SLA — 99% 99.5% 99.9%
价格/月 $49 $199 $799 询价

5.2 超额计费

# api/billing/overage.py
OVERAGE_RATES = {
    "extra_account": 2.50,    # 每个超额的账号
    "extra_api_call": 0.01,   # 每 1000 次超额的 API 调用
    "extra_virtual_number": 2.00,  # 每个超额虚拟号
    "extra_translation": 0.10,    # 每篇翻译
}

async def check_and_charge_overage(tenant_id: str, action: str, count: int = 1):
    """操作前检查配额 + 操作后计算超额"""
    usage = await get_current_usage(tenant_id)
    limit = await get_plan_limits(tenant.tenant.plan)

    if usage[action] + count > limit[action]:
        # 超额,自动按 Stripe Add-on 计费
        await stripe_invoice_item(
            tenant.stripe_customer_id,
            price=OVERAGE_RATES[action],
            quantity=count,
            description=f"{action} overage",
        )

5.3 Stripe 集成

# api/billing/stripe.py
class StripeBilling:
    def __init__(self, api_key: str):
        self.stripe = stripe
        self.stripe.api_key = api_key

    async def create_subscription(self, tenant: Tenant, price_id: str):
        return self.stripe.Subscription.create(
            customer=tenant.stripe_customer_id,
            items=[{"price": price_id}],
            payment_behavior="default_incomplete",
            expand=["latest_invoice.payment_intent"],
        )

    async def handle_webhook(self, payload: bytes, signature: str):
        event = self.stripe.Webhook.construct_event(payload, signature, WEBHOOK_SECRET)

        if event["type"] == "invoice.paid":
            await mark_invoice_paid(event["data"]["object"])
        elif event["type"] == "customer.subscription.deleted":
            await suspend_tenant(event["data"]["object"]["customer"])
        elif event["type"] == "customer.subscription.updated":
            await update_plan(event["data"]["object"])

    async def report_usage(self, subscription_id: str, action: str, qty: int):
        """上报用量给 Stripe 用于按需计费"""
        self.stripe.SubscriptionItem.create_usage_record(
            subscription_id,
            quantity=qty,
            timestamp=int(time.time()),
            action="increment",
        )

六、Worker 调度

6.1 任务分类

类型 例子 SLA 并发
实时任务 1 个账号注册 30s 高
短批任务 设备健康巡检 60s 中
长任务 内容生成 + 发布 5min 低
周期任务 月度结算 hours 单跑

6.2 Celery vs RQ vs 自建

Celery RQ (Redis Queue) Dramatiq
复杂度 🔴 高 🟢 低 🟡 中
多 Broker ✅ ❌ Redis only ✅
监控 🟡 Flower 需部署 🟢 rq-dashboard 🟡 自带
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐

推荐:Celery (主) + RQ (轻量辅助)

6.3 任务定义

# api/tasks/register_account.py
@celery.task(bind=True, max_retries=3, default_retry_delay=60)
def register_account(self, tenant_id: str, account_id: int):
    tenant = get_tenant(tenant_id)
    device = get_available_device(tenant_id)
    if not device:
        raise self.retry(exc=Exception("No available device"))

    try:
        adb = ADBTool(device.serial)
        agent = AutoGLMAgent()
        sms = SMSProvider()

        # 复用已有 client + audit
        with audit_action("register", tenant_id, account_id):
            registration_flow(adb, agent, sms, account_id)

        update_account_status(account_id, "active")
        notify_tenant(tenant_id, "register.success", account_id)
    except Exception as exc:
        update_account_status(account_id, "failed")
        notify_tenant(tenant_id, "register.failed", account_id, str(exc))
        raise self.retry(exc=exc)

七、关键技术决策

7.1 必须做的

决策 选项 A 选项 B 选择
HTTP 框架 FastAPI Flask FastAPI(自动 OpenAPI + 类型)
数据库 PostgreSQL MongoDB PostgreSQL(RLS 是杀手特性)
任务队列 Celery RQ Celery(更成熟)
认证 Auth0 Supabase 自建 + JWT(成本 + 控制)
支付 Stripe Paddle Stripe(API 设计更好)
对象存储 S3 MinIO S3(无所谓,自带 R2 更便宜)
消息 SMTP Postmark Postmark(送达率高)
监控 DataDog Grafana Cloud Grafana Cloud(便宜 + 自定义)
日志 ELK Loki Loki(免费 + 易上手)
错误追踪 Sentry Rollbar Sentry(Python SDK 优秀)

7.2 永远不要做的

  • ❌ 自己写 OAuth
  • ❌ 自己实现 RBAC(用现成的 casbin / spicedb)
  • ❌ 自己实现审计日志(用专用工具)
  • ❌ 自己实现支付集成(用 Stripe / PayPal / Paddle)
  • ❌ 自己写 CRM 系统(用现成 CRM + webhook)
  • ❌ 自己实现 SSO(用 WorkOS / Clerk)

八、安全基线

8.1 网络层

[Cloudflare WAF] → [API Gateway]
  - DDoS 防护
  - 速率限制(per IP + per token)
  - 国家/地区黑名单
  - WAF 规则(SQL 注入 / XSS)

[API Gateway] → [Internal Service]
  - JWT 校验
  - RBAC 校验
  - 审计日志
  - 配额校验

8.2 数据层

项 方案
静态加密 S3 KMS / PostgreSQL TDE
传输加密 TLS 1.3 only
数据库凭证 Hashicorp Vault
API Key 存储 bcrypt(cost ≥ 12)
密码存储 Argon2id
设备指纹存储 AES-256-GCM(敏感信息)
审计日志完整性 链式 hash(每行 hash 含上一行 hash)

8.3 应用层

# api/security/checklist.py
SECURITY_REQUIREMENTS = [
    "TLS-only",          # 强制 HTTPS
    "HSTS",              # 1 年
    "JWT-rotation",      # JWT 24h 过期,Refresh 30 天
    "CSRF-token",        # 所有写操作需要
    "Rate-limit-strict", # 100 req/min
    "Audit-everything",  # 全部写操作记录
    "MFA-admin",         # Admin 强制 2FA
    "API-key-scope",     # 每个 Key 限制 scope
    "IP-allowlist",      # Admin 限制 IP 段
    "Secrets-vault",     # 凭据走 Vault
    "DB-RLS",            # 数据库行级隔离
]

附录 A:MVP 启动清单

周 任务 工时
1 FastAPI + JWT 认证 + 用户 CRUD 40h
2 租户 + RLS + 数据库 schema 40h
3 accounts CRUD + 审计日志 40h
4 Stripe 订阅 + 配额计费 40h
5 Celery 任务队列集成 30h
6 监控 + 日志 + 错误追踪 20h
7-8 Landing Page + 文档站 + Onboarding 50h
9-12 灰度发布 + 5 个种子客户 80h

总计 12 周 / ~370 小时 = 一个全职 backend 工程师


附录 B:成本估算(Stage 2)

项 月成本
Cloud 服务器 (2 workers + DB + Redis) $200
Sentry + Grafana + Loki $50
Stripe 抽成(假设 MRR $10K) $300
Cloudflare Pro $20
域名 + 邮箱 + 杂项 $30
合计 ~$600

对应 MRR $10K,毛利率 94%。


维护者:MiniMax · 2026-09-27 · 配合 monetization/ api/ compliance/ 三大模块