likes
comments
collection
share

NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

作者站长头像
站长
· 阅读数 23

1.什么是MVC(Model View Controller)

MVC架构下,请求会先发给Controller,然后通过Modle层的Service来调用数据库,完成业务逻辑,最后返回对应的ViewNestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

2.nestAOP(Aspect Oriented Programming: 面向切面编程)

正常的流程是一个请求过来之后,会经过Controller(控制器), Service(服务),Reposity(数据访问): NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

这时如果要在这个正常的流程中加一些通用逻辑放在哪里比较好? 通常都是放在Controller之前或者之后,比如加了一个权限验证的,如果没通过,则Controller后面的流程都进不去。 NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 这就相当于在Controller之前切了一刀,增加了一个步骤。这种透明的加入一些切面逻辑的编程方式就叫做AOP(面向切面编程)

AOP的好处是可以把一些通用的逻辑分离到切面中,保持业务逻辑的纯粹性,这样切面逻辑还可以复用,可以进行动态的增删。

Express的中间件的洋葱模型也是一种AOP的实现,实现方式也是透明的在外面包一层,加入一些逻辑,但是在内层是感知不到的。

Nest实现AOP的方式更多,一共有五种,包含MiddlewareGuardPipeInterceptorExceptionFilter

3.Nest下的AOP方式

  • 中间件 Middleware

    • 全局中间件:全局中间件就是在main.ts中使用app.use使用:

      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      import { AaaGuard } from './aaa/aaa.guard';
      
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.use((req, res, next) => {
          console.log('globalBefor', req.url);
          next();
          console.log('globalAfter');
        });
        await app.listen(3000);
      }
      bootstrap();
      

      服务跑起来后npm run start:dev,刷新浏览器,控制台打印如下信息:

      NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilterController中也加一个日志打印: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 之后再次刷新页面,查看控制台日志打印信息: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到Controller的打印信息操作是在中间件执行的中间。

      为了验证全局中间件可以作用于所有的路由,在Controller中再加两个路由:

      @Get('aaa')
        getA(): string {
          console.log('aaa');
          return 'aaa';
        }
        @Get('bbb')
        getB(): string {
          console.log('bbb');
          return 'bbb';
        }
      

      然后在页面中输入/aaa/bbb的路由,刷新页面: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到,相关路由下的日志都打印了出来,全局中间件的逻辑都执行了。

    • 路由中间件: 首先先生成一个中间件文件:

      // --flat: 新生成的文件平铺
      // --no-spec: 不生成测试文件
      nest g middleware log --flat --no-spec
      

      执行后生成的代码如下 NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilterAppModule中启用该中间件: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter consumer.apply(LogMiddleware).forRoutes('aaa'),这里的aaa就是我们要匹配的路由。

      然后修改中间件的代码,添加信息打印,如下: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 之后我们页面路由输入/aaa,然后刷新页面,可以看到控制台信息打印: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到针对路由/aaa的中间件生效了。

      这就是全局中间件和路由中间件的区别,全局中间件针对的是整个项目的路由,而路由中间件针对的是它所配置的路由

  • Gurad

    Guard是路由守卫的意思,可以用于在调用某个Controller之前判断权限,通过返回turefalse来决定是否放行。 NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    创建一个Guard:

    nest g guard login --no-spec --flat
    

    之后生成一个login.guard.ts文件: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter Guard需要实现CanActivate的接口,还需要实现canActivate的方法,可以通过context拿到请求的信息,然后做一些权限验证的操作,之后返回true或false。 将刚新建的文件处理为返回false,然后在AppController中针对aaa路由启用: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    然后启动项目,访问aaa路由: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到访问aaa路由时直接403了,再访问下没有加Guard的bbb路由: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 此时bbb路由访问时没问题。

    这个是针对路由方式的Guard。和中间件一样,Guard也可以分为路由级别的Guard和全局的Guard,我们接着再全局启用这个login.guard: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 全局启用只需要在main.ts下加上app.useGlobalGuards(new LoginGuard())就可以全局启用这个Guard,此时再访问/路由和/bbb路由,都会返回403: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    另外一种全局启用Guard的方式: 上面是第一种使用全局Guard的方式,这种方式是在main.ts文件中介入。接下来再介绍另外一种全局使用Guard的方式,这种方式是需要在AppModule中来做操作。

    首先注释掉main.ts中的useGlobalGuards NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 然后在app.module.ts文件中添加这个LoginGuard,如下: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 之后访问浏览器,发现/,/bbb,/aaa,路由都是返回403: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到这种全局声明Guard的方式依旧有效。

    而且这种使用provider方式声明的Guard是在IOC容器里的,所以可以将其他的provider注入到这个Guard中,在LoginGuard中注入AppService: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 在浏览器访问之后同样可以看到控制台打印出了this.appService.getHello()返回的内容,这里说明了注入的AppService生效了。

  • Interceptor

    interceptor是拦截器的意思,可以在目标controller方法前后加一些逻辑,流程图如下图: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    首先创建一个interceptor:

    nest g interceptor time --no-spec --flat
    

    生成的interceptor是这样的: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter Interceptor需要实现NestInterceptor接口,实现intercept方法,调用nex.handle()方法就会调用目标Controller,可以在这个Controller之前和之后加入一些处理逻辑。

    Controller之前之后的处理逻辑可能是异步的。Nest里通过rxjs来组织他们,所以可以使用rxjs的各种operator。 NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 将之前全局的LoginGuard注释掉,然后启用我们新添加的interceptor: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    之后页面路由切换到/bbb,刷新页面,可以看到控制台有日志打印: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 这样interceptor就算生效了。

    Interceptor和Middleware很像,但他们是有区别的,主要区别在于参数的不同。 inteceptor可以拿到调用的controller和handler: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    Interceptor的启用方式分三个级别: 1.路由级别:就是上面写的那一种。 2.controller级别:这种启用方式意思就是只对当前的cotroller有用,需要在controller这个类上面调用: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 3.全局级别:调用方式有两种,一种是在module中注入,另外一种就是在main.ts中启用: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

  • Pipe

    Pipe是管道的意思,是用来对参数做一些检验和转换的。 首先使用nestcli创建一个pipe

    nest g pipe validate --no-spec --flat
    

    NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 和上面的Guard和Interceptor一样,Pipe需要实现PipeTransform的接口,同时实现一个transform的方法,里面可以对传入的参数值value做参数验证,比如格式、类型是否正确,如果不正确就可以抛出异常。也可以做转换,返回转换后的值。

    我们来实现一个: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 这里value就是传入的参数,如果不能转成数字,就返回参数错误,否则就乘10再传入handler。

    在AppController添加一个handler,然后应用这个pipe: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 然后在浏览器中访问/ccc路由,并分别传入age=12和age=saa的参数,访问下: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter 可以看到,参数正确的时候会乘10并将值传入handler中,参数错误的时候会返回400响应。这就是pipe作用。 Nest内置了一些Pipea,从名字可以看出来他们的意思:

    • ValidationPipe
    • ParseIntPipe
    • ParseBoolPipe
    • ParseArrayPipe
    • ParseUUIDPipe
    • DefaultValuePipe
    • ParseEnumPipe
    • ParseFloatPipe
    • ParseFilePipe

    和Guard,Interceptor使用方式一样,Pipe也可以针对某一路由使用,也可以对Controler使用,也可以全局使用。 对某一路由生效就是我们上面的那种写法:

    针对Controller生效: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    全局生效: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

    需要注意的是,要针对controller和全局生效时,还需要在路由中,获取对应的参数,如: NestJs之面向切面编程之中间件、Guard、Interceptor、Pipe、ExceptionFilter

  • ExceptionFilter: 待更新