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

TyperORM/NestJS:动态构建查询搜索的Where条件

通用动态筛选条件实现方案

场景与问题

我维护着一个包含多模型的服务,所有模型都支持前端调用筛选(比如State州和City城市实体),城市数据示例如下:

{
  "id": 1,
  "name": "Afonso Cláudio",
  "state": {
    "id": 8,
    "name": "Espírito Santo",
    "abbreviation": "ES"
  } 
}

所有服务都继承自实现了find方法的AbstractService:

async find(query: FindDto): Promise<ResultDto> {
  try {
      if (!query.take) query.take = 20;

      if (query.page) query.skip = (query.page - 1) * query.take;

      const data = await this.repository.find(query);
      return httpSuccess(Constant.OK, data);
    } catch (error) {
      httpErrorHandling(Constant.CANNOT_RETURN_DATA, error);
    }
}

FindDto结构定义:

export class FindDto {
  @ApiPropertyOptional()
  @IsNumber()
  @IsOptional()
  page: number;

  @ApiPropertyOptional()
  @IsNumber()
  @IsOptional()
  take: number;

  @Exclude()
  skip: number;

  @Exclude()
  where: any;
}

当前需要实现一个通用方法,自动识别请求中除page、take、skip外的所有参数,构建repository.find可用的where条件,要求支持:

  • 字符串部分匹配(比如name=Afonso返回名称含该字符串的城市)
  • 关联实体筛选(比如按州ID/缩写筛选城市)
  • 无需逐个字段硬编码处理

现有代码仅支持简单精确匹配,无法满足需求:

const entries = Object.entries(query);

if (query) {
  for (const entry of entries) {
    if (entry[1] !== undefined && !['take', 'page', 'skip'].includes(entry[0])) {
      query.where = { [entry[0]]: entry[1] };
    }
  }
}      

通用解决方案

1. 扩展FindDto支持动态参数

修改FindDto,允许接收任意筛选参数:

export class FindDto {
  @ApiPropertyOptional()
  @IsNumber()
  @IsOptional()
  page: number;

  @ApiPropertyOptional()
  @IsNumber()
  @IsOptional()
  take: number;

  @Exclude()
  skip: number;

  @Exclude()
  where: any;

  // 新增任意键值对,用于接收前端筛选参数
  @Exclude()
  [key: string]: any;
}

2. 编写动态Where条件构建工具

创建通用工具函数,解析参数生成符合TypeORM规范的查询条件:

import { Brackets } from 'typeorm';

/**
 * 解析查询参数生成TypeORM Where条件
 * @param query 请求查询参数
 */
export function buildWhereCondition(query: Record<string, any>) {
  const where: Record<string, any> = {};
  const excludedKeys = ['page', 'take', 'skip'];

  for (const [key, value] of Object.entries(query)) {
    if (excludedKeys.includes(key) || value === undefined) continue;

    // 处理关联实体字段(如state_abbreviation)
    if (key.includes('_')) {
      const [relation, field] = key.split('_', 2);
      where[relation] = { ...(where[relation] || {}), [field]: value };
      continue;
    }

    // 处理模糊匹配(如name_like)
    if (key.endsWith('_like')) {
      const field = key.slice(0, -5);
      where[field] = Brackets.create(qb => {
        qb.where(`LOWER(${field}) LIKE :value`, { value: `%${value.toLowerCase()}%` });
      });
      continue;
    }

    // 默认精确匹配
    where[key] = value;
  }

  return where;
}

3. 集成到AbstractService

修改AbstractService的find方法,调用工具函数生成条件:

async find(query: FindDto): Promise<ResultDto> {
  try {
    if (!query.take) query.take = 20;
    if (query.page) query.skip = (query.page - 1) * query.take;

    // 生成动态where条件
    query.where = buildWhereCondition(query);

    const data = await this.repository.find(query);
    return httpSuccess(Constant.OK, data);
  } catch (error) {
    httpErrorHandling(Constant.CANNOT_RETURN_DATA, error);
  }
}

4. 示例用法

  • 模糊匹配城市名称:{{URL}}city?page=1&name_like=Afonso
  • 筛选ES州的城市:{{URL}}city?state_abbreviation=ES
  • 精确匹配城市ID:{{URL}}city?id=1

扩展说明

  • 可按需添加更多匹配规则:比如_gt(大于)、_lt(小于)、_in(包含),只需在工具函数中补充对应逻辑
  • 支持多层关联实体筛选:可扩展拆分逻辑处理city.state.country_code这类嵌套字段
  • 若使用TypeORM QueryBuilder,可基于此思路生成更复杂的联合查询

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:23:18