Skip to main content

自定义装饰器

自定义路由装饰器

Nest 是建立在一种称为装饰器的语言特性周围的。 装饰器在许多常用的编程语言中是一个众所周知的概念,但在 JavaScript 的世界中,它们仍然相对较新。 为了更好地理解装饰器的工作原理,我们建议阅读这篇文章。以下是一个简单的定义:

tip

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

参数装饰器

Nest 提供了一组有用的参数装饰器,您可以与 HTTP 路由处理程序一起使用。 以下是提供的装饰器以及它们代表的普通 Express(或 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() 装饰器,并在所有控制器中重复使用它。

user.decorator.ts
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);
}

传递数据

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

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

让我们定义一个装饰器,它以属性名为键,并返回相关值(如果存在,或者如果不存在, 或者用户对象尚未创建,则返回 undefined)。

user.decorator.ts
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;
},
);

以下是您如何通过控制器中的@User()装饰器访问特定属性:

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

您可以使用相同的装饰器和不同的键访问不同的属性。 如果用户对象是深层次或复杂的,这可以使请求处理程序的实现更容易阅读。

tip

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

与管道一起使用

Nest 将自定义参数装饰器与内置装饰器(@Body()@Param()@Query())以相同的方式处理。 这意味着管道也会对自定义注解的参数执行(在我们的示例中是user参数)。 此外,您还可以直接将管道应用于自定义装饰器

@Get()
async findOne(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserEntity,
) {
console.log(user);
}
tip

请注意,validateCustomDecorators 选项必须设置为 true。 默认情况下,ValidationPipe 不会验证使用自定义装饰器注释的参数。

装饰器组合

Nest 提供了一个帮助方法来组合多个装饰器。 例如,假设您想将所有与身份验证相关的装饰器组合成一个单独的装饰器,可以使用以下构造:

auth.decorator.ts
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() {}

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

tip

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