mcpField
P
per-tenant-database

per-tenant-database

github.com/rivet-dev数据与数据库441.1k 次安装1 天前更新
代码仓库

关于 per-tenant-database

RivetKit 的按租户数据库架构示例,为构建多租户应用的开发者提供参考。它以每个租户对应一个 Rivet Actor 的方式隔离数据,并演示 JSON 状态与实时事件。

主要功能

  1. 01每个租户独立 Actor
  2. 02租户级数据隔离
  3. 03JSON 状态存储
  4. 04写入后广播实时事件

适用场景

  1. 01为不同公司保存员工与项目数据
  2. 02向同一租户的客户端推送数据变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rivet-dev/skills --skill per-tenant-database

安装后在 Claude Code 中输入/per-tenant-database确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

BASE_SKILL.md
index.json
openapi.json
reference/actors/
access-control.md
actions.md
actor-runtime-socket.md
appearance.md
authentication.md
communicating-between-actors.md
connections.md
debugging.md
design-patterns.md
destroy.md
errors.md
events.md
fetch-and-websocket-handler.md
helper-types.md
http-api.md
input.md
inspector-tabs.md
keys.md
kv.md
lifecycle.md
limits.md
metadata.md
queues.md
reference/actors/quickstart/
backend.md
cloudflare.md
effect.md
next-js.md

详细介绍

每个租户使用独立数据库

重要:开始任何操作前,你必须阅读此技能目录中的 BASE_SKILL.md。其中包含有关调试、错误处理、状态管理、部署和项目设置的重要指导。所有 RivetKit 工作都适用这些规则和模式。以下内容均假设你已阅读并理解这些内容。

可运行示例

如需参考实现,请阅读以下模板中的原始可运行示例代码:

RivetKit 中每个租户使用独立数据库的架构模式。每个租户不再共用一个带有 tenant_id 列的数据库表,而是拥有自己的 Rivet Actor,由该 Actor 管理该租户的全部数据集。

入门代码

从 GitHub 上的可运行示例开始,并据此进行调整。此示例将每个租户的数据集存储在 JSON Actor 状态中,并提供带有实时事件更新的 React 仪表板。

主题摘要
隔离每个租户对应一个 companyDatabase Actor,以公司名称为键。切换租户时会切换整个数据集。
状态JSON Actor 状态中包含 employees 和 projects 数组及时间戳。不使用 SQLite、队列或调度。
实时更新每个写入操作都会修改状态,然后向该租户的所有已连接客户端广播类型化事件(employeeAdded、projectAdded)。
身份验证无。登录界面仅用于展示。生产环境的指导见安全检查清单。

隔离模型

Actor 键就是租户 ID。客户端通过 useActor({ name: "companyDatabase", key: [companyName] }) 连接,Actor 在 createState 中读取 c.key[0],以初始化该租户的数据集。这带来以下优势:

  • 每个租户一个 Actor:companyDatabase[tenantId] 精确对应一个 Actor 实例。两个租户绝不会共用同一个 Actor。
  • 每个租户一个数据集:所有读写都通过该 Actor 的状态进行,因此不存在共用且需要正确筛选 tenant_id 列的表。跨租户数据泄漏需要构造错误的键,而不是忘记添加 WHERE 子句。
  • 不会发生键注入:键是数组,而非插入字符串的内容。key: [tenantId] 不会像字符串拼接 "tenant:" + tenantId 那样受到转义问题的影响。请参阅键。

此示例的测试(tests/per-tenant-database.test.ts)验证了隔离效果:写入 companyDatabase["Alpha Co"] 的数据绝不会出现在 companyDatabase["Beta Co"] 中。

选择状态后端

此示例使用纯 JSON Actor 状态。键等于租户的模型适用于任何 Actor 状态后端。

后端适用场景文档可运行代码
JSON Actor 状态数据集较小、读取简单、整个数据集可轻松放入内存。示例使用此方案。状态GitHub
Actor SQLite(rivetkit/db)需要表、索引、SQL 查询、超出内存容量的数据或每租户关系型架构。SQLiteGitHub
SQLite + Drizzle在 Actor SQLite 基础上使用类型化架构、查询构建器和生成的迁移文件。SQLite + DrizzleGitHub

使用这两种 SQLite 方案中的任意一种时,每个租户都会拥有自己的嵌入式 SQLite 数据库,因为数据库的作用域限定在 Actor 内,而 Actor 的作用域限定在租户内。

