0

我正在尝试使用class-transformerNestJS。我正在构建一个基于 Mongoose 和 GraphQL 的 API。这就是我在我的 GraphQL 对象之一中使用 Exclude 装饰器的方式:

@ObjectType('user')
export class User extends Teacher {
  @Field()
  login: string;
    
  @Field()
  @Exclude()
  password: string;
}

这是我在 UserResolver 中与ClassSerializerInterceptor一起使用的方法:

@UseInterceptors(ClassSerializerInterceptor)
@Mutation(returns => User, { name: 'editUserById', nullable: true })
async editById(
  @Args('id') id: string,  
  @Args({ name: "item", type: () => UserInputType }) item: UserInputType
) {
  const user = await this.usersService.editById(id, item);
  return user;
}

我想要做的是从这个突变用户字段中获取没有密码(在 GraphQL 对象中排除)。但不幸的是,所有字段都是空的。来自 GraphQL 游乐场的错误示例:

{
  "errors": [
    {
      "message": "Cannot return null for non-nullable field user.firstName.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "editUserById",
        "firstName"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ],
  "data": {
    "editUserById": null
  }
}

教师对象:

@ObjectType('teacher')
@InputType()
export class Teacher {
  @Field()
  _id: string;

  @Field()
  firstName: string;

  @Field()
  lastName: string;

  @Field(type => [String], { nullable: true })
  schoolsIds: string[];

  @Field(type => [School], { nullable: true })
  schools: School[];
}

没有拦截器,一切正常(除了密码仍然可见)。任何想法我做错了什么?

4

2 回答 2

0

您已经告诉 GraphQL,您将返回一个User具有不可为空字段的对象password。您还告诉过class-transformer,当plainToClass运行时,必须删除该password字段。所以现在 GraphQL 很不高兴,因为你已经破坏了你说应该存在的对象契约(返回的对象必须有一个字段),因为你已经告诉另一个库删除该字段。password

你有几个选择:

  1. 用于@Field({ nullable: true })告诉 GraphQL 该字段不必返回。这仍然意味着有人可以查询该字段,而且总是undefined如此

  2. 删除password @Field()注释,因为它不应该在user查询中返回,并且只保留在@InputType()对象上。

编辑

感谢您从这个答案的评论中得到答案,您正在从数据库中返回一个对象,而不是User该类的一个实例。如果您的数据库返回的内容具有类转换器装饰器,您将需要检查那里可能发生的情况(例如可能不包括字段)。另外,请注意返回 mongoose 对象直接使用类转换器确实存在一些问题,您可能需要将数据库返回转换为纯 JSON,然后转换为类class-transformer才能正常工作

于 2021-01-09T20:53:53.650 回答
0

您可以使用此示例https://gist.github.com/EndyKaufman/aa8a7ebb750540217a08fac72292a9c2

import { CallHandler, ClassSerializerInterceptor, ExecutionContext, Injectable, Logger, Module, PlainLiteralObject, SetMetadata, Type } from '@nestjs/common';
import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
import { ClassTransformOptions, plainToClass } from 'class-transformer';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export const GQL_RETURN_TYPE = 'GQL_RETURN_TYPE';

export const GqlReturn = (type: Type<any>) => SetMetadata('GQL_RETURN_TYPE', type);

@Resolver()
export class PaymentBalanceResolver {
    @GqlReturn(BalanceDto)
    @Query(() => BalanceDto, {
        nullable: false,
    })
    getUserBalance(@CurrentUser() currentUser: CurrentUserType): Observable<BalanceDto> {
        return this.paymentBalanceService.getUserBalance(currentUser.id);
    }
}

@Module({
    providers: [
        {
            provide: APP_INTERCEPTOR,
            useFactory: (reflector: any): ClassSerializerInterceptor => new ClassSerializerInterceptor(reflector),
            inject: [Reflector],
        },
        PaymentBalanceResolver,
    ],
})
export class AppModule {}

@Injectable()
export class CoreClassSerializerInterceptor extends ClassSerializerInterceptor {
    private readonly logger = new Logger(CoreClassSerializerInterceptor.name);

    constructor(protected readonly reflector: any, defaultOptions?: ClassTransformOptions) {
        super(reflector, defaultOptions);
        this.logger.log('create');
    }

    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        if ((context.getType() as string) === 'graphql') {
            const op = context.getArgByIndex(3).operation.operation;
            if (op === 'subscription') {
                return next.handle();
            }
            const contextOptions = this.getContextOptions(context);
            const options = {
                ...this.defaultOptions,
                ...contextOptions,
            };
            return next
                .handle()
                .pipe(
                    map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
                        this.serialize(res, { ...options, returnClass: Reflect.getMetadata(GQL_RETURN_TYPE, context.getHandler()) })
                    )
                );
        }
        return next.handle();
    }

    serialize(
        response: PlainLiteralObject | Array<PlainLiteralObject>,
        options: ClassTransformOptions & { returnClass: any }
    ): PlainLiteralObject | Array<PlainLiteralObject> {
        try {
            const result = super.serialize(options.returnClass ? plainToClass(options.returnClass, response) : response, options);
            return result;
        } catch (err) {
            this.logger.debug(response);
            this.logger.error(err);
            throw err;
        }
    }
}
于 2021-08-26T07:21:15.990 回答