译文:nest 的守卫、拦截器、自定义路由装饰器(四)

学习一个技术最好的方式就是看他的官网,大家好,我是云牧,这次翻译的是 nest 最新官网。

翻译方式是 chatgpt 主力翻译 + 人工每行校对。错误的地方,欢迎大家语雀评论纠正。

语雀文档地址:nest 官网译文 (yuque.com)

过程中我会对一些感觉不恰当内容有少量的增加修改删除。

希望内容会对大家有所帮助。

守卫

守卫是一个用 @Injectable() 装饰器注释的类,它实现了 CanActivate 接口。

守卫有一个单一的职责。它根据运行时的特定条件(如权限、角色、ACL等)确定是否将给定的请求交给路由处理程序处理。这通常被称为授权。

在传统的 Express 应用程序中,授权(以及它通常协作的身份验证)通常由中间件处理。中间件是身份验证的一个很好选择,因为诸如 token 验证和将属性附加到 request 对象上的操作与特定的路由上下文(及其元数据)没有强关联。

但是中间件本质上是愚蠢的。它不知道在调用 next() 函数后将执行哪个处理程序。另一方面,守卫可以访问 ExecutionContext 实例,因此可以准确地知道接下来将执行什么。它们的设计,就像异常过滤器、管道和拦截器一样,可以让您在请求/响应周期的恰当时机插入处理逻辑,并以声明方式进行。这有助于保持您的代码 DRY 和声明性。

**提示:**守卫在所有中间件之后执行,但在任何拦截器或管道之前执行。

Authorization guard 授权守卫

如前所述,授权是守卫的一个很好的用例,因为特定的路由只有在调用者(通常是特定的已认证用户)具有足够权限时才可用。

我们现在要构建的 AuthGuard 假设已经认证了用户(因此请求头中附带了一个 token)。它将提取和验证 token,并根据提取的信息来确定请求是否可以继续进行。

typescript 复制代码
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

**提示:**如果您正在寻找一个实际的例子来了解如何在您的应用程序中实现身份验证机制,请访问本章(visit this chapter.)。同样,如果您想要更复杂的授权示例,请查看此页面( check this page.)。

validateRequest() 函数内部的逻辑可以简单或复杂,根据需要而定。这个例子的主要目的是展示守卫如何适应请求/响应周期。

每个守卫必须实现一个 canActivate() 函数。该函数应返回一个布尔值,指示当前请求是否被允许。它可以同步或异步地返回响应(通过 Promise 或 Observable )。Nest使用返回值来控制下一步操作:

  • 如果返回 true ,请求将被处理。
  • 如果返回 false ,Nest 将拒绝该请求。

Execution context 执行上下文

canActivate() 函数接受一个参数,即 ExecutionContext 实例。 ExecutionContext 继承自 ArgumentsHost 。我们在异常过滤器章节中已经见过 ArgumentsHost 。

在上面的示例中,我们只是使用了之前在 ArgumentsHost 上定义的相同的辅助方法,以获取对 Request 对象的引用。您可以参考异常过滤器章节中的参数主机部分,了解更多相关内容。

通过扩展 ArgumentsHost , ExecutionContext 还添加了几个新的辅助方法,提供有关当前执行过程的附加详细信息。这些详细信息对于构建更通用的保护程序非常有帮助,可以在广泛的控制器、方法和执行上下文中使用。在这里了解更多关于 ExecutionContext 的信息。

Role-based authentication 基于角色的身份验证

让我们构建一个更加功能强大的守卫,只允许具有特定角色的用户访问。我们将从一个基本的守卫模板开始,并在接下来的部分进行扩展。目前,它允许所有请求继续进行:

typescript 复制代码
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

Binding guards 绑定守卫

像管道和异常过滤器一样,守卫可以是控制器范围、方法范围或全局范围的。下面,我们使用 @UseGuards() 装饰器设置了一个控制器范围的守卫。这个装饰器可以接受一个参数,或者逗号分隔的参数列表。这样你就可以通过一次声明轻松应用适当的守卫集合。

typescript 复制代码
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

**提示:**装饰器从 @nestjs/common 包中导入。

以上,我们传递了 RolesGuard 类(而不是实例),将实例化的责任留给了框架,并实现了依赖注入。与管道和异常过滤器一样,我们也可以传递一个就地实例:

typescript 复制代码
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}

上面的构造将守卫附加到此控制器声明的每个处理程序。如果我们希望守卫仅适用于单个方法,我们在方法级别应用 @UseGuards() 装饰器。

为了设置全局守卫,请使用 Nest 应用实例的 useGlobalGuards() 方法:

typescript 复制代码
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

