当前位置: 首页 > news >正文

NestJS 的 拦截器 学习

拦截器会用到RxJs,所以在学习拦截器之前可以先了解一下它。

拦截器是使用@Injectable()装饰器装饰的类并且实现了接口NestInterceptor

拦截器受到 AOP(面向切面编程)技术的启发,具有如下的功能:

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

基础知识

每个拦截器都实现 Intercept() 方法,该方法采用两个参数。第一个参数是ExecutionContext的实例,ExecutionContext继承ArgumentsHost。另外一个参数则是CallHandler,在后面的小节中会介绍。

ExecutionContext

ExecutionContext对象在守卫学习笔记已经做过简要的介绍,我们会在后续的笔记中进行详细说明。

CallHandler 接口说明

CallHandler 接口需要实现两个方法,一个是handle()方法,这个方法可以在拦截器中某个时刻调用路由处理程序的方法。另外一个方法是intercept()方法,这个方法可以对请求/响应流进行包装,因此可以在最终路由处理程序之前和之后实现自定义的逻辑。两个方法的关系是如果在 intercept() 方法的实现中不调用 handle()方法,则根本不会执行路由处理程序方法。

所以很明显,您可以在调用 handle()之前执行的 intercept()方法中编写代码,但是如何影响之后发生的事情呢?因为 handle()方法返回一个 Observable,所以我们可以使用强大的 RxJS 运算符来进一步操作响应。使用面向方面的编程术语,路由处理程序的调用(即调用 handle())称为切入点,表明这是插入我们的附加逻辑的点。

上面我们做了一些概述,我们现在结合例子来说明一下。在提供者笔记中我们有一个 POST /commodity/save的请求接口,这个请求路径是在CommodityController里面进行声明的。假如我们在这个请求路径上绑定拦截器的话,如果在此过程中的任何地方调用了不调用 handle()方法的拦截器,则 create()方法将不会被执行。一旦 handle()被调用(并且它的 Observable 已经返回),create()处理程序将被触发。一旦通过 Observable 接收到响应流,就可以对流执行附加操作,并将最终结果返回给调用者。

切面拦截

通过上述的介绍,我们来看看第一个用例,这个用例主要是使用拦截器来记录用户交互(存储用户调用、异步分派事件或计算时间戳)。具体实例代码如下:

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 指示 an 的类型 Observable(支持响应流),R 是 所包装的值的类型 Observable。

由于 handle()返回 RxJS Observable,我们有多种操作符可供选择来操作流。在上面的示例中,我们使用了 tap()运算符,它在可观察流正常或异常终止时调用我们的匿名日志记录函数,但不会以其他方式干扰响应周期。

绑定拦截器

我们编写好拦截器后,可以使用@UseInterceptors()来绑定拦截器。与管道和守卫一样,拦截器可以是控制器范围、方法范围或全局范围。

@Controller("commodity")
@UseInterceptors(LoggingInterceptor)
export class CommodityController {}

上述的例子中,我们的CommodityController所有路由处理程序都将被LoggingInterceptor进行拦截,当我们请求任何接口后,都可以在控制台看到如下的信息:

Before...
After... 2ms

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

@Controller("commodity")
@UseInterceptors(new LoggingInterceptor())
export class CommodityController {}

全局拦截器设置可以如下:

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

全局范围拦截器会应用于整个应用程序、每个控制器和每个路由。当我们需要在某个模块注入全局范围拦截器的依赖项是不行的,因为全局范围拦截器是在没有上下文环境下完成,所以没有上下文的支持,模块是不能找到对应实例。为了解决这个问题,我们可以用下面的方式来进行拦截器的注入:

import { Module } from "@nestjs/common";
import { APP_INTERCEPTOR } from "@nestjs/core";@Module({providers: [{provide: APP_INTERCEPTOR,useClass: LoggingInterceptor,},],
})
export class AppModule {}

响应映射

我们已经知道 handle()返回一个 Observable. 该流包含从路由处理程序返回的值,因此我们可以使用 RxJS 的 map()运算符轻松更改它。

让我们创建 TransformInterceptor,它将以一种简单的方式修改每个响应来演示该过程。它将使用 RxJS 的 map()运算符将响应对象分配给 data 新创建的对象的属性,并将新对象返回给客户端。具体实例如下:

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 })));}
}
// 路由调用的方法
findAll(): Commodity[] {return this.commoditys;
}

调用上述/commodity/all请求地址后,我们可以看到如下的返回结果:

{"data": [{"id": 1,"username": "123","password": "123"},{"id": 2,"username": "456","password": "456"}]
}

说明:嵌套拦截器可以使用同步和异步 intercept()方法。async 如果需要, 您可以简单地切换该方法。

我们再举一个全局拦截器的例子,假如我们的请求参数可能会有带有 null 的参数,而 null 在程序处理时可能会出现异常,为此我们需要把 null 转为空字符串,这样我们一个一个方法去处理的话不太友好,这样我们就可以使用全局拦截器来处理,具体的例子如下:

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)));}
}

异常拦截器例子

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())));}
}

流覆盖

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

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()路由处理程序。当有人调用使用 的端点时,将立即返回响应(硬编码的空数组)。为了创建通用解决方案,您可以利用并创建自定义装饰器。后续的笔记中会举出一些更详细的例子。

http://www.lryc.cn/news/104895.html

相关文章:

  • Spring AOP 中的代理对象是怎么创建出来的?
  • 解决@Scope(“prototype“)不生效的问题
  • Mybatis 知识点
  • PHP中关于is,between,in等运算符的用法是什么?
  • 2023-07-29:华清远见嵌入式2017年线下班:文件IO笔记
  • 2023年第四届“华数杯”数学建模思路 - 复盘:光照强度计算的优化模型
  • Typescript第七章 处理错误(返回null,抛出异常,返回异常,Option类型)
  • Qt库xcb问题
  • C++ | 哈希表的实现与unordered_set/unordered_map的封装
  • 【漏洞挖掘】Xray+rad自动化批量漏洞挖掘
  • Swagger UI教程 API 文档和Node的使用
  • P5691 [NOI2001] 方程的解数
  • rust里用什么表示字节类型?
  • CMake简介
  • [threejs]相机与坐标
  • Qt信号与槽机制的基石-MOC详解
  • 关于单体架构缓存刷新实现方案
  • 洞悉安全现状,建设网络安全防护新体系
  • spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象
  • 三元组表实现矩阵相加(数据结构)
  • ChinaJoy 2023微星雷鸟17游戏本震撼发布:搭载AMD锐龙9 7945HX首发8499元
  • 各种运算符
  • yolov3-tiny原理解析及代码分析
  • 深入了解Redis-实战篇-短信登录
  • Mysql的锁
  • 【EI/SCOPUS征稿】2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023)
  • Go语言性能优化建议与pprof性能调优详解——结合博客项目实战
  • K阶斐波那契数列(数据结构)
  • 【JavaEE】博客系统前后端交互
  • Redis 简介