如何在TypeORM中自动为find*或QueryBuilder添加租户查询条件?
实现TypeORM多租户查询/更新/删除自动追加tenantId条件
下面提供两种实用方案,可根据你的场景选择:
方案一:自定义基础Repository(推荐,灵活可控)
创建通用的多租户基础Repository,所有需要多租户隔离的实体Repository都继承它,封装通用的查询、更新、删除逻辑:
import { Repository, SelectQueryBuilder, EntityManager } from "typeorm"; // 多租户基础Repository export class BaseTenantRepository<T> extends Repository<T> { // 重写find方法,自动追加tenantId条件 async find(options: any = {}) { const tenantId = getCurrentTenantId(); if (tenantId) { options.where = { ...options.where, tenantId }; } return super.find(options); } // 重写findOne方法 async findOne(options: any = {}) { const tenantId = getCurrentTenantId(); if (tenantId) { options.where = { ...options.where, tenantId }; } return super.findOne(options); } // 重写createQueryBuilder,自动给查询链加tenantId条件 createQueryBuilder(alias?: string, queryRunner?: EntityManager): SelectQueryBuilder<T> { const queryBuilder = super.createQueryBuilder(alias, queryRunner); const tenantId = getCurrentTenantId(); if (tenantId) { queryBuilder.andWhere(`${alias}.tenantId = :tenantId`, { tenantId }); } return queryBuilder; } // 重写update方法,确保只更新当前租户的数据 async update(criteria: any, partialEntity: Partial<T>) { const tenantId = getCurrentTenantId(); if (tenantId) { return super.update({ ...criteria, tenantId }, partialEntity); } return super.update(criteria, partialEntity); } // 重写delete方法,确保只删除当前租户的数据 async delete(criteria: any) { const tenantId = getCurrentTenantId(); if (tenantId) { return super.delete({ ...criteria, tenantId }); } return super.delete(criteria); } }
然后在实体类中指定使用这个自定义Repository:
import { Entity, PrimaryColumn, Column } from "typeorm"; import { BaseTenantRepository } from "./BaseTenantRepository"; @Entity({ repository: () => BaseTenantRepository }) export class User { @PrimaryColumn() id!: string; // 其他字段... @Column() tenantId!: string; }
方案二:扩展订阅者(全局统一生效)
直接扩展你已有的TenantSubscriber,监听查询、更新、删除的前置事件,自动追加tenantId条件:
import { EventSubscriber, EntitySubscriberInterface, BeforeInsertEvent, BeforeSelectEvent, BeforeUpdateEvent, BeforeRemoveEvent } from "typeorm"; @EventSubscriber() export class TenantSubscriber implements EntitySubscriberInterface { // 原有的插入自动设置tenantId逻辑 async beforeInsert(event: InsertEvent<any>) { const tenantId = getCurrentTenantId(); if (tenantId && event.entity['tenantId'] === undefined) { event.entity['tenantId'] = tenantId; } } // 查询前追加tenantId条件 async beforeSelect(event: BeforeSelectEvent<any>) { const tenantId = getCurrentTenantId(); if (tenantId) { event.queryBuilder.andWhere(`${event.alias}.tenantId = :tenantId`, { tenantId }); } } // 更新前追加tenantId条件,防止跨租户更新 async beforeUpdate(event: BeforeUpdateEvent<any>) { const tenantId = getCurrentTenantId(); if (tenantId) { event.queryBuilder.andWhere(`${event.alias}.tenantId = :tenantId`, { tenantId }); } } // 删除前追加tenantId条件,防止跨租户删除 async beforeRemove(event: BeforeRemoveEvent<any>) { const tenantId = getCurrentTenantId(); if (tenantId) { event.queryBuilder.andWhere(`${event.alias}.tenantId = :tenantId`, { tenantId }); } } }
关键注意事项
- 超级管理员绕过逻辑:如果需要允许超级管理员跨租户操作,可在
getCurrentTenantId函数中判断权限,返回null或特殊标识,此时上述逻辑会自动跳过tenantId条件追加。 - 原生查询处理:如果使用
createQueryBuilder().execute()或原生SQL,上述方案不会生效,需手动封装原生查询工具函数,统一追加tenantId条件。 - 关联表处理:如果关联的实体也需要多租户隔离,需确保关联查询时也追加对应表的tenantId条件(自定义Repository可在join时手动处理,订阅者可通过遍历queryBuilder的join项判断处理)。
- 订阅者注册:确保在TypeORM的数据源配置中注册订阅者,比如在
data-source.ts的subscribers数组中添加TenantSubscriber。
内容的提问来源于stack exchange,提问作者seepine
相关产品推荐
相关产品推荐

