Files
sundynix-agentix/SAAS_DESIGN.md
T
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

9.9 KiB
Raw Blame History

sundynix-agentix · 多租户 SaaS 化设计(收敛版)

版本:v1 日期:2026-07-06 前提(已确认):① 目标是真·多租户商业化 ② 桌面端仍是主产品(不做 Web-first 重写) 基线:ARCHITECTURE_DESIGN.md(当前架构)+ ARCHITECTURE_REVIEW.md(评审) 立场:这是对 sundynix-agentix-saas-refactor-plan.mdGPT 方案)的收敛替代——保留其正确诊断, 砍掉与本项目阶段/定位不符的过度设计。原则:保架构、按需求拉动、每步可回退,不一次大重构。


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_idTask/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_eventtoken + 成本,按 tenant)。
  • 按 tenant 套餐限制可用模型 + quota;BYOK(租户自带 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