首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

拦截器

拦截器是一个用@Injectable()装饰器注释的类。拦截器应该实现NestInterceptor接口。

拦截器具有一系列有用的功能,这些功能受到面向切面编程(AOP)技术的启发。他们可以:

  • 在方法执行之前/之后绑定额外的逻辑
  • 转换函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本功能行为
  • 根据所选条件完全覆盖函数(例如缓存目的)

基本

每个拦截器都有intercept()方法,它带有2个参数。第一个是ExecutionContext实例(与看守器完全相同的对象)。ExecutionContext继承自ArgumentsHost(之前在“异常过滤器”一章中看到过)。ArgumentsHost是传递给原始处理程序的参数的包装器,它根据应用程序的类型在引擎下包含不同的参数数组。

代码语言:javascript
复制
export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}

ArgumentsHost提供了一组有用的方法,帮助我们从底层数组中选择正确的参数。换句话说,ArgumentsHost只不过是一个参数数组。例如,当在HTTP应用程序上下文中使用guard时,ArgumentsHost将包含[request, response]数组。但是,当当前上下文是Web套接字应用程序时,此数组将等于[client, data]。此设计决策使您可以访问最终将传递给相应处理程序的任何参数。

ExecutionContext提供的多一点点。它扩展了ArgumentsHost,而且提供了当前执行过程的更多详细信息。

代码语言:javascript
复制
export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}

所述getHandler()返回一个参考当前处理的处理程序,而getClass()返回的类型的Controller此特定处理程序属于类别。使用换句话说,如果用户指向create()方法被定义和内注册CatsController时,getHandler()将返回一个参考create()方法和getClass()在这种情况下,将简单地返回一个CatsController类型(未实例)。

第二个参数是a call$,一个Observable流。如果不返回此流,则根本不会评估主处理程序。这是什么意思?基本上,这call$是一个推迟最终处理程序执行的流。比方说,有人提出了POST /cats请求。此请求指向在其中create()定义的处理程序CatsController。如果call$沿途调用未返回流的拦截器,则不会create()评估该方法。仅当call$返回流时,才会触发最终方法。为什么?因为Nest 订阅了返回的流,并使用此流生成的值来为最终用户创建单个响应或多个响应。而且,正如刚才所说,call$是一个Observable意思,它为我们提供了一组非常强大的运算符,可以帮助我们进行响应操作。

截取方面

第一个用例是使用拦截器在函数执行之前或之后添加额外的逻辑。当我们要记录与应用程序的交互时,例如存储用户调用,异步调度事件或计算时间戳,这很有用。举个例子,让我们创建一个简单的LoggingInterceptor

logging.interceptor.ts

JS

代码语言:javascript
复制
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

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

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

提示NestInterceptor<T, R>是一个通用接口,其中T指示处理的类型Observable<T>(在流后面),而R返回的值的返回类型返回Observable<R>

注意拦截器的作用与控制器,提供者,警卫等相同,这意味着它们可以通过注入注入依赖关系constructor

由于call$是RxJSObservable,我们可以使用很多各种操作符来操作流。在上面的例子中,我们使用了tap()在可观察序列的正常或异常终止时调用函数的运算符。

为了设置拦截器,我们使用@UseInterceptors()@nestjs/common包中导入的装饰器。与警卫一样,拦截器也可以是控制器作用域,方法作用域和全局作用域。

cats.controller.ts

JS

代码语言:javascript
复制
@UseInterceptors(LoggingInterceptor)
export class CatsController {}

提示@UseInterceptors()装饰器从导入的@nestjs/common包。

多亏了这个,定义的每个路由处理程序都CatsController将使用LoggingInterceptor。当有人调用GET /cats端点时,您将在控制台窗口中看到以下输出:

代码语言:javascript
复制
Before...
After... 1ms

请注意,我们传递的是LoggingInterceptor类型而不是实例,使框架具有实例化责任并启用依赖注入。另一种可用的方法是传递立即创建的实例:

cats.controller.ts

JS

代码语言:javascript
复制
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}

如前所述,上面的构造将拦截器附加到此控制器声明的每个处理程序。如果我们决定只限制其中一个,我们只需要在方法级别设置拦截器。为了绑定全局拦截器,我们使用useGlobalInterceptors()Nest应用程序实例的方法:

代码语言:javascript
复制
const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());

全局拦截器用于整个应用程序,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局拦截器(如上例中所示)不能注入依赖关系,因为它们不属于任何模块。为了解决这个问题,您可以使用以下构造直接从任何模块设置防护:

app.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class ApplicationModule {}
  • 提示替代选项是使用执行上下文功能。此外,useClass这不是处理自定义提供程序注册的唯一方法。在这里了解更多。

响应映射

我们已经知道这call$是一个Observable。该对象包含从路由处理程序返回的值,因此我们可以使用map()运算符轻松改变它。

警告响应映射功能不适用于特定于库的响应策略(@Res()禁止直接使用该对象)。

让我们创建TransformInterceptor将通过分配data新创建的对象的属性来获取每个响应并对其进行修改。

transform.interceptor.ts

JS

代码语言:javascript
复制
import { Injectable, NestInterceptor, ExecutionContext } 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,
    call$: Observable<T>,
  ): Observable<Response<T>> {
    return call$.pipe(map(data => ({ data })));
  }
}

HINTNest拦截器就像使用异步intercept()方法的魅力一样,这意味着,async如果需要,您可以毫不费力地切换方法。

之后,当有人调用GET /cats端点时,请求将如下所示(我们假设路由处理程序返回一个空数组[]):

代码语言:javascript
复制
{
    "data": []
}

在创建整个应用程序中使用的可重用解决方案时,拦截器具有巨大的潜力。例如,让我们假设我们需要将每个发生的null值转换为空字符串''。我们可以使用一行代码并将拦截器绑定为全局代码。多亏了这一点,每个注册的处理程序都会自动重用它。

JS

代码语言:javascript
复制
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

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

异常映射

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

errors.interceptor.ts

JS

代码语言:javascript
复制
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  HttpStatus,
} from '@nestjs/common';
import { HttpException } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    call$: Observable<any>,
  ): Observable<any> {
    return call$.pipe(
      catchError(err =>
        throwError(new HttpException('Message', HttpStatus.BAD_GATEWAY)),
      ),
    );
  }
}

流重写

有时我们可能希望完全阻止调用处理程序并返回不同的值(例如,由于性能问题而从缓存中),有几个原因。一个很好的例子是缓存拦截器,它将使用一些TTL存储缓存的响应。不幸的是,这个功能需要更多的代码,并且由于简化,我们将仅提供一个应该简要解释主要概念的基本示例。

cache.interceptor.ts

JS

代码语言:javascript
复制
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { of } from 'rxjs/observable/of';

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

这里CacheInterceptor有一个硬编码isCached变量和硬编码响应[]。我们在这里返回了一个通过of运算符创建的新流,因此根本不会调用路由处理程序。当有人调用使用的端点时CacheInterceptor,响应(硬编码的空数组)将立即返回。为了创建通用解决方案,您可以利用Reflector并创建自定义装饰器。在Reflector很好的描述警卫章。

返回流的可能性为我们提供了许多可能性。让我们考虑另一个常见的用例。想象一下,你想要处理超时。当您的端点在一段时间后没有返回任何内容时,我们希望以错误响应进行响应。

timeout.interceptor.ts

JS

代码语言:javascript
复制
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    call$: Observable<any>,
  ): Observable<any> {
    return call$.pipe(timeout(5000))
  }
}

5秒后,请求处理将被取消。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com