迁移

按租户划分的示例没有迁移,因为 JSON 状态没有架构。采用 SQLite 后,迁移会在每个租户的数据库中运行:

  • 原始 SQL:db({ onMigrate }) 会在 Actor 开始处理流量前,在 SQLite 保存点内运行你的迁移 SQL。如果 onMigrate 抛出错误,所有迁移 SQL 都会原子性回滚,Actor 也不会启动。请参阅 SQLite。
  • Drizzle:drizzle-kit 根据你的类型化架构生成迁移文件,db({ schema, migrations }) 会在 Actor 唤醒时应用这些迁移。请参阅 SQLite + Drizzle。

由于每个租户都有自己的数据库,迁移会随着各租户的 Actor 唤醒而逐个推出,而不是针对共享数据库一次性执行大型迁移。

租户 ID 必须来自身份验证

示例中的登录只是表面流程:客户端可以选择任意公司字符串,该字符串会成为 Actor 键,因此任何访客都可以读取和写入任何租户的数据。请勿将其用于正式环境。以下是正式环境必须实现的扩展(示例中未实现):

  • 从已验证的凭据(例如 JWT 声明)中获取租户 ID,绝不能使用用户输入。
  • 在 onBeforeConnect(判断通过或失败)或 createConnState(将已验证的用户存入连接状态)中,依据 c.key 验证凭据。请参阅身份验证和连接。
  • 在连接级身份验证之外,为每个操作添加权限检查。请参阅访问控制。

Actors

  • 键:companyDatabase[companyName](单元素数组键;c.key[0] 是公司名称)
  • 职责:每个租户一个 Actor。将该公司的员工和项目保存在持久化状态中,通过操作提供读写服务,并向已连接的客户端广播变更。
  • 操作
    • addEmployee
    • listEmployees
    • addProject
    • listProjects
    • getStats
  • 队列
    • 无
  • 事件
    • employeeAdded
    • projectAdded
  • 状态
    • JSON
    • company_name
    • employees
    • projects
    • created_at
    • updated_at

每个写入操作都遵循相同的“先修改,再广播”模式:将记录推入 c.state,更新 updated_at,广播类型化事件,然后返回记录。请参阅操作和事件。

生命周期

sequenceDiagram
	participant A as Tenant A client
	participant DA as companyDatabase A
	participant B as Tenant B client
	participant DB as companyDatabase B

	Note over A: authenticate and derive tenant id
	A->>DA: connect with key [tenantA]
	Note over DA: createState seeds company_name, employees, projects
	A->>DA: listEmployees() + listProjects() + getStats()
	A->>DA: addEmployee(name, role)
	DA-->>A: employeeAdded event
	B->>DB: connect with key [tenantB]
	Note over DB: separate actor, separate dataset
	B->>DB: listEmployees()
	DB-->>B: tenant B data only

在示例中,“身份验证”步骤是一个自由文本公司选择器。其余流程与图示一致:createState 会在首次创建时初始化数据集,仪表板会调用 listEmployees、listProjects 和 getStats 加载数据,同一租户的所有已连接客户端都会收到 employeeAdded 和 projectAdded 事件。

安全检查清单

示例中没有实现以下任何措施。正式部署前请全部落实。

  • 租户身份:从已验证的 JWT 声明中获取租户 ID,绝不能使用客户端提供的字符串。
  • 连接验证:在 onBeforeConnect 或 createConnState 中,验证凭据中的租户声明是否与 c.key 匹配,并拒绝不匹配的连接。
  • 逐操作授权:在执行变更操作(addEmployee、addProject)前检查调用者的角色,而不只是在连接时检查。请参阅访问控制。
  • 输入验证:限制名称和角色的长度,并验证枚举值。示例只会清理输入两端的空白,并使用备用默认值。
  • 键构造:始终将租户 ID 作为数组元素传入(key: [tenantId])。绝不要将租户 ID 插入键字符串,也不要使用一个租户的输入构造键来访问另一个租户的 Actor。
  • 增长限制:建议扩展此示例,对 employees 和 projects 数组设置上限或分页。示例中的 JSON 状态允许数组无限增长;当数据集超出内存容量时,请迁移到 SQLite。

参考索引

Actors
命令行工具
客户端
教程集
部署
通用
自托管

评论

支持 Markdown