NestJS11-Custom decorators

379 阅读3分钟

Nest里使用到了装饰器decorators。装饰器在程序语言里是非常被人熟知的,但是在JavaScript领域,它还是相对较新的。为了能够更好的理解装饰器,推荐读一下这篇文章,下面是它简单定义描述:

ES2016装饰器是一个返回函数的表达式,可以将目标名称和属性描述符作为参数通过在装饰符前面加上@字符并将其放在要装饰的内容的最顶端来应用它可以为类方法或属性定义修饰符

参数装饰器

Nest提供一些有用的参数装饰器您可以在Http路由里使用他们。下面是装饰器相对Exporess(或者 fastify)简单的对应表

装饰器对应
@Request(), @Req()req
@Response(), @Res()res
@Next()next
@Session()req.session
@Param(param?: string)req.params / req.params[param]
@Body(param?: string)req.body / req.body[param]
@Query(param?: string)req.query / req.query[param]
@Headers(param?: string)req.headers / req.headers[param]
@Ip()req.ip
@HostParam()req.hosts

另外,您可以定义您自己的装饰器。为什么他们有用? 在Node.js的世界,通常将属性附加到请求对象。然后使用如下代码在每个路由处理程序中手动提取它们:

const user = req.user;

为了使您的代码更具可读性,您可以创建@User装饰器然后在您的控制器Controllers上利用它。

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

然后,您可以在任何你想用的地方用它。

  @Get()
  async findOne(@User() user: UserEntity) {
    console.log(user);
  }

传递数据

当装饰器的行为取决于某些条件时,可以使用数据参数将参数传递给装饰器的工厂函数。其中一个用例是自定义装饰器,它通过键从请求对象中提取参数。例如,假设我们的身份验证层验证请求并将用户实体附加到请求对象。经过身份验证的请求的用户实体可能如下所示:

{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}

让我定义装饰器拿属性参数作为key,如果存在的话则返回相应的值(如果不存在则返回undefined,或者user对象还没有被创建)

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user?.[data] : user;
  },
);

下面是在controller中你怎么通过@user()来访问特定的属性

  @Get()
  async findOneByName(@User('firstName') firstName: string) {
    console.log(`Hello ${firstName}`);
  }

您可以利用不同的key的相同的装饰器来取得不同的属性。如果user又深又复杂,这可以使request请求参数变得更简单更易阅读。

注意
对于TypeScript用户,请注意createParamDecorator<T>()是一个泛型。这意味着您可以显式地强制类型安全,例如createParamDecorator<string>((data,ctx)=>…)。或者,在工厂函数中指定参数类型,例如createParamDecorator((data:string,ctx)=>…)。如果省略这两个,数据的类型将为any

和管道一起工作

Nest对待自定义参数装饰器的方式和内置装饰器(@Body(),@Param()@Query())相同。这意味着也为自定义装饰器可作为执行管道的参数(在我们的示例中为用户参数)。此外,可以将管道直接应用于自定义装饰器:

  @Get('c')
  async findOnePipe(
    @User(new ValidationPipe({ validateCustomDecorators: true }))
    user: UserEntity,
  ) {
    console.log(user);
  }
注意validateCustomDecorators选项必须设置为true。默认情况下,ValidationPipe不验证用自定义修饰符注释的参数。

装饰器组合

Nest提供的帮助方法来组合多个装饰器。比如:您想要把多个认证的装饰器整合到一个装饰器内。可以参照下面的做法。

import { applyDecorators } from '@nestjs/common';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

然后您就能使用@Auth()装饰器了

@Get('users')
@Auth('admin')
findAllUsers() {}

这具有用一个声明应用所有四个装饰器的效果。

警告
@nestjs/swagger包中的@ApiHideProperty()装饰器不可组合,无法与applyDecorators函数一起正常工作。

本章代码

代码