前提:真·多租户商业化 + 桌面端仍是主产品(非 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>
9.9 KiB
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 Gateway(OAuth/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 RBAC(Phase 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 + subject(Phase 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 & Billing(Phase 2,事件优先、不急接支付)
5.1 先落用量事件
sundynix_usage_event(tenant_id, user_id, task_id, run_id, resource_type, quantity, unit, unit_price, cost, currency, created_at)。
计量点:模型 token(in/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 Gateway(Phase 4,扩展现有、不重建服务)
不拆独立服务。把现有 llm.Pool(已有 failover/熔断/缓存/热配置)扩成 model runtime:
- 每次调用 emit
usage_event(token + 成本,按 tenant)。 - 按 tenant 套餐限制可用模型 + quota;BYOK(租户自带 key)。
- Provider Adapter 接口化(当前是 openai-compatible 单形态)便于接 vLLM/Ollama/未来 Claude。 真扛不住/要独立扩缩容再拆服务。先在包边界内做清楚。
7. Tool Policy(Phase 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 是整件事的地基,也是唯一"越早越省"的。建议第一步只做最小闭环:
- 建
tenant+tenant_member表;注册时给每个新用户建一个单人默认 tenant。 contract+ 核心表加tenant_id;migration 脚本给存量数据回填一个默认 tenant。- 一个统一的 gorm scope 强制所有查询带 tenant_id + 鉴权中间件注入 tenant 上下文。
- 先只覆盖 PG(Redis/MinIO/向量/图谱的 tenant 前缀随后一层层加),跑通"注册→建库→提交任务"在 tenant 作用域下端到端。
做完这一刀,多租户地基就立住了,后面 P2–P5 都能增量长上去。
本设计对应「做什么」;对当前架构本身的评价见 ARCHITECTURE_REVIEW.md,当前架构描述见 ARCHITECTURE_DESIGN.md。