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

Mongoose与GraphQL过滤最佳实现方案咨询

问题描述

我正在给一个基于GraphQL + Mongoose的项目添加过滤功能,目前的POC实现如下,但存在严重弊端:每个字段都需要编写大量过滤逻辑,代码复杂度极高,且当Customer模型新增字段时,过滤逻辑代码会持续膨胀。求更优的实现思路或实践经验?


现有实现代码

解析器(Resolver)

//FindAll
@Query(() => [Customer])
@Roles({roles: ['user']})
async customers(@Args('filters') customersFiltersInput: CustomersFiltersInput) {
  const customers = await this.customerService.findAll(customersFiltersInput);
  return customers;
}

服务层(Service)

async findAll(
  customersFiltersInput: CustomersFiltersInput,
): Promise<Customer[]> {
  var filters: any = {};

  if (customersFiltersInput.customerTypes) {
    filters.customerType =
      customersFiltersInput.customerTypesFilteringOption ===
      FilterOptionList.In
        ? { $in: customersFiltersInput.customerTypes }
        : { $nin: customersFiltersInput.customerTypes };
  }
  if (customersFiltersInput.name) {
    if (customersFiltersInput.nameFilteringOption == FilterOptionText.Equals)
      filters.name = { $eq: customersFiltersInput.name };
    if (
      customersFiltersInput.nameFilteringOption == FilterOptionText.NotEquals
    )
      filters.name = { $ne: customersFiltersInput.name };
  }

 //...
 //...
 //新增Customer字段时,此处代码会持续膨胀

  const query = this.customerModel.find(filters);
  return await query.exec();
}

输入类型定义(Input)

@InputType()
export class CustomersFiltersInput {
    @Field(() => [CustomerType, {nullable: true}])
    public readonly customerTypes?: [CustomerType];

    @Field(() => FilterOptionList, {nullable: true, defaultValue: FilterOptionList.In})
    public readonly customerTypesFilteringOption: FilterOptionList

    @Field({nullable: true})
    public readonly name?: String;

    @Field(() => FilterOptionText, {nullable: true, defaultValue: FilterOptionText.Equals})
    public readonly nameFilteringOption: FilterOptionText
}

列表类型过滤选项枚举

import { registerEnumType } from '@nestjs/graphql';

export enum FilterOptionList {
  In = 'In',
  NotIn = 'NotIn',
}

registerEnumType(FilterOptionList, {
  name: 'FilterOptionList',
});

文本类型过滤选项枚举

import { registerEnumType } from '@nestjs/graphql';

export enum FilterOptionText {
  Equals = 'Equals',
  NotEquals = 'NotEquals',
  Like = 'Like',
  NotLike = 'NotLike'
}

registerEnumType(FilterOptionText, {
  name: 'FilterOptionText',
});

GraphQL 查询示例

query allCustomers {
  customers(filters:{
    customerTypes: [ BUSINESS, OTHER, INDIVIDUAL],
    customerTypesFilteringOption: In,
    name: "Alice",
    nameFilteringOption: Equals
  }) {
    id, name, customerType, customerStatus,
    addresses {id, city, street, addressType }
  }
}

优化思路与实践

1. 重构过滤输入结构,字段与过滤选项绑定

将每个字段的过滤值和对应的操作选项封装为独立的输入对象,避免分散定义。新增字段时只需添加对应的过滤输入字段即可:

@InputType()
class TextFilterInput {
  @Field({ nullable: true })
  value?: string;
  
  @Field(() => FilterOptionText, { defaultValue: FilterOptionText.Equals })
  option: FilterOptionText;
}

@InputType()
class ListFilterInput<T> {
  @Field(() => [CustomerType], { nullable: true })
  values?: T[];
  
  @Field(() => FilterOptionList, { defaultValue: FilterOptionList.In })
  option: FilterOptionList;
}

@InputType()
export class CustomersFiltersInput {
  @Field(() => ListFilterInput<CustomerType>, { nullable: true })
  customerType?: ListFilterInput<CustomerType>;
  
  @Field(() => TextFilterInput, { nullable: true })
  name?: TextFilterInput;
  
  // 新增字段时,直接添加对应的过滤输入字段即可
}

2. 封装通用过滤转换工具函数

编写工具函数统一处理不同类型字段的过滤逻辑,将GraphQL过滤输入转换为Mongoose可识别的查询条件,彻底消除重复的if-else判断:

// filter.utils.ts
import { FilterOptionList } from './filter-option-list.enum';
import { FilterOptionText } from './filter-option-text.enum';

export function buildMongooseFilter(filterInput: any, fieldConfig: Record<string, { type: 'text' | 'list' }>) {
  const filters: Record<string, any> = {};

  for (const [field, config] of Object.entries(fieldConfig)) {
    const filter = filterInput[field];
    if (!filter || (!filter.value && !filter.values)) continue;

    switch (config.type) {
      case 'text':
        filters[field] = convertTextFilter(filter.option, filter.value);
        break;
      case 'list':
        filters[field] = convertListFilter(filter.option, filter.values);
        break;
    }
  }

  return filters;
}

function convertTextFilter(option: FilterOptionText, value: string) {
  switch (option) {
    case FilterOptionText.Equals:
      return { $eq: value };
    case FilterOptionText.NotEquals:
      return { $ne: value };
    case FilterOptionText.Like:
      return { $regex: value, $options: 'i' };
    case FilterOptionText.NotLike:
      return { $not: { $regex: value, $options: 'i' } };
    default:
      return { $eq: value };
  }
}

function convertListFilter(option: FilterOptionList, values: any[]) {
  return option === FilterOptionList.In ? { $in: values } : { $nin: values };
}

3. 服务层复用通用逻辑

在服务层直接调用工具函数生成过滤条件,新增字段时仅需在字段配置中添加一行定义:

async findAll(customersFiltersInput: CustomersFiltersInput): Promise<Customer[]> {
  const fieldConfig = {
    customerType: { type: 'list' as const },
    name: { type: 'text' as const },
    // 新增字段时,在这里添加对应的类型配置
  };

  const filters = buildMongooseFilter(customersFiltersInput, fieldConfig);
  return this.customerModel.find(filters).exec();
}

4. 进阶:自动生成过滤输入类型

如果想进一步减少重复代码,可以基于Mongoose Schema自动生成对应的GraphQL过滤输入类型。比如利用@nestjs/mongoose的元数据,或者编写脚本扫描Schema字段,自动生成包含过滤选项的输入类型文件,完全避免手动定义每个字段的过滤输入。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 20:27:25