NestJS/Mongoose:序列化不排除普通输出中的属性

kos*_*smo 8 javascript nestjs nestjs-mongoose

我开始使用 NestJS,从旧的express/mongoose 项目迁移,并立即撞上了栅栏,只是遵循 NestJS 文档中的 MongoDB/序列化章节。我准备了以下架构

/////// schema
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Exclude, Expose } from 'class-transformer';

export type UserDocument = User & mongoose.Document;

@Schema()
export class User {
    @Prop()
    @Exclude()
    _id: String

    @Expose()
    get id(): String { return this._id ? `${this._id}` : undefined }

    @Prop()
    name: string

    @Prop({ unique: true })
    login: string

    @Exclude()
    @Prop()
    password: string        
}

export const UserSchema = SchemaFactory.createForClass(User);
Run Code Online (Sandbox Code Playgroud)

将其注册到app.module中

MongooseModule.forRoot('mongodb://localhost/old_project'), 
MongooseModule.forFeature([ { name: User.name, schema: UserSchema } ]),
Run Code Online (Sandbox Code Playgroud)

并尝试跟随电话,期望结果中不会显示密码属性

/////// controller
  @UseInterceptors(ClassSerializerInterceptor)
  @Get('default')
  async default(): Promise<User> {
    let u = new User();
    u.name = 'Kos';
    u.password = "secret";
    u.login = 'k@o.s'

    return u;
  }
  
  // returns
  // {"name":"Kos","login":"k@o.s"}

  @Get('first_raw')
  async firstRaw(): Promise<User> {
    return this.userModel.findOne()
  }
  
  @Get('first_lean')
  async firstLean(): Promise<User> {
    return this.userModel.findOne().lean()
  }
  
  //both return
  // {"_id":"5f8731a36fc003421db08921","name":"Kos","login":"kos","password":"secret","__v":0}

  @UseInterceptors(ClassSerializerInterceptor)
  @Get('first_raw_stripped')
  async firstRawStripped(): Promise<User> {
    return this.userModel.findOne()
  }
  
  //returns
  // {"$__":{"strictMode":true,"selected":{},"getters":{},"_id":"5f8731a36fc003421db08921","wasPopulated":false,"activePaths":{"paths":{"_id":"init","name":"init","login":"init","password":"init","__v":"init"},"states":{"ignore":{},"default":{},"init":{"_id":true,"name":true,"login":true,"password":true,"__v":true},"modify":{},"require":{}},"stateNames":["require","modify","init","default","ignore"]},"pathsToScopes":{},"cachedRequired":{},"$setCalled":[],"emitter":{"_events":{},"_eventsCount":0,"_maxListeners":0},"$options":{"skipId":true,"isNew":false,"willInit":true,"defaults":true}},"isNew":false,"$locals":{},"$op":null,"_doc":{"_id":"5f8731a36fc003421db08921","name":"Kos","login":"kos","password":"secret","__v":0},"$init":true}

  @UseInterceptors(ClassSerializerInterceptor)
  @Get('first_lean_stripped')
  async firstLeanStripped(): Promise<User> {
    return this.userModel.findOne().lean()
  }
  
  //returns
  // {"_id":"5f8731a36fc003421db08921","name":"Kos","login":"kos","password":"secret","__v":0}
Run Code Online (Sandbox Code Playgroud)

最后我发现只有 User 类的手动实例化才能以某种方式完成它应该做的事情,所以我向 User 类添加了构造函数

constructor(partial?: Partial<User>) {
    if (partial)
        Object.assign(this, partial);
}
Run Code Online (Sandbox Code Playgroud)

然后它最终返回了预期的结果 - 结果中没有密码道具

  @UseInterceptors(ClassSerializerInterceptor)
  @Get('first')
  async first(): Promise<User> {
    return new User(await this.userModel.findOne().lean());
  }
  
  //finally returns what's expected
  // {"name":"Kos","login":"kos","__v":0,"id":"5f8731a36fc003421db08921"}
Run Code Online (Sandbox Code Playgroud)

我错过了什么吗?不知怎的,似乎有点太过分了……

更新:这是关于 NestJS mongoose 和序列化耦合的问题 - 为什么会这样

  @UseInterceptors(ClassSerializerInterceptor)
  @Get('first')
  async first(): Promise<User> {
    return await this.userModel.findOne().lean();
  }
Run Code Online (Sandbox Code Playgroud)

不起作用,这个

  @UseInterceptors(ClassSerializerInterceptor)
  @Get('first')
  async first(): Promise<User> {
    return new User(await this.userModel.findOne().lean());
  }
Run Code Online (Sandbox Code Playgroud)

有效(这也意味着每个结果可枚举映射需要创建实体)

Ali*_*fat 8

花了几个小时终于找到了这篇文章中描述的解决方案

\n
\n

我们用于连接 MongoDB 并获取实体的 Mongoose 库不会返回 User 类的实例。因此,ClassSerializerInterceptor won\xe2\x80\x99t 开箱即用。

