我想做的是为我的 NestJS 控制器链接两个管道,一个用于针对特定 DTO 类型验证请求正文,第二个用于将此 DTO 转换为特定类型,这是传递给服务的参数的类型。
@Post()
@UseGuards(JwtAuthGuard, ProductOwnershipGuard)
async create(@Body() createAuctionDto: CreateAuctionDto) {
return this.auctionsService.create(createAuctionDto);
}
JwtAuthGuard
检查用户是否已登录ProductOwnershipGuard
检查请求正文中传递的 product_id
是否属于发行用户拥有的产品ValidateAuctionPipe
根据 createAuctionDto
类型TransformAuctionPipe
通过添加关联产品的一些属性(通过 createAuctionDto
,如我之前提到的),将
Auction
转换为
product_id
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 检查拍卖类型的主体,而不是 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 验证主体
这是我的管道代码:
/* 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);
}
}
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;
}
}
你可以像这样链接多个管道:
@Body(new ParseJsonPipe(), new ZodValidationPipe(createWhitelistUserSchema))
这里 ParseJsonPipe 将在 ZodValidationPipe 之前执行。 ZodValidationPipe 将接收并处理的值将是来自 ParseJsonPipe 的转换值