# 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 是整件事的地基,也是唯一"越早越省"的。建议第一步只做最小闭环:** 1. 建 `tenant` + `tenant_member` 表;注册时给每个新用户建一个单人默认 tenant。 2. `contract` + 核心表加 `tenant_id`;migration 脚本给存量数据回填一个默认 tenant。 3. 一个统一的 gorm scope 强制所有查询带 tenant_id + 鉴权中间件注入 tenant 上下文。 4. 先只覆盖 PG(Redis/MinIO/向量/图谱的 tenant 前缀随后一层层加),跑通"注册→建库→提交任务"在 tenant 作用域下端到端。 做完这一刀,多租户地基就立住了,后面 P2–P5 都能增量长上去。 --- *本设计对应「做什么」;对当前架构本身的评价见 `ARCHITECTURE_REVIEW.md`,当前架构描述见 `ARCHITECTURE_DESIGN.md`。*