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
相关产品推荐
相关产品推荐

