欢迎持续关注*NestJs之旅
*系列文章html
拦截器是一个实现了 NestInterceptor 接口且被 @Injectable 装饰器修饰的类。typescript
拦截器是基于AOP编程思想的一种应用,如下是经常使用的功能:数据库
每一个拦截器都须要实现NestInterceptor接口的**intercept()**方法,该方法接收两个参数。方法原型以下:express
function intercept(context: ExecutionContext, next: CallHandler): Observable<any> 复制代码
该接口是对路由处理函数的抽象,接口定义以下:编程
export interface CallHandler<T = any> {
handle(): Observable<T>;
}
复制代码
handle()函数的返回值也就是对应路由函数的返回值。json
以获取用户列表为例:缓存
// user.controller.ts
@Controller('user')
export class UserController {
@Get()
list() {
return [];
}
}
复制代码
当访问 /user/list 时,路由处理函数返回**[]**,若是在应用拦截器的状况下,调用CallHandler接口的handle()方法获得的也是Observable<[]>(RxJs包装对象)。数据结构
因此,若是在拦截器中调用了next.handle()方法就会执行对应的路由处理函数,若是不调用的话就不会执行。app
随着微服务的兴起,原来的单一项目被拆分红多个比较小的子模块,这些子模块能够独立开发、独立部署、独立运行,大大提升了开发、执行效率,可是带来的问题也比较多,一个常常遇到的问题是接口调用出错很差查找日志。async
若是在业务逻辑中硬编码这种链路调用日志是很是不可取的,严重违反了单一职责的原则,这在微服务开发中是至关很差的一种行为,会让微服务变得臃肿,这些逻辑彻底能够经过拦截器来实现。
// app.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Request } from 'express';
import { format } from 'util';
@Injectable()
export class AppInterceptor implements NestInterceptor {
private readonly logger = new Logger(); // 实例化日志记录器
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now(); // 请求开始时间
return next.handle().pipe(tap((response) => {
// 调用完handle()后获得RxJs响应对象,使用tap能够获得路由函数的返回值
const host = context.switchToHttp();
const request = host.getRequest<Request>();
// 打印请求方法,请求连接,处理时间和响应数据
this.logger.log(format(
'%s %s %dms %s',
request.method,
request.url,
Date.now() - start,
JSON.stringify(response),
));
}));
}
}
复制代码
// user.controller.ts
@UseInterceptors(AppInterceptor)
export class UserController {
@Get()
list() {
return [];
}
}
复制代码
当访问 /user时控制台想输出
[Nest] 96310 - 09/10/2019, 2:44 PM GET /user 1ms []
复制代码
拦截器能够在如下做用域进行绑定:
在main.ts中使用如下代码便可:
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new AppInterceptor());
复制代码
将对该控制器全部路由方法生效:
@Controller('user')
@UseInterceptors(AppInterceptor)
export class UserController {
}
复制代码
只对当前被装饰的路由方法进行拦截:
@Controller('user')
export class UserController {
@UseInterceptors(AppInterceptor)
@Get()
list() {
return [];
}
}
复制代码
CallHandler接口的handle()返回值其实是RxJs的Observable对象,利用RxJs操做符能够对该对象进行操做,好比有一个API接口,以前返回的数据结构以下,若是正常响应,响应体就是数据,没有包装结构:
{
"id":1,
"name":"xialei"
}
复制代码
新的需求是要把以前的纯数据响应包装为一个data属性,结构以下:
{
"data": {
"id": 1,
"name":"xialei"
}
}
复制代码
接到这个需求时有的小伙伴可能已经在梳理响应接口的数量而后评估工时准备进行开发了,而使用NestJs的拦截器,不到一炷香的时间便可实现该需求。
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class AppInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().
pipe(map(data => ({ data }))); // map操做符与Array.prototype.map相似
}
}
复制代码
应用上述拦截器后响应数据就会被包上一层data属性。
另一个有趣的例子是利用RxJs的catchError来覆盖路由处理函数抛出的异常。
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())) // catchError用来捕获异常
);
}
}
复制代码
在文章开始部分提到了拦截器能够重写路由处理函数逻辑。以下是一个缓存拦截器的例子
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private readonly cacheService: CacheService) {}
async intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const host = context.switchToHttp();
const request = host.getRequest();
if(request.method !== 'GET') {
// 非GET请求放行
return next.handle();
}
const cachedData = await this.cacheService.get(request.url);
if(cachedData) {
// 命中缓存,直接放行
return of(cachedData);
}
return next.handle().pipe(tap(response) => {
// 响应数据写入缓存,此处能够等待缓存写入完成,也能够不等待
this.cacheService.set(request.method, response);
});
}
}
复制代码
本文是NestJs基础知识的最后一篇,接下将针对特定模块进行更新,好比数据库、上传、鉴权等等。
因为直接放出群二维码致使加群门槛极低,近期有闲杂人员扫码入群发送广告/恶意信息,严重骚扰群成员,二维码入群通道已关闭。有须要的伙伴能够关注公众号来得到加群资格。