node.js - @UsePipes(ValidationPipe) 不适用于泛型(抽象 Controller )

标签 node.js typescript oop generics nestjs

我正在使用 Nest.js 和 MySQL 构建 API。由于敏捷性和 DRY 原则,我正在创建一个 OOP 结构,它为给定实体(来自 TypeORM)设置所有基本 CRUD 端点。主要目标是避免为不同的实体编写相同的通用方法。

为了实现这一目标,我使用了 TypeScript Generics 策略。我仍然必须为每个实体创建所有通用文件(.controller.ts.service.ts.module.ts.entity.ts),但我不必编写其方法。相反,我只扩展了两个类:RestControllerRestService。这些类已经实现了通用方法,但我必须传递一些T类型作为参数,以便 TypeORM 可以将正确的存储库注入(inject)到服务中。

问题:当我在父类 (RestController) 中使用 @UsePipes 装饰器时,它不会被调用,但当我在子类 (SubcategoriesController) 中覆盖 de RestController create 方法时,它可以正常工作。

rest.controller.ts:

import { Get, Post, Body, Param, Put, Delete, UsePipes, ValidationPipe } from '@nestjs/common';
import { RestService } from './rest.service';
import { ObjectLiteral } from 'typeorm';

export abstract class RestController<T, C = T, U = T> {
  constructor(protected service: RestService<T, C, U>) {}

  @Get()
  async index(): Promise<T[]> {
    return this.service.getAll();
  }

  @Post('create')
  @UsePipes(ValidationPipe) //HERE!
  async create(@Body() data: C): Promise<T> {
    return this.service.create(data as C);
  }
}

rest.service.ts:

import { Repository, UpdateResult, DeleteResult, Entity, DeepPartial } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

export interface RestClass<T, C = T, U = T> {
  // Properties
  repository: Repository<T>;

  // Default Methods
  getAll(): Promise<T[]>;
  create(model: T | C | U): Promise<T>;
}

export class RestService<T, C = T, U = T> implements RestClass<T, C, U> {
  constructor(
    public repository: Repository<T>,
  ) {}

  getAll = async () => {
    return await this.repository.find({relations:: this.repository.metadata.ownRelations.map(r => r.propertyName)});
  }

  create = async (model: C) => {
    return await this.repository.save(model as C);
  }
}

这是我如何设置真实的实体端点,扩展上述类:

subcategories.controller.ts:

import { Controller, Get, Post, UsePipes, ValidationPipe, Body } from '@nestjs/common';
import { SubcategoriesService } from './subcategories.service';
import { Subcategory } from './subcategory.entity';
import { RestController } from '../rest.controller';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';

//NOTE THE TYPE PARAMS IN <>
@Controller('subcategories')
export class SubcategoriesController extends RestController<Subcategory, CreateSubcategoryDTO> {
  constructor(public service: SubcategoriesService) {
    super(service);
  }    
}

子类别.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Subcategory } from './subcategory.entity';
import { Repository } from 'typeorm';
import { RestService } from '../rest.service';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';

//NOTE THE TYPE PARAMS IN <>
@Injectable()
export class SubcategoriesService extends RestService<Subcategory, CreateSubcategoryDTO> {

  constructor(
    @InjectRepository(Subcategory) repository: Repository<Subcategory>,
  ) {
    super(repository);
  }
}

createSubcategory.dto.ts

import { IsString, Length, IsInt } from 'class-validator';

export class CreateSubcategoryDTO {

  @IsString()
  @Length(5, 60)
  name: string;

  @IsString()
  @Length(0, 140)
  summary: string;

  @Length(0, 140)
  icon: string;

  @IsInt()
  category: number;
}

可以看到父类接受3种类型的参数:

  • T:实体
  • C:CreateDTO,可选
  • U:UpdateDTO,可选

上面的代码完美地创建了端点,但是它没有验证/create中的有效负载,正如ValidationPipe所期望的那样。

如果我覆盖 SubcategoriesController 中的 create 方法,并在其中添加 UsePipes,它可以工作!

我认为这可能是一个涉及 Nest 生命周期的错误,它可能不支持在抽象类中使用 Pipes。

有人有想法吗?

附注。没有转译错误、lint 警告或运行时异常。

最佳答案

对此的一个解决方案是为 Controller 创建一个工厂函数,该函数将接受您的 body param 类作为参数,然后将其传递给自定义 ValidationPipe 扩展,如下所示:

@Injectable()
export class AbstractValidationPipe extends ValidationPipe {
  constructor(
    options: ValidationPipeOptions,
    private readonly targetTypes: {
      body?: Type<any>;
      query?: Type<any>;
      param?: Type<any>;
      custom?: Type<any>;
    },
  ) {
    super(options);
  }

  async transform(value: any, metadata: ArgumentMetadata) {
    const targetType = this.targetTypes[metadata.type];
    if (!targetType) {
      return super.transform(value, metadata);
    }
    return super.transform(value, { ...metadata, metatype: targetType });
  }
}

export interface IController<T> {
  hello(body: T);
}

export function Factory<T>(bodyDto: ClassType<T>): ClassType<IController<T>> {
  @Controller()
  class ControllerHost<T> implements IController<T> {
    @Post()
    @UsePipes(new AbstractValidationPipe({whitelist: true, transform: true}, {body: bodyDto}))
    hello(@Body() body: T) {
      return "hello"
    }
  }
  return ControllerHost;
}

export class MyDto {
  @Expose()
  @IsDefined()
  @IsString()
  hello: string;
}

export class AppController extends Factory<MyDto>(MyDto) {}

没有关于 Reflection 可用的泛型的信息,因此标准 ValidationPipe 无法从 metadata.metatype 获取任何有意义的信息。我正在通过为其提供可选类型参数来解决这个问题,它可以用来覆盖 metadata.metatype 的内容。它有一个很好的功能,它也适用于正常用例(没有泛型)。如果您也想覆盖 queryparam,只需通过 targetTypes param 提供适当的值即可。

关于node.js - @UsePipes(ValidationPipe) 不适用于泛型(抽象 Controller ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55818694/

相关文章:

oop - 内部和私有(private)有什么区别

node.js - 为什么我使用 nodemon 和 ts-node 时 Node 检查器不启动?

javascript - 对象方法比全局函数快吗?

python - 与 Node.js 服务器应用程序通信

javascript - Angular 4 ng serve --prod 与 ng serve

javascript - index.js 没有做 index.ts 做的事情

javascript - 将 Angular 6 应用程序作为简单的 JS 文件集成到现有的 HTML 站点中

c++ - 如何巧妙地、多态地用许多不同的成员类型填充对象?

javascript - node.js 从字符串属性名称设置属性

javascript - 下载多个SFTP文件时NodeJS报错 "Possible EventEmitter memory leak detected. 11 error listeners added"