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

拦截器具有一组有用的功能,受到面向切面编程(AOP)技术的启发。 它们使以下操作成为可能:
- 在方法执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据特定条件(例如,用于缓存目的)完全覆盖函数
基础知识
每个拦截器都实现了 intercept() 方法,
该方法接受两个参数。
第一个是 ExecutionContext 实例(与守卫的对象完全相同)。
ExecutionContext 继承自 ArgumentsHost。
我们在异常过滤器章节中已经看到过 ArgumentsHost。
在那里,我们看到它是对传递给原始处理程序的参数的包装,根据应用程序类型包含不同的参数数组。
您可以回到异常过滤器章节了解更多关于这个主题的信息。
执行上下文
通过扩展 ArgumentsHost,ExecutionContext 还添加了几个新的辅助方法,提供有关当前执行过程的额外详细信息。
这些详细信息对于构建更通用的拦截器,在广泛的控制器、方法和执行上下文中工作,是有帮助的。
在这里了解有关ExecutionContext的更多信息。
调用处理程序
第二个参数是 CallHandler。CallHandler 接口实现了 handle() 方法,
您可以在拦截器的某个点使用它来调用路由处理程序方法。
如果在 intercept() 方法的实现中没有调用 handle() 方法,路由处理程序方法将根本不会被执行。
这种方法意味着 intercept() 方法有效地包装了请求/响应流。
因此,您可以在执行最终路由处理程序之前和之后实现自定义逻辑。
很明显,您可以在 intercept() 方法中编写在调用 handle() 之前执行的代码,
但是如何影响之后发生的事情呢?因为 handle() 方法返回一个 Observable,
我们可以使用强大的 RxJS 操作符来进一步操作响应。使用面向切面编程的术语,
路由处理程序的调用(即调用 handle())被称为切点(Pointcut),
表明这是我们插入附加逻辑的点。
例如,考虑一下传入的 POST /cats 请求。
此请求将发送到 CatsController 内定义的 create() 处理程序。
如果在任何地方调用了不调用 handle() 方法的拦截器,create() 方法将不会被执行。
一旦调用了 handle()(并且其 Observable 已经返回),将触发 create() 处理程序。
并且一旦通过 Observable 收到响应流,就可以在流上执行其他操作,并将最终结果返回给调用方。
切面拦截
我们首先将看一下使用拦截器记录用户交互的用例(例如,存储用户调用、异步调度事件或计算时间戳)。
下面展示了一个简单的 LoggingInterceptor:
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<T>(支持响应流)的类型,
而R是由Observable<R>包装的值的类型。
拦截器,就像控制器、提供者、守卫等一样,可以通过其构造函数注入依赖项。
由于handle()返回一个 RxJS Observable,我们可以选择广泛使用操作符来操作流。
在上面的示例中,我们使用了tap()操作符,它在可观察流的正常或异常终止时调用我们的匿名日志函数,
但不干扰响应周期以外的任何操作。