\n
\n

首先:创建一个用于 mongoose 序列化的拦截器:

\n

mongooseClassSerializer.interceptor.ts

\n
import {\n  ClassSerializerInterceptor,\n  PlainLiteralObject,\n  Type,\n} from \'@nestjs/common\';\nimport { ClassTransformOptions, plainToClass } from \'class-transformer\';\nimport { Document } from \'mongoose\';\n \nfunction MongooseClassSerializerInterceptor(\n  classToIntercept: Type,\n): typeof ClassSerializerInterceptor {\n  return class Interceptor extends ClassSerializerInterceptor {\n    private changePlainObjectToClass(document: PlainLiteralObject) {\n      if (!(document instanceof Document)) {\n        return document;\n      }\n \n      return plainToClass(classToIntercept, document.toJSON());\n    }\n \n    private prepareResponse(\n      response: PlainLiteralObject | PlainLiteralObject[],\n    ) {\n      if (Array.isArray(response)) {\n        return response.map(this.changePlainObjectToClass);\n      }\n \n      return this.changePlainObjectToClass(response);\n    }\n \n    serialize(\n      response: PlainLiteralObject | PlainLiteralObject[],\n      options: ClassTransformOptions,\n    ) {\n      return super.serialize(this.prepareResponse(response), options);\n    }\n  };\n}\n \nexport default MongooseClassSerializerInterceptor;\n
Run Code Online (Sandbox Code Playgroud)\n

更新您的控制器以应用此拦截器:

\n
@UseInterceptors(MongooseClassSerializerInterceptor(User))\n
Run Code Online (Sandbox Code Playgroud)\n

你的模型(架构)应该如下所示:

\n
import { Prop, Schema, SchemaFactory } from \'@nestjs/mongoose\';\nimport { Document } from \'mongoose\';\nimport { Exclude, Transform } from \'class-transformer\';\n \nexport type UserDocument = User & Document;\n \n@Schema()\nexport class User {\n  @Transform(({ value }) => value.toString())\n  _id: string;\n \n  @Prop({ unique: true })\n  email: string;\n \n  @Prop()\n  name: string;\n \n  @Prop()\n  @Exclude()\n  password: string;\n}\n \nexport const UserSchema = SchemaFactory.createForClass(User);\n
Run Code Online (Sandbox Code Playgroud)\n


PRA*_*PUT 5

正如@Ali Sherafat所解释的,不幸的是,解决方案对我不起作用。

\n
\n

我们用于连接到 MongoDB 和\n获取实体的 Mongoose 库不会返回 User 类的实例。\n因此,ClassSerializerInterceptor 不会\xe2\x80\x99 开箱即用。

\n
\n

我们肯定会要求interceptor for mongoose serialization。因此,我又提出了一种经过修改的类似解决方案。

\n

为猫鼬序列化创建拦截器

\n
import {\n    CallHandler,\n    ExecutionContext,\n    NestInterceptor,\n    UseInterceptors,\n} from '@nestjs/common';\nimport { plainToClass } from 'class-transformer';\nimport { map, Observable } from 'rxjs';\n\ninterface ClassConstructor {\n    new ( ...args: any[ ] ): { };\n}\n\nexport function MongooseClassSerializerInterceptor( dto: any ) {\n    return UseInterceptors( new SerializeInterceptor( dto ) );\n}\n\nexport class SerializeInterceptor implements NestInterceptor {\n    constructor( private dto: any ) { }\n    intercept( context: ExecutionContext, handler: CallHandler ): Observable< any > {\n\n        return handler.handle( ).pipe(\n            map( ( data: any ) => { \n                return plainToClass( this.dto, data, { \n                    excludeExtraneousValues: true\n                } )\n            } )\n        )\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

创建用户 dto as,这样您就可以将其用于不同的角色。因此,对于普通用户,我们可以公开所需的内容

\n
import { Expose } from "class-transformer";\n\n    export class UserDto {\n    \n        @Expose( )\n        id: number;\n    \n        @Expose( )\n        name: string;\n\n        @Expose( )\n        login: string;\n    \n    }\n
Run Code Online (Sandbox Code Playgroud)\n

现在在您的控制器中使用@MongooseClassSerializerInterceptor( UserDto )

\n

当想要基于某些角色返回响应时,在 schema 中使用exclude不太灵活,e.g in required case admin may have access to more fields than normal user or vice-versa. In that case this is better approach.

\n


小智 0

我想我有解决方案

@Schema()
export class User {
  @Prop({select: false})
  password: string;
  @Prop()
  username: string;
}
Run Code Online (Sandbox Code Playgroud)

当您对装饰器执行此道具时,查找中将忽略 mongo 内部属性的值。

  • 如果你设置为 false,则不会获得用户的密码,当你想将密码与 bcrypt 进行比较时,这会破坏代码 (2认同)