NestJs學習之旅(9)——攔截器

歡迎持續關注*NestJs之旅*系列文章html

img

攔截器是一個實現了 NestInterceptor 接口且被 @Injectable 裝飾器修飾的類。typescript

img

攔截器是基於AOP編程思想的一種應用,如下是經常使用的功能:數據庫

  • 在方法執行以前或以後執行額外的邏輯,這些邏輯通常不屬於業務的一部分
  • 轉換函數執行結果
  • 轉換函數執行時拋出的異常
  • 擴展函數基本行爲
  • 特定場景下徹底重寫函數的行爲(好比緩存攔截器,一旦有可用的緩存則直接返回,不執行真正的業務邏輯,即業務邏輯處理函數行爲已經被重寫)

攔截器接口

每一個攔截器都須要實現NestInterceptor接口的**intercept()**方法,該方法接收兩個參數。方法原型以下:express

function intercept(context: ExecutionContext, next: CallHandler): Observable<any> 複製代碼

CallHandler

該接口是對路由處理函數的抽象,接口定義以下:編程

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基礎知識的最後一篇,接下將針對特定模塊進行更新,好比數據庫、上傳、鑑權等等。

因爲直接放出羣二維碼致使加羣門檻極低,近期有閒雜人員掃碼入羣發送廣告/惡意信息,嚴重騷擾羣成員,二維碼入羣通道已關閉。有須要的夥伴能夠關注公衆號來得到加羣資格。

相關文章
相關標籤/搜索