首先,@Carlo Corradini 的评论是正确的,你应该看看 class-transformer 和 class-validator 库,它们也在 NestJS 管道的底层使用,并且可以很好地与 @987654327 结合使用@。
1:根据@Carlo Corradini 的评论及其相应链接
现在,由于您的 DTO 实例是您希望向消费者公开的数据的表示形式,因此您必须在检索到用户实体后对其进行实例化。
- 创建一个新的
user-response.dto.ts 文件并在其中声明一个您将导出的UserResponseDto 类。假设您想公开之前检索到的 User 实体中的所有内容,代码如下所示
user-response.dto.ts
import { IsNumber, IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class UserResponseDto {
@Expose()
@IsNumber()
id: number;
@Expose()
@IsString()
username: string;
@Expose()
@IsString()
firstName: string;
@Expose()
@IsString()
lastName: string;
@Expose()
@IsString()
birthDate: string;
}
这里@Exclude() 在UserResponseDto 的顶部,我们告诉class-transformer 在我们将实例化时排除DTO 文件中没有@Expose() 装饰器的任何字段来自任何其他对象的UserResponseDto。
然后使用@IsString() 和@IsNumber(),我们告诉类验证器在我们验证给定字段的类型时验证它们。
- 将您的 User 实体转换为
UserResponseDto 实例:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async findByUsername(username: string): Promise<User | undefined> {
const retrievedUser = await this.usersRepository.findOne({ username });
// instantiate our UserResponseDto from retrievedUser
const userResponseDto = plainToClass(UserResponseDto, retrievedUser);
// validate our newly instantiated UserResponseDto
const errors = await validate(userResponseDto);
if (errors.length) {
throw new BadRequestException('Invalid user',
this.modelHelper.modelErrorsToReadable(errors));
}
return userResponseDto;
}
}
2:另一种实现方式:
您还可以使用 @nestjs/common 中的 ClassSerializerInterceptor interceptor 自动将您返回的 Entity 实例从服务转换为控制器中定义的正确返回类型方法。这意味着您甚至不必费心在您的服务中使用 plainToClass 并让 Nest 的拦截器自己完成工作,官方文档中说明了一些细节
请注意,我们必须返回该类的实例。如果您返回一个
纯 JavaScript 对象,例如 { user: new UserEntity() },
对象不会被正确序列化。
代码如下所示:
users.controller.ts
import { ClassSerializerInterceptor, Controller, Get, Query } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('api/v1/backoffice')
@UseInterceptors(ClassSerializerInterceptor) // <== diff is here
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':username')
findOne(@Query('username') username: string) {
return this.usersService.findByUsername(username);
}
}
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async findByUsername(username: string): Promise<User | undefined> {
return this.usersRepository.findOne({ username }); // <== must be an instance of the class, not a plain object
}
}
最后的想法:
使用最新的解决方案,您甚至可以在您的用户实体文件中使用class-transformer 的装饰器,而不必声明 DTO 文件,但您会丢失数据验证。
如果有帮助或不清楚的地方,请告诉我:)
使用传入的有效负载验证和转换为适当的 DTO 进行编辑
你可以声明一个带有用户名属性的GetUserByUsernameRequestDto,如下所示:
get-user-by-username.request.dto.ts
import { IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class GetUserByUsernameRequestDto {
@Expose()
@IsString()
@IsNotEmpty()
username: string;
}
users.controller.ts
import { ClassSerializerInterceptor, Controller, Get, Query } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('api/v1/backoffice')
@UseInterceptors(ClassSerializerInterceptor) // <== diff is here
@UsePipes( // <= this is where magic happens :)
new ValidationPipe({
forbidUnknownValues: true,
forbidNonWhitelisted: true,
transform: true
})
)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':username')
findOne(@Param('username') getUserByUsernameReqDto: GetUserByUsernameRequestDto) {
return this.usersService.findByUsername(getUserByUsernameReqDto.username);
}
}
这里我们使用Nest's pipes concept - @UsePipes() - 来完成工作。以及来自 Nest 的内置 ValidationPipe。
您可以参考Nest 和class-validator 自己的文档,以了解有关传递给ValidationPipe 的选项的更多信息
因此,这样您的传入参数和有效负载数据可以在处理之前进行验证:)