什么是多租户?
多租户(Multi-Tenant)是指一个系统服务多个客户(租户),每个租户的数据必须完全隔离。
三种数据隔离模式
共享数据库共享表
所有租户共用表,通过 tenantId 区分
共享数据库独立表
每个租户独立 schema
独立数据库
每个租户一个数据库
推荐方案:tenantId 模式
在大多数 SaaS 系统中,推荐使用共享数据库 + tenantId 字段的方式,成本最低,扩展性最好。
数据库设计
schema.prisma
prisma
model User {
id Int @id @default(autoincrement())
name String
email String
tenantId String
createdAt DateTime @default(now())
@@index([tenantId])
}租户上下文传递
必须确保 tenantId 在请求生命周期内全局可访问,否则会造成跨租户数据访问风险。
tenant-context.ts
typescript
import { AsyncLocalStorage } from 'async_hooks';
export const tenantStorage = new AsyncLocalStorage<{ tenantId: string }>();
export function getTenantId() {
return tenantStorage.getStore()?.tenantId;
}NestJS Middleware 注入 tenant
tenant.middleware.ts
typescript
import { Injectable, NestMiddleware } from '@nestjs/common';
import { tenantStorage } from './tenant-context';
@Injectable()
export class TenantMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
const tenantId = req.headers['x-tenant-id'];
tenantStorage.run({ tenantId }, () => {
next();
});
}
}Prisma 全局过滤(关键)
prisma-middleware.ts
typescript
import { PrismaClient } from '@prisma/client';
import { getTenantId } from './tenant-context';
const prisma = new PrismaClient();
prisma.$use(async (params, next) => {
const tenantId = getTenantId();
if (params.model && params.action !== 'findMany' && params.action !== 'aggregate') {
if (params.args?.data) {
params.args.data.tenantId = tenantId;
}
}
if (params.action === 'findMany') {
params.args.where = {
...params.args.where,
tenantId
};
}
return next(params);
});
export default prisma;安全风险说明
必须防范的问题
数据泄露
忘记过滤 tenantId
查询绕过
直接 prisma 调用
中间件失效
AsyncLocalStorage 未传递
测试环境污染
多租户数据混用
企业级增强方案
升级路径
Row Level Security
数据库级隔离(PostgreSQL)
独立数据库模式
高安全 SaaS 架构
分片策略
大规模租户扩展
读写分离
性能优化架构
总结
多租户系统的核心不是 Prisma,而是“数据隔离策略 + 强制执行机制”。只要 tenantId 全链路可靠,就可以构建安全 SaaS 系统。
下一篇(终局升级)
最终架构方向
CQRS + Prisma
读写分离架构设计
分库分表系统
超大规模数据架构
相关笔记
在数字化一期,此笔记未产生更深相关的共鸣分枝。