阶段 5-A:计费与用量设计
auth 多租户 IDaaS 商业化第一块——按租户用量计费、收钱、配额执行。 目标:能知道每个租户用了多少、能按套餐收费、超额拦截。 本文档为可追溯设计依据,定稿于 2026-07-10。整体路线见
docs/TENANT_PHASE1.md。
0. 现有可复用基础
tenants表已有max_users(配额字段雏形)。audit_logs已记录大部分事件(登录/验证/CRUD),可作用量数据源。enforce_user_quota/enforce_tenant_usable(tenant.rs)是配额校验的现成模式,可泛化。- RBAC + 中间件 + RequestMeta.tenant_id 已就绪,埋点能拿到 tenant 上下文。
1. 用量采集
原则:埋点不阻塞主链路(fire-and-forget 到内存 channel,后台批写)。
采集指标
| metric | 含义 | 采集点 |
|---|---|---|
mau | 月活用户(按 user 去重) | login / OAuth grant 成功 |
api_calls | 受保护 API 调用数 | auth_middleware(每次带 token 请求) |
tokens_issued | token 签发数 | login / refresh / OAuth grant |
oauth_logins | OAuth 授权次数 | /oauth/token 各 grant |
存储:Redis 实时计数 + 定时落库
- Redis:
HINCRBY usage:{tenant_id}:{yyyymmdd} {metric} 1(hash,每天一行)。 - MAU 去重:
SADD usage:mau:{tenant_id}:{yyyymm} {user_id}(set,月末 SCARD)。 - 定时任务(每小时):Redis →
usage_daily表(持久化 + 清 Redis 旧 key)。
usage_daily 表
CREATE TABLE usage_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id VARCHAR(50) NOT NULL,
date CHAR(8) NOT NULL, -- yyyymmdd
api_calls INTEGER DEFAULT 0,
tokens_issued INTEGER DEFAULT 0,
oauth_logins INTEGER DEFAULT 0,
mau INTEGER DEFAULT 0, -- 仅当天去重用户数(月聚合另算)
created_at DATETIME DEFAULT (datetime('now','+8 hours')),
UNIQUE(tenant_id, date)
);
CREATE INDEX idx_usage_tenant_date ON usage_daily(tenant_id, date);
2. 套餐与订阅
CREATE TABLE plans (
id VARCHAR(50) PRIMARY KEY, -- free / pro / enterprise
name VARCHAR(100) NOT NULL,
price_monthly INTEGER NOT NULL, -- 单位:分
max_users INTEGER NOT NULL, -- 0 = 不限
max_api_calls_monthly INTEGER NOT NULL,
max_oauth_clients INTEGER NOT NULL,
features TEXT DEFAULT '[]', -- JSON: ["sso","branding","audit_export",...]
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE subscriptions (
tenant_id VARCHAR(50) PRIMARY KEY REFERENCES tenants(id),
plan_id VARCHAR(50) NOT NULL REFERENCES plans(id),
status VARCHAR(20) NOT NULL, -- trialing / active / past_due / canceled
current_period_end DATETIME, -- 当前周期结束(到期续费/降级)
stripe_customer_id VARCHAR(100), -- 支付网关客户 ID
stripe_subscription_id VARCHAR(100),
created_at DATETIME DEFAULT (datetime('now','+8 hours')),
updated_at DATETIME DEFAULT (datetime('now','+8 hours'))
);
plans 预置(db.rs seed):free(0 元,限 10 用户/1k 调用)、pro(99 元/月,1k 用户/100k 调用)、enterprise(999 元/月,不限 + SSO + 品牌)。
3. 配额执行(泛化 enforce_user_quota)
把 tenant.rs 的 enforce_user_quota 泛化:
#![allow(unused)]
fn main() {
async fn enforce_quota(db: &Database, tenant_id: &str, metric: QuotaMetric) -> Result<()>
// QuotaMetric: Users / OauthClients / ApiCalls(monthly) / Storage
}
create_user→ enforce_quota(Users)(已有,重构)。create_oauth_client→ enforce_quota(OauthClients)。- API 调用(auth_middleware)→ 月度 api_calls 计数(Redis),超额返 429(限流,不拒服务)或降级提示。
4. 计费账单 + 支付
推荐:Stripe Billing(国际,最省事)
- 创建 Stripe Customer(租户首次订阅)→ Subscription(按 plan)→ Invoice(周期账单)。
- Webhook
invoice.paid/invoice.payment_failed同步subscriptions.status(active/past_due)。 - 前端用 Stripe Checkout / Customer Portal(自助管理订阅/换卡),不用自建支付表单。
- 国内客户:Stripe 支持支付宝(需 Stripe 账户开通),或单独对接支付宝微信(自建,工作量大)。
欠费处理
past_due(支付失败):宽限期(3-7 天)→ 仍失败降级(功能只读/隐藏 SSO)→ 不删数据。canceled:数据保留 N 天(可恢复)→ 到期删除(合规)。
自建账单(备选,国内为主)
invoices表(金额/周期/状态)+ 支付宝微信支付接口 + 手动对账。工作量大,不推荐除非纯国内。
5. API
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/tenant/usage | 租户管理员 | 本租户用量(当月 MAU/调用/token + 趋势) |
| GET | /api/tenant/subscription | 租户管理员 | 当前套餐/周期/账单历史 |
| POST | /api/tenant/subscribe | 租户管理员 | 选套餐 → 返 Stripe Checkout session URL |
| GET | /api/admin/tenants/:id/usage | 平台 admin | 指定租户用量 |
| GET | /api/admin/billing/overview | 平台 admin | 全平台收入/用量总览 |
| POST | /api/webhooks/stripe | 公开(签名校验) | Stripe 事件回调(订阅状态同步) |
/api/tenant/* 是租户管理员域(持 system:* 本租户权限 + 不持 admin:manage),跟 /api/admin/*(平台 admin)区分。
6. 前端
- 租户管理页加「用量」tab:MAU/调用/token 图表(echarts 或简易柱状)。
- 租户管理员(需建 role_type=custom 的租户 admin 角色):
- 「订阅与账单」页:当前套餐 + 用量进度条 + 账单历史 + 升级按钮(跳 Stripe Portal)。
- 平台 admin「计费总览」页:各租户收入/用量/到期提醒。
7. 计价模型(待你定,影响 plans 配置)
| 模型 | 适合 | 复杂度 |
|---|---|---|
| 固定套餐(free/pro/enterprise 月费) | 用户量可控、功能分层 | 低(plans 表即可) |
| 按 MAU(每活跃用户月费) | To SaaS 开发者、用量波动大 | 中(MAU 精确去重 + 按量结算) |
| 按调用(每万次 API) | API 优先、机器调用多 | 中(调用计数 + 按量) |
| 混合(套餐 + 超额按量) | 兼顾稳定 + 弹性 | 中高 |
建议起步:固定套餐(最简,3 档),跑通后再加超额按量(pro 套餐含 X 调用,超出按量)。
8. 成本(1 人估)
| 子项 | 工作量 |
|---|---|
| 用量采集(埋点 + Redis + usage_daily + 定时任务) | ~1 周 |
| plans/subscriptions 表 + 配额泛化 | ~0.5 周 |
| Stripe Billing 集成(Customer/Subscription/Webhook) | ~1.5 周 |
| API(usage/subscription/webhook) | ~0.5 周 |
| 前端(用量图表 + 订阅页 + 总览) | ~1 周 |
| 合计 | ~4-4.5 周 |
难点:Stripe Billing 状态机(trialing/active/past_due/canceled + webhook 幂等);用量采集性能(异步队列不阻塞主链路)。
9. 增量落地(每步可独立上线)
- 用量采集 + usage_daily(先看清谁用了多少,不收费)—— 1 周。先做这步,决策有数据支撑。
- plans/subscriptions + 配额泛化(能限超额,免费 + 限额)—— 0.5 周。
- Stripe Billing(真能收钱)—— 1.5 周。
- 前端用量/账单页(自助体验)—— 1 周。
10. 不在本阶段做(边界)
- ❌ SSO 企业版(阶段 5-B 另设计:Inbound OIDC / SAML)。
- ❌ 合规认证(SOC 2 / ISO 27001,流程非代码,数月 + 第三方)。
- ❌ 计价模型动态配置(起步用 plans 表硬编码三档即可)。
- ❌ 退款/税务/多币种(Stripe 默认能力,不自建)。