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

NestJS 的 拦截器 学习
拦截器会用到RxJs,所以在学习拦截器之前可以先了解一下它。 拦截器是使用Injectable()装饰器装饰的类并且实现了接口NestInterceptor。 拦截器受到 AOP(面向切面编程)技术的启发,具有如下的功能: 在方法执行之前/之后绑定额外的逻辑转换函…...

Spring AOP 中的代理对象是怎么创建出来的?
文章目录 1. AOP 用法2. 原理分析2.1 doCreateBean2.2 postProcessAfterInitialization2.3 getAdvicesAndAdvisorsForBean2.3.1 findCandidateAdvisors2.3.2 findAdvisorsThatCanApply2.3.3 extendAdvisors 2.4 createProxy 今天和小伙伴们聊一聊 Spring AOP 中的代理对象是怎么…...

解决@Scope(“prototype“)不生效的问题
目录 Scope(“prototype“)不生效Scope(“prototype“)正确用法——解决Bean多例问题 1.问题,Spring管理的某个Bean需要使用多例2.问题升级3. Spring给出的解决问题的办法(解决Bean链中某个Bean需要多例的问题) Scope(“prototype“)不生效 …...

Mybatis 知识点
Mybatis 知识点 1.1 Mybatis 简介 1.1.1 什么是 Mybatis Mybatis 是一款优秀的持久层框架支持定制化 SQL、存储过程及高级映射Mybatis 几乎避免了所有的 JDBC 代码和手动设置参数以及获取结果集MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO…...

PHP中关于is,between,in等运算符的用法是什么?
我们学习了解了这么多关于PHP的知识,不知道你们对PHP中关于is,between,in等运算符的用法是什么?是否已经完全掌握了呢,如果没有,那就跟随本篇文章一起继续学习吧 相关推荐:关于PHP中的增删改如…...

2023-07-29:华清远见嵌入式2017年线下班:文件IO笔记
这里写目录标题 华清远见嵌入式2017年线下班:文件IO笔记文件权限文件IO文件创建和打开操作文件关闭操作出错处理创建设备文件 || create || 老师自己忘了文件读操作练习:计算文件的大小?文件写操作练习:打开file1和file2ÿ…...

2023年第四届“华数杯”数学建模思路 - 复盘:光照强度计算的优化模型
文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米,宽为12米࿰…...

Typescript第七章 处理错误(返回null,抛出异常,返回异常,Option类型)
第七章 处理错误 Typescript竭尽所能,把运行时异常转移到编译时。Typescript是功能丰富的系统,加上强大的静态和符号分析能力,包揽了大量辛苦的工作。 但是有些问题是无法避免的,比如网络和文件系统异常,解析用户输入…...

Qt库xcb问题
首先在~/.bashrc中加入 export QT_DEBUG_PLUGINS1然后看具体的报错 查看某个库链接的库: ldd libqxcb.so然后找到真正缺少的库,再在路径下搜索,然后建立软链接。 https://blog.csdn.net/LOVEmy134611/article/details/107212845 https://…...

C++ | 哈希表的实现与unordered_set/unordered_map的封装
目录 前言 一、哈希 1、哈希的概念 2、哈希函数 (1)直接定址法 (2)除留余数法 (3)平方取中法(了解) (4)随机数法(了解) 3、哈…...

【漏洞挖掘】Xray+rad自动化批量漏洞挖掘
文章目录 前言一、挖掘方法二、使用步骤工具安装使用方法开始挖掘 总结 前言 自动化漏洞挖掘是指利用计算机程序和工具来扫描、分析和检测应用程序、网络和系统中的安全漏洞的过程。这种方法可以帮助安全专家和研究人员更高效地发现和修复潜在的安全威胁,从而提高整…...

Swagger UI教程 API 文档和Node的使用
在团队开发中,一个好的 API 文档可以减少很多交流成本,也可以使一个新人快速上手业务。 前言 swagger ui是一个API在线文档生成和测试的利器,目前发现最好用的。为什么好用?Demo 传送门 支持API自动生成同步的在线文档 这些文档可…...

P5691 [NOI2001] 方程的解数
[NOI2001] 方程的解数 题目描述 已知一个 n n n 元高次方程: ∑ i 1 n k i x i p i 0 \sum\limits_{i1}^n k_ix_i^{p_i} 0 i1∑nkixipi0 其中: x 1 , x 2 , … , x n x_1, x_2, \dots ,x_n x1,x2,…,xn 是未知数, k 1 ,…...

