编写租户感知的代码
字数
894 字
预计阅读
4 分钟
后台怎么用
这一页讲写代码。多租户是什么、怎样打开、租户和套餐怎样管理,见多租户。
租户是使用这套系统的一家公司。所有租户共用一个数据库,每一行数据用 tenant_id 列记下它属于哪个租户。多租户开关关闭时,所有请求都在默认租户里运行,所以代码总是按多租户来写,开关怎么设都不用改代码。
五条规则
- 新表默认是租户表:迁移里加
tenant_id bigint unsigned NOT NULL列,唯一键以它开头,比如UNIQUE (tenant_id, order_no, alive),这样不同租户可以有相同的值;没有唯一键的表加一个KEY idx_<表名>_tenant (tenant_id)。实体用@TenantColumn()映射这一列(表里有这一列时,代码生成器生成的实体会自动带上); - ORM 自动加租户条件:通过 ORM(实体、Repository、QueryBuilder)读、写、计数、子查询和联表时,自动加上当前租户的条件,插入时自动写入当前租户。超级管理员和
@SkipDataScope()也跳不过; - 手写 SQL 自己写条件:原始 SQL 不经过 ORM,每张租户表(包括联表进来的)都要带
<别名>.tenant_id = ?,插入时写tenant_id。pnpm verify会把漏写的找出来; - 跨租户只走两个函数:
withTenant(租户 id, fn)在指定的租户里运行,allTenants(fn)不带租户条件运行。每处调用都在旁边写注释qw:cross-tenant <理由>,确实要跨租户的手写 SQL 也一样; - 平台表和全局唯一键集中登记:所有租户共用、由平台维护的表(比如字典、参数),登记进
apps/server/src/core/db/tenant-tables.ts的PLATFORM_TABLES;必须在所有租户里都唯一的键,登记进同一个文件的GLOBAL_UNIQUE。平台表做成的页面,还要把它的路由名加进packages/shared/src/common/tenancy.ts的PLATFORM_ONLY_ROUTES,否则租户可能拿到一个打开就报 403 的页面。
忘了加 tenant_id 会怎样
没有 tenant_id 的项目表会被所有租户共享:任何租户都能读写它的全部行。代码生成器导入这样的表时只给提示,不会拦下。
例子:手写 SQL
用 tenantId() 取当前请求的租户,每张租户表各写一个条件(改写自请假模块):
ts
import { tenantId } from '../../../core/context/tenant.js'
const tid = tenantId()
const rows = await this.txHost.tx.query(
`SELECT t.id FROM wf_task t
JOIN wf_instance i ON i.id = t.instance_id AND i.tenant_id = ?
WHERE t.instance_id = ? AND t.tenant_id = ?`,
[tid, instanceId, tid],
)更多
Redis 键、按租户运行的定时任务、实时推送、不需要登录的接口、每个租户都要的初始数据和跨租户测试,也都有对应的写法,见仓库的文档:
- 多租户 · 编写租户感知的项目代码:完整规则和检查;
- 设计说明 · 多租户:为什么这样设计,以及已知的上限;
- 代码生成 · 建表约定:生成器对租户列和唯一键的要求。