Files
Blizzard 2f19e322e3 docs: 多租户 SaaS 化设计(收敛版 SAAS_DESIGN.md)
前提:真·多租户商业化 + 桌面端仍是主产品(非 Web-first)。
对 GPT 重构方案的收敛替代:保留正确诊断(tenant_id前置/usage先落事件/subject规范/migration),
砍掉过度设计(Web重写/7服务拆分/Temporal/Remote MCP/独立Tool Policy)。
核心:保架构、穿一条多租户+计费脊柱、按需求拉动分阶段(P1地基tenant_id→P2用量→P3客户端租户化→P4enforcement→P5支付+硬化)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 16:47:11 +08:00

157 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sundynix-agentix · 多租户 SaaS 化设计(收敛版)
> 版本:v1 日期:2026-07-06
> 前提(已确认):**① 目标是真·多租户商业化 ② 桌面端仍是主产品**(不做 Web-first 重写)
> 基线:`ARCHITECTURE_DESIGN.md`(当前架构)+ `ARCHITECTURE_REVIEW.md`(评审)
> 立场:这是对 `sundynix-agentix-saas-refactor-plan.md`GPT 方案)的**收敛替代**——保留其正确诊断,
> 砍掉与本项目阶段/定位不符的过度设计。原则:**保架构、按需求拉动、每步可回退,不一次大重构。**
---
## 0. 一句话设计
**不改架构骨架,只往里穿一条"多租户 + 计费"的脊柱。** 桌面端保持主产品、变租户感知;加一个很薄的
Web 面做注册/计费/团队(桌面端不适合放这些)。后端按"地基→用量→enforcement→支付"分阶段长出 SaaS 能力,
**每一块都由验证过的需求拉动,而不是提前把 7 个服务的底座全建出来。**
---
## 1. 与 GPT 方案的取舍(明确保留 / 砍掉)
| GPT 方案主张 | 我的判断 | 理由 |
|---|---|---|
| **tenant_id 全链路前置** | ✅ **采纳,最高优先** | 现在便宜、以后要命;多租户地基 |
| **Usage Meter 先落事件(不接支付)** | ✅ **采纳** | 事件溯源式用量→再计费,顺序对;现 billing 太薄 |
| **NATS subject 规范化 + Envelope(带 tenant_id)** | ✅ **采纳(轻量)** | 做多租户本就要给消息带 tenant,顺手;但**不拆多根总线** |
| **AutoMigrate → migration 工具** | ✅ **采纳** | 生产必须、可控变更 |
| Web App 主产品化(重写前端) | ❌ **砍掉** | **你定了桌面端为主**;改为桌面端租户感知 + 薄 Web 面 |
| 拆 7 个物理服务(task-runtime/model-gateway/tool-policy 独立服务) | ⚠️ **降级为"包/边界",不拆服务** | 单团队扛不动 7 服务运维;先做成 dispatcher 内的清晰模块,真扛不住再拆 |
| Task Runtime 独立层 + Temporal | ❌ **砍掉(现阶段)** | 现有 FSM+JetStream+checkpoint 够用;加 cancel/retry 收口即可,别上 Temporal |
| Remote MCP GatewayOAuth/registry 整套) | ❌ **砍掉(现阶段)** | 远程 MCP 市场是远期产品赌注,无近期需求不建 |
| Tool Policy 强制拦每次工具调用 | ⚠️ **降级** | 内部工具先只做 tenant 作用域 + 用量计数;策略引擎等有不可信工具再上 |
**核心分歧**:GPT 把它当"推翻重来做成标准 SaaS 平台";我把它当"给现有平台穿一条多租户脊柱"。
---
## 2. 多租户数据模型(Phase 1 地基)
### 2.1 层级
```
Tenant(组织/账户,计费单位)
└── Workspace(工作区)
└── Project(项目)
└── Task / KB / Agent / ...
User ── (via TenantMember, 带 role) ── Tenant
```
桌面端主产品下,多数个人用户 = 一个单人 Tenant(免费档);团队/企业 = 多成员 Tenant。
### 2.2 新增表(雪花 id + BaseModel,沿用 `sundynix_` 前缀)
- `sundynix_tenant`(id, name, slug, plan, status)
- `sundynix_tenant_member`(tenant_id, user_id, role, status) — 唯一(tenant_id,user_id)
- `sundynix_workspace`(id, tenant_id, name, created_by)
- `sundynix_project`(id, tenant_id, workspace_id, name)
### 2.3 tenant_id 全链路(这是重点,也是最大工作量)
现有 owner_id 隔离 → 升级为 tenant 作用域。**所有核心表加 `tenant_id`**Task/Eval/KB/Doc/DocLink/Agent/Prompt/AuditLog/GuardrailEvent…)+ 索引。隔离规则:
| 层 | 现状 | 加 tenant |
|---|---|---|
| PostgreSQL | owner 过滤 | 全表加 tenant_id**所有查询默认带 tenant_id**(用 gorm scope 统一强制,防漏) |
| Redis key | 无前缀 | `sundynix:{tenant_id}:...`(限流/流/会话) |
| NATS envelope | 裸 payload | 统一 Envelope 带 tenant_id(见 §3 |
| MinIO path | 平铺 | `tenants/{tenant_id}/workspaces/{ws}/projects/{proj}/...` |
| Milvus | kb 字段 | collection 不变,加 tenant 到 metadata filter(或 partition |
| Neo4j | kb 属性 | 节点/关系加 `tenant_id` 属性 |
> ⚠️ 最易漏的是"查询默认带 tenant_id"。用一个**统一的 gorm scope + code review 清单**强制,别靠人肉记。
### 2.4 RBACPhase 1 同期)
角色:`owner / admin / member / viewer / billing_admin`。权限动作(示例):
`tenant.manage / billing.manage / workspace.create / project.create / task.run / task.approve / model.manage / tool.use / kb.write / audit.read`
实现:中间件从 JWT 取 user → 查 TenantMember 得 role → 按 role→permission 表判权。
`RequireAdmin` 升级为按 `tenant.manage` 权限判定(替代现在的开发期放行 + env 白名单)。
---
## 3. NATS Envelope + subjectPhase 1,轻量)
**不拆总线**(保持单 NATS,HA 是 T3)。只做两件卫生:
- 统一 `Envelope{id,type,version,tenant_id,workspace_id,user_id,task_id,trace_id,created_at,payload}` 包裹消息。
- subject 按语义规范前缀(现已半规范):`cmd.* / events.* / streams.* / tools.* / control.* / health.*`
命令/事件区分先做最小:任务提交=command,状态变更落 `task_events`(见 §4)。
---
## 4. 任务运行收口(Phase 1,轻量——不做独立服务)
不建 Task Runtime 服务、不上 Temporal。现有 FSM + JetStream + checkpoint 上补:
-`task_events`(tenant_id, task_id, run_id, event_type, payload, trace_id) 事件日志(可回放 + 计费溯源)。
-**cancel / retry**(现有 resume/超时已在)——在 dispatcher 消费侧加取消信号 + 幂等重试。
- 这些是 dispatcher 内的能力增强,不新增服务。
---
## 5. Usage & BillingPhase 2,事件优先、不急接支付)
### 5.1 先落用量事件
`sundynix_usage_event`(tenant_id, user_id, task_id, run_id, resource_type, quantity, unit, unit_price, cost, currency, created_at)。
计量点:**模型 tokenin/out/cached)· 工具调用次数 · 沙箱时长 · 解析页数 · 入库量 · 存储 · 任务时长**。
### 5.2 聚合
`sundynix_usage_daily_rollup`(tenant_id, date, resource_type, quantity, cost) —— 按天汇总,admin 展示成本。
### 5.3 分阶段
`①仅记 usage_event → ②按天 rollup + 后台成本可视 → ③套餐 quota + 超额限制 → ④接支付 → ⑤企业账单`
**现在只做 ①②**(把已有的 Redis token 计数升级为落库的 usage_event)。
---
## 6. Model GatewayPhase 4,扩展现有、不重建服务)
不拆独立服务。把现有 `llm.Pool`(已有 failover/熔断/缓存/热配置)**扩成 model runtime**
- 每次调用 emit `usage_event`token + 成本,按 tenant)。
- 按 tenant 套餐限制可用模型 + quotaBYOK(租户自带 key)。
- Provider Adapter 接口化(当前是 openai-compatible 单形态)便于接 vLLM/Ollama/未来 Claude。
真扛不住/要独立扩缩容再拆服务。**先在包边界内做清楚。**
---
## 7. Tool PolicyPhase 4,降级版)
不建独立 Policy 服务、不强制拦内部工具。分阶段:
- **现在**:工具调用带 tenant 作用域 + 计 usage_event(复用 §5)。
- **有不可信工具时**(远程 MCP / shell / 本地连接器):再上 allow/deny/approval/quota 的策略前置 + `tool_call_event` 审计。
---
## 8. 客户端(Phase 3,桌面端为主 + 薄 Web 面)
**不重写 Web App。**
- **桌面端(主产品)**:变**租户感知**——登录后带 tenant 上下文;所有数据 tenant 作用域;工作区/项目切换;团队成员看到共享资源。原生能力(文件/另存为)不变。
- **管理端(现有 React)**:长出**租户/成员/套餐/用量账单/审计**管理(运维 + 商业后台合一)。
- **新增薄 Web 面**(只做桌面端不适合的):注册/登录/组织创建、**订阅计费、团队邀请**——因为这些要在装桌面端**之前**就能用,且计费/组织管理放桌面端别扭。技术上可以是管理端的一部分,或一个极简独立页,**不是完整产品重写**。
---
## 9. 分阶段落地(我的收敛排序 vs GPT 的全家桶)
| 阶段 | 内容 | 为什么这个顺序 |
|---|---|---|
| **P1 地基**(先做,最关键) | tenant/workspace/project 表 + **tenant_id 全链路** + RBAC + 各存储隔离 + migration 工具 | 没这个,多租户什么都无从谈;schema 越小越便宜 |
| **P2 用量** | usage_event + daily rollup(模型/工具/存储计量),admin 成本可视 | 计费前必须先有可信用量;低风险 |
| **P3 客户端租户化** | 桌面端 tenant 上下文 + 管理端长出租户/账单页 + 薄 Web 注册/计费面 | 有了后端隔离才谈得上前端多租户;不重写 |
| **P4 enforcement** | 套餐 quota + model runtime 成本/quota/BYOK + 工具 tenant 作用域 | 有用量数据后才谈限额/套餐 |
| **P5 支付 + 硬化** | Stripe 接入 + NATS 集群 + DB HA + TLS= T3) | 有真实付费流量再付分布式硬化的税 |
**明确不做(现阶段)**:Web 前端重写、7 服务物理拆分、Temporal、Remote MCP Gateway、独立 Tool Policy 服务。
这些等"验证过的需求"拉动再上——不提前建地基。
---
## 10. 现在就能起手的第一刀
**P1 的 tenant_id 是整件事的地基,也是唯一"越早越省"的。建议第一步只做最小闭环:**
1.`tenant` + `tenant_member` 表;注册时给每个新用户建一个单人默认 tenant。
2. `contract` + 核心表加 `tenant_id`;migration 脚本给存量数据回填一个默认 tenant。
3. 一个统一的 gorm scope 强制所有查询带 tenant_id + 鉴权中间件注入 tenant 上下文。
4. 先只覆盖 PGRedis/MinIO/向量/图谱的 tenant 前缀随后一层层加),跑通"注册→建库→提交任务"在 tenant 作用域下端到端。
做完这一刀,多租户地基就立住了,后面 P2–P5 都能增量长上去。
---
*本设计对应「做什么」;对当前架构本身的评价见 `ARCHITECTURE_REVIEW.md`,当前架构描述见 `ARCHITECTURE_DESIGN.md`。*