Nest.js前端

8、Nest.js 中的拦截器

2018-08-14  本文已影响483人  RoyLin1996

什么是拦截器(Interceptor)?

拦截器就是使用 @Injectable 修饰并且实现了 NestInterceptor 接口的类。
在Nest中拦截器是实现 AOP 编程的利器。

传统 MVC 应用

Nest 默认将控制器处理程序的返回值解析成 JSON(纯字符串不解析),我们如何在 Nest 中实现传统 MVC 程序呢? 即返回一个使用模板引擎渲染的视图。

首先选择一个模板引擎,这里使用的 art-template

chart.png
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);
  
  // 处理静态文件
  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();

目前程序已经可以处理 resource 目录下的静态文件了。
resource 目录是和 src 目录平级的,看起来像下面这样:


image.png

新建一个 home 模块:

image.png

在控制器中拿到 response 对象然后调用它的 render 函数就可以返回渲染后的 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
    
    // 要渲染的数据源
    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 回调函数转成一个 promisify 然后绑定执行的上下文
        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个参数。 第一个是 ExecutionContext 实例它继承自 ArgumentsHost,可以根据上下文的不同, 拿到不同的对象, 如果是 HTTP 请求, 则这个对象中包含 getRequest() 和 getResponse(),如果是 websockets 则包含 getData() 和 getClient()。第二个参数是一个 Observable 流,需要读者有一定的 Rxjs 知识。 Nest 使用 call$ 的 subscribe 结果作为最终的响应。response 的 render 函数第三个参数是一个回调函数,如果不传入 express 会直接响应输出(这样会导致重复设置响应),如果传入了则可以获取到 模板引擎渲染后的 字符串,在这里我们需要将这个回调函数 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 中的看守器

上一篇下一篇

猜你喜欢

热点阅读