什么是攔截器(Interceptor)夯辖?
攔截器就是使用 @Injectable 修飾并且實現(xiàn)了 NestInterceptor 接口的類琉预。
在Nest中攔截器是實現(xiàn) AOP 編程的利器。
傳統(tǒng) MVC 應用
Nest 默認將控制器處理程序的返回值解析成 JSON(純字符串不解析)蒿褂,我們?nèi)绾卧?Nest 中實現(xiàn)傳統(tǒng) MVC 程序呢圆米? 即返回一個使用模板引擎渲染的視圖卒暂。
首先選擇一個模板引擎,這里使用的 art-template:
npm install --save art-template
npm install --save express-art-template
修改我們的入口程序:
src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from 'app.module';
import { HttpExceptionFilter } from 'common/filters/http-exception.filter';
import { ApiParamsValidationPipe } from 'common/pipes/api-params-validation.pipe';
import { static as resource } from 'express';
import * as art from 'express-art-template';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 處理靜態(tài)文件
app.use('/static', resource('resource'));
// 指定模板引擎
app.engine('art', art);
// 設置模板引擎的配置項
app.set('view options', {
debug: process.env.NODE_ENV !== 'production',
minimize: true,
rules: [
{ test: /<%(#?)((?:==|=#|[=-])?)[ \t]*([\w\W]*?)[ \t]*(-?)%>/ },
{ test: /{%([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*%}/ }
]
});
// 設置視圖文件的所在目錄
app.setBaseViewsDir('resource/views');
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalPipes(new ApiParamsValidationPipe());
await app.listen(3000);
}
bootstrap();
目前程序已經(jīng)可以處理 resource 目錄下的靜態(tài)文件了娄帖。
resource 目錄是和 src 目錄平級的也祠,看起來像下面這樣:
新建一個 home 模塊:
在控制器中拿到 response 對象然后調(diào)用它的 render 函數(shù)就可以返回渲染后的 html:
src/home/home.controller.ts
import { Controller, Get, Res } from '@nestjs/common';
@Controller()
export class HomeController {
@Get()
index(@Res() res) {
return res.render('home/home.art');
}
}
這樣做太丑陋了,我們的思路是近速,只要控制器返回的是一個 View 類型诈嘿,則渲染視圖,否則使用默認的解析邏輯削葱。
新建一個 View.ts:
src/common/libs/View.ts
export class View {
// 視圖的名稱
public name: string
// 要渲染的數(shù)據(jù)源
public data: any
constructor(name: string, data: any = {}) {
this.name = name;
this.data = data;
}
}
HomeController 中就是很簡單的返回一個 View 的實例:
src/home/home.controller.ts
import { Controller, Get } from '@nestjs/common';
import { View } from 'common/libs/view';
@Controller()
export class HomeController {
@Get()
index(): View {
// 返回首頁的視圖
return new View('home/home.art');
}
}
攔截器登場
src/common/Interceptors/view.interceptor.ts
import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as util from 'util';
import { View } from '../libs/view';
@Injectable()
export class ViewInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// 拿到 response 對象
const response = context.switchToHttp().getResponse();
// 將 render 回調(diào)函數(shù)轉成一個 promisify 然后綁定執(zhí)行的上下文
const render = util.promisify(response.render.bind(response));
// 請自行了解什么是 Rxjs
return call$.pipe(map(async value => {
if (value instanceof View) {
// 返回渲染后的 html
value = await render(value.name, value.data);
}
return value;
}))
}
}
這里對于一些基礎知識如 promisify奖亚, bind 等不做介紹了。每個攔截器都有 intercept() 方法析砸,這個方法有2個參數(shù)昔字。 第一個是 ExecutionContext 實例它繼承自 ArgumentsHost,可以根據(jù)上下文的不同首繁, 拿到不同的對象李滴, 如果是 HTTP 請求, 則這個對象中包含 getRequest() 和 getResponse()蛮瞄,如果是 websockets 則包含 getData() 和 getClient()所坯。第二個參數(shù)是一個 Observable 流,需要讀者有一定的 Rxjs 知識挂捅。 Nest 使用 call$ 的 subscribe 結果作為最終的響應芹助。response 的 render 函數(shù)第三個參數(shù)是一個回調(diào)函數(shù),如果不傳入 express 會直接響應輸出(這樣會導致重復設置響應)闲先,如果傳入了則可以獲取到 模板引擎渲染后的 字符串状土,在這里我們需要將這個回調(diào)函數(shù) promisify 化,拿到響應然后使用 map 操作符改變流的結果伺糠,最終的響應就是模板引擎渲染后的字符串蒙谓。
最后
只在 home 模塊中使用 View 攔截器:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ViewInterceptor } from 'common/interceptors/view.interceptor';
import { HomeController } from './home.controller';
@Module({
controllers: [HomeController],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: ViewInterceptor,
},
]
})
export class HomeModule {}
同理 異常過濾器、管道 等等 也可以只作用在特定模塊上训桶,使用不同的常量就可以了累驮。
這里使用的是 APP_INTERCEPTOR 來標識提供者是一個 攔截器,如果是管道則用 APP_PIPE舵揭。
使用攔截器還可以做更多的事情谤专,例如:記錄日志、 返回緩存 等等午绳。
上一篇:7置侍、Nest.js 中的類驗證器
下一篇:9、Nest.js 中的看守器