构建产品

组织、团队与租户隔离

决定客户如何协作,并调整默认的组织角色、邀请、席位上限与租户边界。

已根据 starter 提交 2a1a04a 验证。

先回答产品问题,再回答代码问题:你的 SaaS 是卖给个人、共享工作区,还是需要一人管理多个工作区?Sushi SaaS 默认采用“可成长的单人团队”模型。每位客户从个人组织开始,所有可计费资源都属于该组织。

默认协作模型

行为默认值以后可选的扩展
资源所有权组织内共享通过 can() 增加创建者限制或逐资源共享
角色owneradminmember增加产品特有角色或 Better Auth 动态权限
计费每个组织固定价格增加席位数量与经过测试的 prorate 策略
导航使用 ?org=<slug> 的工作区切换器如果 URL 需要更强层级,再增加带组织的路径段
团队组织成员与邀请只有产品确实需要子组时才启用嵌套团队

每个账号都有组织

注册会创建个人组织及 owner 成员关系。系统没有另一套“用户拥有资源”的路径:文件、任务、订单、订阅、积分和预约都使用 org_uuid

Better Auth 提供成员表与邀请机制;Sushi 补充租户范围、应用权限、席位限制、共享余额和并发保护。

角色与能力

角色层级为 owneradminmember

操作MemberAdminOwner
读取组织数据、创建/删除文件、消费积分
邀请、移除成员、修改角色
更新组织
管理计费或删除组织

使用 getOrgContext(request, optionalSlug) 获取用户、组织 UUID 和角色,再用 can(ctx, action, resource) 进行授权。绝不从请求 body 接受 org_uuid

租户隔离

只有 model 查询数据库。所有租户表查询都使用 scopedToOrg(column, ctx.orgUuid),插入必须写入组织 UUID。架构测试会扫描这些规则。

应用使用 organizations.uuid;Better Auth 的 organizations.id 仅供其内部关系使用。带品牌的 OrgUuid 类型防止误传用户 UUID。

邀请与席位上限

邀请 72 小时后过期,只能由被邀请邮箱接受;接受与拒绝使用独立端点。重新邀请会替换旧的待处理邀请。

方案总席位
Free1
Plus5
Max20

发送和接受时都会检查“已加入成员 + 有效待处理邀请”。两个检查共享组织级 PostgreSQL advisory lock,避免两个并发请求同时占用最后一个席位。管理员后台可设置带审计记录的临时或永久例外。

安全不变量

  • 组织不能失去最后一个 owner。
  • 用户不能离开自己的唯一组织。
  • 成员不能授予高于自己的角色。
  • 降级不会移除现有成员,只会阻止新的邀请与接受。
  • 积分和 Stripe Customer 属于组织,而不是点击结账的成员。

用户界面位于 /{locale}/account/team,API 位于 /api/account/team/*。组织支持操作位于独立管理应用中。

starter 已提供基于查询参数的工作区切换器,但暂未提供组织 slug 路由、自定义角色、嵌套团队或 Stripe 按席位数量计费;这些是需要产品策略的扩展。

修改该模型前,请写清楚客户数据归谁所有、谁能邀请或移除成员、普通成员能否删除共享内容以及由谁付款。当两个组织无法读取彼此的数据、最后一个 owner 无法被移除、并发邀请无法超过最后席位,且降级会保留现有数据和成员时,协作模型才算验证完成。

相关内容:套餐与权益设置席位上限,并通过 Sushi SaaS 的代码结构判断组织优先的数据归属是否适合你的产品。

组织、团队与租户隔离 · Sushi SaaS