**注意:**在混合应用的情况下,默认情况下 useGlobalGuards() 方法不会为网关和微服务设置守卫(有关如何更改此行为的信息,请参见混合应用(Hybrid application))。对于"标准"(非混合)微服务应用程序, useGlobalGuards() 会全局挂载守卫。

全局守卫在整个应用程序中使用,适用于每个控制器和每个路由处理程序。就依赖注入而言,从任何模块外部注册的全局守卫(如上面的示例中的 useGlobalGuards() )无法注入依赖项,因为这是在任何模块的上下文之外完成的。为了解决这个问题,您可以使用以下结构直接从任何模块设置守卫:

typescript 复制代码
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

**提示:**使用这种方法进行守卫的依赖注入时,请注意,无论在哪个模块中使用此结构,守卫实际上都是全局的。应该在哪里进行这个操作?选择定义守卫(如上面的 RolesGuard )的模块。此外, useClass 并不是处理自定义提供程序注册的唯一方式。在这里了解更多信息(here)。

Setting roles per handler 设置每个处理程序的角色

我们的 RolesGuard 正在工作,但它还不够智能。我们还没有充分利用最重要的守卫功能-执行上下文。它还不知道角色,或者每个处理程序允许哪些角色。例如, CatsController 可以针对不同的路由具有不同的权限方案。有些可能仅对管理员用户可用,而其他人可能对所有人开放。我们如何以灵活和可重用的方式将角色与路由匹配?

这就是自定义元数据发挥作用的地方(learn more here)。Nest 提供了通过静态方法创建的装饰器或内置的 @SetMetadata() 装饰器,将自定义元数据附加到路由处理程序的能力。

例如,让我们使用 Reflector#createDecorator 方法创建一个 @Roles() 装饰器,将元数据附加到处理程序上。框架提供了 Reflector ,并从 @nestjs/core 包中公开。

typescript 复制代码
import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();

这里的 Roles 装饰器是一个接受 string[] 类型的单个参数的函数。

现在,要使用这个装饰器,我们只需在处理程序上进行注解:

typescript 复制代码
@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

在这里,我们将 Roles 装饰器元数据附加到 create() 方法上,表示只有具有 admin 角色的用户才能访问此路由。

或者,我们可以使用内置的 @SetMetadata() 装饰器,而不是使用 Reflector#createDecorator 方法。Learn more about here

Putting it all together 将所有内容整合在一起

现在让我们回过头来,将这与我们的 RolesGuard 联系起来。目前,它只是在所有情况下返回 true ,允许每个请求继续进行。

我们希望根据当前用户分配的角色与当前处理的路由所需的实际角色进行比较,使返回值成为条件。为了访问路由的角色(自定义元数据),我们将再次使用 Reflector 辅助类,如下所示:

typescript 复制代码
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Roles } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

**提示:**在 node.js 世界中,将授权用户附加到 request 对象是常见做法。因此,在我们上面的示例代码中,我们假设 request.user 包含用户实例和允许的角色。在您的应用程序中,您可能会在自定义身份验证守卫(或中间件)中进行这种关联。有关此主题的更多信息,请参阅本章(this chapter)。
** 警告:**matchRoles() 函数内部的逻辑可以简单或复杂,根据需要而定。这个例子的主要目的是展示守卫如何适应请求/响应周期。

有关在上下文敏感方式下利用 Reflector 的更多详细信息,请参考执行上下文章节的反射和元数据部分(Reflection and metadata)。

当权限不足的用户请求一个端点时,Nest 会自动返回以下响应:

json 复制代码
{
  "statusCode": 403,
  "message": "Forbidden resource",
  "error": "Forbidden"
}

请注意,在幕后,当一个守卫返回 false 时,框架会抛出一个 ForbiddenException 。如果你想返回一个不同的错误响应,你应该抛出你自己的特定异常。例如:

typescript 复制代码
throw new UnauthorizedException();

任何由守卫抛出的异常都将由异常层处理(全局异常过滤器和应用于当前上下文的任何异常过滤器)。

**提示:**如果你正在寻找有关如何实现授权的真实示例,请查看本章(this chapter)。

拦截器

拦截器是一个使用 @Injectable() 装饰器注解的类,并实现 NestInterceptor 接口。

拦截器具有一组有用的功能,受到面向切面编程(AOP)技术的启发。它们使得在方法执行之前/之后绑定额外的逻辑成为可能。

  • 从函数返回的结果进行转换
  • 从函数抛出的异常进行转换
  • 扩展基本函数行为
  • 根据特定条件完全覆盖一个函数(例如用于缓存目的)

Basics 基础

