Nest js POST 请求不识别 DTO 方法

Nest js POST Request Not Recognizing DTO method

我在点击 POST 端点时遇到了一些问题,该端点触发了我的 postgres 数据库的 typeorm repository.save() 方法。

这是我的 DTO 对象:

import { ApiProperty } from '@nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';

import { Client } from '../../../models';
import { User } from '../../../user.decorator';


export class ClientDTO implements Readonly<ClientDTO> {
    @ApiProperty({ required: true })
    @IsUUID()
    id: string;


    @ApiProperty({ required: true })
    @IsString()
    name: string;

    public static from(dto: Partial<ClientDTO>) {
      const cl = new ClientDTO();
      cl.id = dto.id;
      cl.name = dto.name;
      return cl;
    }

    public static fromEntity(entity: Client) {
      return this.from({
        id: entity.id,
        name: entity.name,
      });
    }

    public toEntity = (user: User | null) => {
      const cl = new Client();
      cl.id = this.id;
      cl.name = this.name;
      cl.createDateTime = new Date();
      cl.createdBy = user ? user.id : null;
      cl.lastChangedBy = user ? user.id : null;
      return cl;
    }
  }

我的控制器在 POST - /client:

import { 
    Body,
    Controller, 
    Get, Post 
} from '@nestjs/common';

import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';

@Controller('client')
export class ClientController {
    constructor(
        private clientService: ClientService
    ) { }

    @Get()
    public async getAllClients(): Promise<ClientDTO[]> {
        return this.clientService.getAllClients();
    }

    @Post()
    public async createClient(@User() user: User, @Body() dto: ClientDTO): Promise<ClientDTO> {
        return this.clientService.createClient(dto, user);
    }
}

我的服务:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';


@Injectable()
export class ClientService {
    constructor(
        @InjectRepository(Client) private readonly clientRepository: Repository<Client>
    ) {}

    public async getAllClients(): Promise<ClientDTO[]> {
        return await this.clientRepository.find()
            .then(clients => clients.map(e => ClientDTO.fromEntity(e)));
    }

    public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
        return this.clientRepository.save(dto.toEntity(user))
            .then(e => ClientDTO.fromEntity(e));
    }
}

我收到 500 内部服务器错误,日志消息指出我的 ClientDTO.toEntity 不是函数。

TypeError: dto.toEntity is not a function
    at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
    at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
    at C:\...\nest-backend\node_modules\@nestjs\core\router\router-execution-context.js:37:29
    at process._tickCallback (internal/process/next_tick.js:68:7)

我很困惑,因为这只能通过 http 请求发生。我有一个脚本,在我在名为 seed.ts:

的 docker 容器中重新启动它后,它会播种我的开发数据库
import * as _ from 'lodash';

import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';

async function run() {

  const seedUser: User = { id: 'seed-user' };

  const seedId = Date.now()
    .toString()
    .split('')
    .reverse()
    .reduce((s, it, x) => (x > 3 ? s : (s += it)), '');

  const opt = {
    ...configService.getTypeOrmConfig(),
    debug: true
  };

  const connection = await createConnection(opt as ConnectionOptions);
  const clientService = new ClientService(connection.getRepository(Client));

  const work = _.range(1, 10).map(n => ClientDTO.from({
      name: `seed${seedId}-${n}`,
    }))
######################## my service calls ClientDTO.toEntity() without issue ###########################
    .map(dto => clientService.createClient(dto, seedUser) 
      .then(r => (console.log('done ->', r.name), r)))

  return await Promise.all(work);
}

run()
  .then(_ => console.log('...wait for script to exit'))
  .catch(error => console.error('seed error', error));

这让我觉得我错过了什么simple/obvious。

谢谢!

当通过 api 调用进入时,您的 dto 不是基于 class 的对象——它只是一个通用对象。因此它不能有方法,因此您的 toEntity 方法将不起作用。您收到的错误消息是转移注意力的信息,它没有告诉您失败的真正原因。

您可以通过基于您的 class 创建一个新对象,然后在新对象上调用一个方法来从您的普通对象 dto 中复制属性来解决这个问题,或者使用 class-transformer 库,或任何你想要达到相同结果的东西。

dto 在控制器中这样声明 dto: ClientDTO 的事实不足以创建 class 的 个实例。这只是对您和该项目的其他开发人员的指示,以防止在应用程序的其余部分滥用 dto 对象。

为了拥有 classes 的实例,并使用 class 中的方法,您必须像这样显式设置映射:

@Post()
public async createClient(@User() user: User, @Body() dto: ClientDTO): Promise<ClientDTO> {
    const client = ClientDTO.from(dto);
    return this.clientService.createClient(client, user);
}

假设 ClientDTO.from 是用于 dto 中包含的数据的正确函数。如果没有,就修改它,创建一个新的,或者添加一个 constructor.

您似乎在使用 ValidationPipe。这里提到了解决方案 https://github.com/nestjs/nest/issues/552

设置验证管道时,您需要告诉它进行转换,例如

app.useGlobalPipes(new ValidationPipe({
  transform: true
}));