rust里用什么表示字节类型?
在Rust中,字节可以使用 u8 类型来表示。 u8 是一个无符号8位整数类型,可以表示0到255之间的值,对应于一个字节的范围。 以下是一个示例,演示了如何声明和使用字节: fn main() {let byte: u8 65; // 表示字母A的ASCI…...

CMake简介
文章目录 为什么需要头文件为什么 C 需要声明头文件 - 批量插入几行代码的硬核方式头文件进阶 - 递归地使用头文件 CMake什么是编译器多文件编译与链接CMake 的命令行调用为什么需要库(library)CMake 中的静态库与动态库CMake 中的子模块子模块的头文件如…...

[threejs]相机与坐标
搞清相机和坐标的关系在threejs初期很重要,否则有可能会出现写了代码,运行时一片漆黑的现象,这种情况就有可能是因为你相机没弄对。 先来看一下threejs中的坐标(世界坐标) 坐标轴好理解,大家只需要知道在three中不同颜色代表的轴…...

Qt信号与槽机制的基石-MOC详解
引入 上篇讲到了信号与槽就是实现的观察者模式,那具体如何生成映射表就是moc做的事情。 一、moc简介 1. moc的定义 moc 全称是 Meta-Object Compiler,也就是“元对象编译器”,它主要用于处理C源文件中的非标准C代码。Qt 程序在交由标准编…...

关于单体架构缓存刷新实现方案
背景 如果各位看官是分布式项目应该都采用分布式缓存了,例如redis等,分布式缓存不在本次讨论范围哈;我个人建议是,如果是用户量比较大,建议采用分布式缓存机制,后期可以很容易前后到分布式服务或微服务。 …...

洞悉安全现状,建设网络安全防护新体系
一、“网络攻防演练行动“介绍 国家在2016年发布《网络安全法》,出台网络安全攻防演练相关规定:关键信息基础设施的运营者应“制定网络安全事件应急预案,并定期进行演练”。同年“实战化网络攻防演练行动”成为惯例。由公安部牵头࿰…...

spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象
😀前言 本章是spring基于XML 配置bean系类中第4篇讲解spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象 🏠个人主页:尘觉主页 🧑个人简介:大家好,我是尘觉,希望…...

三元组表实现矩阵相加(数据结构)
代码: 含注释,供参考 #include <stdio.h> #include <stdlib.h>typedef struct {int row,col,value;//分别为行数,列数,数值 } Triple; typedef struct {int len;//非零数值的个数Triple data[200]; } TSMatrix;void…...

ChinaJoy 2023微星雷鸟17游戏本震撼发布:搭载AMD锐龙9 7945HX首发8499元
ChinaJoy 2023展会中微星笔记本再次给大家带来惊喜,发布了搭载AMD移动端16大核的旗舰游戏本:雷鸟17,更重要的这样一款旗舰性能的游戏本,首发价8499元堪称当今游戏本市场中的“性价比爆款”! 本着和玩家一同制霸游戏战场…...

各种运算符
算术运算符 1.双目运算符 */%:从左到右优先级依次降低 一些注意事项: 1若a/b都为整型那么结果也为整型,如果ab其中有一个为实型,结果则为实型 求余运算符注意事项: 1运算对象必须为整数 2运算结果的整数跟左边数字的…...

yolov3-tiny原理解析及代码分析
前言 从去年十一月份开始学习yolo神经网络用于目标识别的硬件实现,到现在已经六个月了。一个硬件工程师,C/C基础都差劲的很,对照着darknet作者的源码和网上东拼西凑的原理讲解,一点一点地摸索。刚开始进度很慢,每天都…...

深入了解Redis-实战篇-短信登录
深入了解Redis-实战篇-短信登录 一、故事背景二、知识点主要构成2.1、短信登录2.1.1、生成随机短信验证码引入maven依赖生成验证码 2.1.2、实现登录校验拦截器2.1.3、基于Redis实现短信登录2.1.3.1、发送验证码时存入Redis2.1.3.2、登录时校验验证码 2.1.4、解决状态登录刷新的…...

Mysql的锁
加锁的目的 对数据加锁是为了解决事务的隔离性问题,让事务之前相互不影响,每个事务进行操作的时候都必须先加上一把锁,防止其他事务同时操作数据。 事务的属性 (ACID) 原子性 一致性 隔离性 持久性 事务的隔离级别 锁…...

【EI/SCOPUS征稿】2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023)
2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023) 2023 International Conference on Algorithm, Image Processing and Machine Vision(AIPMV2023) 2023年算法、图像处理与机器视觉国际学术会议(AIPMV2023&am…...

Go语言性能优化建议与pprof性能调优详解——结合博客项目实战
文章目录 性能优化建议Benchmark的使用slice优化预分配内存大内存未释放 map优化字符串处理优化结构体优化atomic包小结 pprof性能调优采集性能数据服务型应用go tool pprof命令项目调优分析修改main.go安装go-wrk命令行交互界面图形化火焰图 性能优化建议 简介: …...

K阶斐波那契数列(数据结构)
代码: 注意k阶斐波那契序列定义:第k和k1项为1,前k - 1项为0,从k项之后每一项都是前k项的和 例如:k2时,斐波那契序列为:0,1,1,2,3,5,8,13... k3时,斐波那契序列为:0,0,…...

【JavaEE】博客系统前后端交互
目录 一、准备工作 二、数据库的表设计 三、封装JDBC数据库操作 1、创建数据表对应的实体类 2、封装增删改查操作 四、前后端交互逻辑的实现 1、博客列表页 1.1、展示博客列表 1.2、博客详情页 1.3、登录页面 1.4、强制要求用户登录,检查用户的登录状态 …...