每个拦截器实现 intercept() 方法,该方法接受两个参数。第一个参数是 ExecutionContext 实例(与守卫的对象完全相同)。 ExecutionContext 继承自 ArgumentsHost 。我们在异常过滤器章节中已经见过 ArgumentsHost 。在那里,我们看到它是对传递给原始处理程序的参数的包装器,并且根据应用程序的类型包含不同的参数数组。

Execution context 执行上下文

通过扩展 ArgumentsHost , ExecutionContext 还添加了几个新的辅助方法,提供有关当前执行过程的附加详细信息。这些详细信息对于构建更通用的拦截器,可以在广泛的控制器、方法和执行上下文中使用,非常有帮助。在这里(here)了解更多关于 ExecutionContext 的信息。

Call handler 调用处理程序

第二个参数是一个 CallHandler 。 CallHandler 接口实现了 handle() 方法,您可以使用该方法在拦截器中的某个时刻调用路由处理程序方法。如果您在 intercept() 方法的实现中不调用 handle() 方法,则路由处理程序方法将根本不会被执行。

这种方法意味着 intercept() 方法有效地包装了请求/响应流。因此,您可以在最终路由处理程序执行之前和之后实现自定义逻辑。很明显,您可以在调用 handle() 之前的 intercept() 方法中编写代码,但是如何影响之后发生的事情呢?由于 handle() 方法返回一个 Observable ,我们可以使用强大的 RxJS 操作符进一步操作响应。使用面向方面编程的术语,路由处理程序的调用(即调用 handle() )被称为切入点,表示这是我们插入附加逻辑的点。

考虑一个例子,一个传入的请求。这个请求是发送给在 CatsController 内定义的 create() 处理程序的。如果在任何地方调用了一个不调用 handle() 方法的拦截器,那么 create() 方法将不会被执行。一旦调用了 handle() (并且它的 Observable 已经返回),将触发 create() 处理程序。一旦通过 Observable 接收到响应流,可以对流执行其他操作,并将最终结果返回给调用者。

Aspect interception 切面拦截

我们将首先看一个使用拦截器来记录用户交互的用例(例如,存储用户调用、异步分发事件或计算时间戳)。我们在下面展示一个简单的示例: LoggingInterceptor

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

**提示:**NestInterceptor<T, R> 是一个通用接口,其中 T 表示 Observable 的类型(支持响应流),而 R 是由 Observable 包装的值的类型。
**注意:**拦截器、控制器、提供者、守卫等都可以通过它们的 constructor 来注入依赖项。

由于 handle() 返回一个RxJS Observable ,我们有很多操作符可以用来操作流。在上面的例子中,我们使用了 tap() 操作符,它在可观察流的正常或异常终止时调用我们的匿名日志函数,但不会干扰响应周期。

Binding interceptors 绑定拦截器

为了设置拦截器,我们使用从 @nestjs/common 包导入的 @UseInterceptors() 装饰器。与管道和守卫一样,拦截器可以是控制器范围、方法范围或全局范围。

typescript 复制代码
@UseInterceptors(LoggingInterceptor)
export class CatsController {}

**提示:**装饰器从 @nestjs/common 包中导入。

使用上述结构, CatsController 中定义的每个路由处理程序将使用 LoggingInterceptor 。当有人调用 GET /cats 端点时,您将在标准输出中看到以下输出:

bash 复制代码
Before...
After... 1ms

请注意,我们传递了 LoggingInterceptor 类型(而不是实例),将实例化的责任留给了框架,并启用了依赖注入。与管道、守卫和异常过滤器一样,我们也可以传递一个立即创建的实例:

typescript 复制代码
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}

如上所述,上述构造将拦截器附加到此控制器声明的每个处理程序。如果我们想将拦截器的范围限制在单个方法上,只需在方法级别应用装饰器即可。

为了设置全局拦截器,我们使用 Nest 应用实例的 useGlobalInterceptors() 方法:

typescript 复制代码
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

全局拦截器在整个应用程序中使用,适用于每个控制器和每个路由处理程序。

在依赖注入方面,从任何模块外部注册的全局拦截器(如上面的示例中的 useGlobalInterceptors() )无法注入依赖项,因为这是在任何模块的上下文之外完成的。为了解决这个问题,您可以使用以下结构直接从任何模块设置拦截器:

typescript 复制代码
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}

**提示:**当使用这种方法来执行拦截器的依赖注入时,请注意,无论在哪个模块中使用这种构造方式,拦截器实际上都是全局的。应该在哪里进行这个操作?选择定义拦截器(上面的 LoggingInterceptor )的模块。此外, useClass 并不是处理自定义提供程序注册的唯一方式。在这里( here)了解更多信息。

Response mapping 响应映射

