You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJS中链式调用验证与转换管道?

NestJS验证与转换管道链式调用

我想要为NestJS控制器链式调用两个管道:一个用于验证请求体是否符合指定DTO类型,另一个用于将该DTO转换为服务方法所需的特定类型。

当前控制器:

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body() createAuctionDto: CreateAuctionDto) {
  return this.auctionsService.create(createAuctionDto);
}

期望流程

  1. JwtAuthGuard 校验用户是否已登录
  2. ProductOwnershipGuard 校验请求体中的product_id对应的产品是否属于当前用户
  3. ValidateAuctionPipe 校验用户传入的请求体是否符合CreateAuctionDto类型
  4. TransformAuctionPipe 将CreateAuctionDto转换为Auction类型,通过关联产品(前文提到的product_id)添加部分属性
  5. Controller 接收Auction类型的变量,并将其传入auctionsService

已尝试的解决方案

首先,我使用了全局ValidationPipe,因此@Body() createAuctionDto: CreateAuctionDto会自动验证请求体是否符合CreateAuctionDto,无需额外注解。

方案1

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(createAuctionDto);
}

问题:全局ValidationPipe会校验请求体是否符合Auction类型,而非CreateAuctionDto。

方案2

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(new ValidationPipe()) createAuctionDto: CreateAuctionDto, @Body(TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(auction);
}

问题:能正确验证createAuctionDto,但TransformAuctionPipe接收到的是Auction类型的空对象。

方案3

@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body(new ValidationPipe({ expectedType: CreateAuctionDto }), TransformAuctionPipe) auction: Auction) {
  return this.auctionsService.create(auction);
}

问题:完全没有对请求体进行CreateAuctionDto验证。

方案4

尝试使用@UsePipes()装饰器替代在@Body()中传入管道,但同样无效。

管道代码

ValidateDtoPipe

/* eslint-disable @typescript-eslint/ban-types */
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

@Injectable()
export class ValidateDtoPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToInstance(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

TransformAuctionPipe

import { ArgumentMetadata, Injectable, NotFoundException, PipeTransform } from '@nestjs/common';
import { ProductsService } from 'src/api/products/products.service';
import { CreateAuctionDto } from '../dto/create-auction.dto';
import { Auction } from '../entities/auction.entity';
import { EAuctionStatus } from '../types/auction.types';

/**
 * This pipe transforms CreateAuctionDto to Auction,
 * by fetching associated Product and adding its properties to CreateAuctionDto
 * */
@Injectable()
export class TransformAuctionPipe implements PipeTransform<CreateAuctionDto, Promise<Auction>> {
  constructor(private readonly productsService: ProductsService) {}

  async transform(createAuctionDto: CreateAuctionDto, metadata: ArgumentMetadata): Promise<Auction> {
    const product = await this.productsService.findOneById(createAuctionDto.product_id);

    if (!product) {
      throw new NotFoundException('Product not found.');
    }

    const auctionStatus: EAuctionStatus = +createAuctionDto.start_at <= Date.now() ? EAuctionStatus.ACTIVE : EAuctionStatus.PENDING;
    const { name, description, price } = product;

    const auction = new Auction({ ...createAuctionDto, name, description, price, product, status: auctionStatus });

    return auction;
  }
}

内容的提问来源于stack exchange,提问作者fufuj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 16:23:08