You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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 });
    }
  }
}

关键注意事项

  1. 超级管理员绕过逻辑:如果需要允许超级管理员跨租户操作,可在getCurrentTenantId函数中判断权限,返回null或特殊标识,此时上述逻辑会自动跳过tenantId条件追加。
  2. 原生查询处理:如果使用createQueryBuilder().execute()或原生SQL,上述方案不会生效,需手动封装原生查询工具函数,统一追加tenantId条件。
  3. 关联表处理:如果关联的实体也需要多租户隔离,需确保关联查询时也追加对应表的tenantId条件(自定义Repository可在join时手动处理,订阅者可通过遍历queryBuilder的join项判断处理)。
  4. 订阅者注册:确保在TypeORM的数据源配置中注册订阅者,比如在data-source.ts的subscribers数组中添加TenantSubscriber。

内容的提问来源于stack exchange,提问作者seepine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 22:08:15