多租户架构
租户隔离、数据范围、默认租户与单租户模式
平台从设计之初就是多租户的:一套部署服务多个互不干扰的租户,每个租户有自己的站点、 用户和数据。
什么是租户
租户(Tenant)是数据隔离的顶层边界,一个租户对应一个组织或客户,拥有:
- 自己的营销站点(页面、文档、主题)
- 自己的用户与角色权限
- 自己的站点会员
- 自己的业务数据(按
tenant_id隔离)
在租户侧与公开面,界面文案不出现「租户」「Tenant」字样——对终端用户来说,它就 是「站点」或「组织」。这不是措辞洁癖:同一套代码要能作为单客户私有部署交付,那时 「租户」是一个客户根本不该看见的内部概念。
数据隔离
所有业务表都带 tenant_id 列,查询统一走 withTenantScope(tenant_id) 注入租户范围:
const records = await prisma.siteDoc.findMany({
where: withTenantScope(tenant_id, { status: "published" }),
});
隔离靠统一的查询入口保证,而不是靠每个调用点自觉。绕过它直接写 where 会被
模块契约校验挡下来。
默认租户
slug 为 rewindom 的租户是产品主域隐式绑定的组织。访问 FRONTEND_URL(本地
localhost)看到的就是它的站点。
它的站点与文档库在服务端启动时自动初始化,幂等且按语言分别判断:某种语言已经有 已发布文档就跳过,不覆盖后续编辑。所以升级部署不会把你改过的首页冲掉,而新增一门 语言的初始内容会在下次启动时自动补上。
租户的创建与管理
新租户在平台控制台(PLATFORM_URL,本地 127.0.0.1)创建。新建出来的租户是
active 的;创建时会自动铺好默认页头 / 页脚与主题(initializeTenantSite)。
首页与会员页的版式不预建:站点未必要用它们,预建出来就是几张删不掉的空版式。 访客访问时仍由内置版式兜底渲染,站点管理员在「官网 → 页面」下方的版式区点「初始化 版式」,那张页面才成为可编辑的记录;开通某项功能(如会员)时它名下的版式会自动建好。
单租户模式
面向私有部署,设 SINGLE_TENANT=true:
- 隐藏平台控制台与租户管理入口
- 禁止新建租户;自助注册与 OAuth 首次登录一律并入默认租户
- 界面上不暴露多租户概念
单租户与多租户共用同一套代码,区别只是运行时配置——不需要为私有部署维护单独的 分支。
路由由 Host 决定
FRONTEND_URL→ 默认租户{slug}.{TENANT_BASE_DOMAIN}→ 对应 slug 的租户- 租户自定义域名 → 绑定的那个租户
详见 Host 分流机制。