当前位置: 首页 > 后端技术 > Node.js

NestJs学习之旅(九)——拦截器

时间:2023-04-03 13:25:04 Node.js

欢迎继续关注NestJs之旅系列文章Interceptor是一个实现了NestInterceptor接口的类,使用@Injectable装饰器进行装饰。拦截器是基于AOP编程思想的应用。以下是常用函数:在方法执行之前或之后执行附加逻辑。这些逻辑一般不属于业务的一部分。转换函数执行结果。转换函数执行期间抛出的异常。Extension函数的基本行为完全重写了函数在特定场景下的行为(比如缓存拦截器,一旦有可用的缓存就直接返回,不执行真正的业务逻辑,即业务逻辑处理函数已被重写)。每个拦截器接口所有的拦截器都需要实现NestInterceptor接口的intercept()方法,它接收两个参数。方法原型如下:functionintercept(context:ExecutionContext,next:CallHandler):ObservableExecutionContext执行上下文,与NestJs学习之旅(七)中的执行上下文相同——CallHandler路由处理函数CallHandler这个interface是对路由处理函数的抽象,接口定义如下:exportinterfaceCallHandler{handle():Observable;}handle()函数的返回值也是return相应路由函数的值。以获取用户列表为例://user.controller.ts@Controller('user')exportclassUserController{@Get()list(){return[];}}访问/user/list时,路由处理函数Return[],如果应用了拦截器,则调用CallHandler接口的handle()方法获取Observable<[]>(RxJswrapperobject)。因此,如果在拦截器中调用了next.handle()方法,就会执行相应的路由处理函数,如果没有调用,则不会执行。一个请求链接日志拦截器随着微服务的兴起,将原来单一的项目拆分成多个更小的子模块,可以独立开发、独立部署、独立运行,大大提高了开发和执行效率,但也带来了很多问题。一个普遍的问题是接口调用出错时很难找到日志。将这种链接调用日志硬编码在业务逻辑中是非常不可取的,严重违背了单一职责原则。这是微服务开发中非常糟糕的行为,会让微服务变得臃肿。这些逻辑完全可以通过拦截器来实现。//app.interceptor.tsimport{CallHandler,ExecutionContext,Injectable,Logger,NestInterceptor}from'@nestjs/common';import{Observable}from'rxjs';import{tap}from'rxjs/operators';import{Request}从'express';import{format}from'util';@Injectable()exportclassAppInterceptorimplementsNestInterceptor{privatereadonlylogger=newLogger();//实例化记录器intercept(context:ExecutionContext,next:CallHandler):Observable{conststart=Date.now();//请求开始时间returnnext.handle().pipe(tap((response)=>{//调用handle()后获取RxJs响应对象,使用tap获取路由函数的返回值consthost=context.switchToHttp();constrequest=host.getRequest();//打印请求方法、请求链接、处理时间和响应数据this.logger.log(format('%s%s%dms%s',request.method,request.url,Date.now()-开始,JSON.stringify(response),));}));}}//user.controller.ts@UseInterceptors(AppInterceptor)exportclassUserController{@Get()list(){return[];}}访问/user时,控制台要输出[Nest]96310-09/10/2019,2:44PMGET/user1ms[]InterceptorScope拦截器可以在以下范围绑定:globalinterceptorcontrollerinterceptorroutingmethodinterceptor全局拦截器只需在main.ts中使用以下代码:constapp=awaitNestFactory.create(AppModule);app.useGlobalInterceptors(newAppInterceptor());controller拦截器会对controller的所有路由方法生效:@Controller('user')@UseInterceptors(AppInterceptor)exportclassUserController{}路由方法拦截器只针对当前装饰的路由方法拦截:@Controller('user')exportclassUserController{@UseInterceptors(AppInterceptor)@Get()list(){return[];}}响应处理CallHandler接口的handle()返回值实际上是RxJs的Observable对象,可以使用RxJs操作符对其进行操作。比如有一个API接口。之前返回的数据结构如下。如果响应正常,响应体就是数据,没有封装结构:{"id":1,"name":"xialei"}新需求是将之前的纯数据响应封装成一个数据属性,结构如下:{"data":{"id":1,"name":"xialei"}}收到这个需求时不时有些小伙伴可能已经在整理响应接口的数量和然后评估工时以准备开发。但是,使用NestJs的拦截器,这个需求一炷香就可以实现。从'@nestjs/common'导入{CallHandler,ExecutionContext,Injectable,Logger,NestInterceptor};从'rxjs'导入{Observable};从'rxjs/operators'导入{map};@Injectable()导出类AppInterceptor实现NestInterceptor{拦截(上下文:ExecutionContext,下一个:CallHandler):Observable{returnnext.handle().pipe(map(data=>({data})));//mapoperator类似于Array.prototype.map}}应用上面的拦截器后,响应数据会被一层数据属性包裹起来。异常映射的另一个有趣示例是使用RxJscatchError来覆盖路由处理程序抛出的异常。import{Injectable,NestInterceptor,ExecutionContext,BadGatewayException,CallHandler,}from'@nestjs/common';import{Observable,throwError}from'rxjs';import{catchError}from'rxjs/operators';@Injectable()exportclassErrorsInterceptorimplementsNestInterceptor{intercept(context:ExecutionContext,next:CallHandler):Observable{returnnext.handle().pipe(catchError(err=>throwError(newBadGatewayException()))//catchError用于捕获异常);}}重写路由函数逻辑文章开头提到拦截器可以重写路由处理函数逻辑。下面是一个缓存拦截器的例子privatereadonlycacheService:CacheService){}asyncintercept(context:ExecutionContext,next:CallHandler):Observable{consthost=context.switchToHttp();constrequest=host.getRequest();if(request.method!=='GET'){//非GET请求被释放returnnext.handle();}constcachedData=awaitthis.cacheService.get(request.url);if(cachedData){//命中缓存,直接释放returnof(cachedData);}returnnext.handle().pipe(tap(response)=>{//响应数据写入缓存,这里可以等待缓存写入完成,也可以不等待this.cacheService.set(request.method,response);});}}结束本文是NestJs基础知识的最后一篇,后续会针对数据库、上传、鉴权等具体模块进行更新。由于直接放出群二维码,入群门槛极低。近期微商等人扫描二维码进群发广告/恶意消息,严重骚扰群成员,已关闭群二维码访问权限。有需要的小伙伴可以关注公众号获取入群资格。