我们已经知道 handle() 返回一个 Observable 。流中包含了路由处理程序返回的值,因此我们可以使用RxJS的 map() 操作符轻松地对其进行变异。

**警告:**响应映射功能与特定库的响应策略不兼容(直接使用 @Res() 对象是被禁止的)。

让我们创建 TransformInterceptor ,它将以微不足道的方式修改每个响应,以展示过程。它将使用 RxJS 的 map() 操作符将响应对象分配给新创建对象的 data 属性,并将新对象返回给客户端。

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(map(data => ({ data })));
  }
}

**提示:**Nest 拦截器可以与同步和异步方法一起使用。如果需要,您可以简单地切换方法。

通过上述的构造,当有人调用 GET /cats 端点时,响应将如下所示(假设路由处理程序返回一个空数组 [] ):

typescript 复制代码
{
  "data": []
}

拦截器在创建可重用解决方案方面具有巨大价值,适用于整个应用程序中出现的需求。例如,假设我们需要将每个 null 值转换为空字符串 '' 。我们可以使用一行代码来实现,并将拦截器全局绑定,以便它会自动被每个注册的处理程序使用。

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(map(value => value === null ? '' : value ));
  }
}

Exception mapping 异常映射

另一个有趣的用例是利用 RxJS 的 catchError() 操作符来覆盖抛出的异常:

typescript 复制代码
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  BadGatewayException,
  CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        catchError(err => throwError(() => new BadGatewayException())),
      );
  }
}

Stream overriding 流覆盖

有几个原因可能会导致我们有时希望完全阻止调用处理程序并返回不同的值。一个明显的例子是实现缓存以提高响应时间。让我们来看一个简单的缓存拦截器,它从缓存中返回其响应。在实际的例子中,我们还需要考虑其他因素,如 TTL、缓存失效、缓存大小等,但这超出了本讨论的范围。在这里,我们将提供一个演示主要概念的基本示例。

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const isCached = true;
    if (isCached) {
      return of([]);
    }
    return next.handle();
  }
}

我们的 CacheInterceptor 有一个硬编码的 isCached 变量和一个硬编码的响应 [] 。需要注意的关键点是,我们在这里返回一个由RxJS of() 操作符创建的新流,因此路由处理程序根本不会被调用。当有人调用使用 CacheInterceptor 的端点时,响应(一个硬编码的空数组)将立即返回。为了创建一个通用的解决方案,您可以利用 Reflector 并创建一个自定义装饰器。在守卫章节中对 Reflector 进行了详细描述。

More operators 更多操作符

使用RxJS操作符来操作流的可能性给了我们很多能力。让我们考虑另一个常见的用例。想象一下,你想要处理路由请求的超时情况。当你的终点在一段时间后没有返回任何内容时,你希望以错误响应终止。以下结构可以实现这一点:

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  };
};

5秒后,请求处理将被取消。您还可以在抛出 RequestTimeoutException 之前添加自定义逻辑(例如释放资源)。

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

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

自定义路由装饰器

Param decorators 参数装饰器

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 世界中,将属性附加到请求对象是常见的做法。然后在每个路由处理程序中手动提取它们,使用类似以下代码的方式:

typescript 复制代码
const user = req.user;

为了使您的代码更易读和透明,您可以创建一个装饰器,并在所有控制器中重复使用它。

typescript 复制代码
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

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

然后,您可以根据需要在任何地方简单地使用它。

typescript 复制代码
@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

Passing data 传递数据

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

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

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

typescript 复制代码
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() 装饰器访问特定属性的方法:

typescript 复制代码
@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}

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

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

Working with pipes 使用管道进行工作

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

typescript 复制代码
@Get()
async findOne(
  @User(new ValidationPipe({ validateCustomDecorators: true }))
  user: UserEntity,
) {
  console.log(user);
}

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

Decorator composition 装饰器组合

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

typescript 复制代码
import { applyDecorators } from '@nestjs/common';

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

您可以按照以下方式使用此自定义 @Auth() 装饰器:

typescript 复制代码
@Get('users')
@Auth('admin')
findAllUsers() {}

这样做的效果是通过一次声明应用所有四个装饰器。

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

相关推荐
Adolf_1993几秒前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
dot.Net安全矩阵8 分钟前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥12 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼15 分钟前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
Hellc00719 分钟前
MacOS升级ruby版本
前端·macos·ruby
工业甲酰苯胺26 分钟前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
前端西瓜哥28 分钟前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG29 分钟前
npm install安装缓慢及npm更换源
前端·npm·node.js
cc蒲公英42 分钟前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
Java开发追求者43 分钟前
在CSS中换行word-break: break-word和 word-break: break-all区别
前端·css·word