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

1.什么是MVC(Model View Controller)

MVC架构下,请求会先发给Controller,然后通过Modle层的Service来调用数据库,完成业务逻辑,最后返回对应的View

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

正常的流程是一个请求过来之后,会经过Controller(控制器), Service(服务),Reposity(数据访问):

这时如果要在这个正常的流程中加一些通用逻辑放在哪里比较好?

通常都是放在Controller之前或者之后,比如加了一个权限验证的,如果没通过,则Controller后面的流程都进不去。 这就相当于在Controller之前切了一刀,增加了一个步骤。这种透明的加入一些切面逻辑的编程方式就叫做AOP(面向切面编程)

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

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

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

3.Nest下的AOP方式

中间件 Middleware

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

    js 复制代码
    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,刷新浏览器,控制台打印如下信息:

    Controller中也加一个日志打印:
    之后再次刷新页面,查看控制台日志打印信息:
    可以看到Controller的打印信息操作是在中间件执行的中间。

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

    js 复制代码
    @Get('aaa')
      getA(): string {
        console.log('aaa');
        return 'aaa';
      }
      @Get('bbb')
      getB(): string {
        console.log('bbb');
        return 'bbb';
      }

    然后在页面中输入/aaa/bbb的路由,刷新页面: 可以看到,相关路由下的日志都打印了出来,全局中间件的逻辑都执行了。

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

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

    执行后生成的代码如下
    AppModule中启用该中间件:
    consumer.apply(LogMiddleware).forRoutes('aaa'),这里的aaa就是我们要匹配的路由。

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

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

Gurad

Guard是路由守卫的意思,可以用于在调用某个Controller之前判断权限,通过返回turefalse来决定是否放行。

创建一个Guard:

js 复制代码
nest g guard login --no-spec --flat

之后生成一个login.guard.ts文件: Guard需要实现CanActivate的接口,还需要实现canActivate的方法,可以通过context拿到请求的信息,然后做一些权限验证的操作,之后返回true或false。

将刚新建的文件处理为返回false,然后在AppController中针对aaa路由启用:

然后启动项目,访问aaa路由:

可以看到访问aaa路由时直接403了,再访问下没有加Guard的bbb路由:

此时bbb路由访问时没问题。

这个是针对路由方式的Guard。和中间件一样,Guard也可以分为路由级别的Guard和全局的Guard,我们接着再全局启用这个login.guard:

全局启用只需要在main.ts下加上app.useGlobalGuards(new LoginGuard())就可以全局启用这个Guard,此时再访问/路由和/bbb路由,都会返回403:

另外一种全局启用Guard的方式

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

首先注释掉main.ts中的useGlobalGuards

然后在app.module.ts文件中添加这个LoginGuard,如下: 之后访问浏览器,发现/,/bbb,/aaa,路由都是返回403: 可以看到这种全局声明Guard的方式依旧有效。

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

Interceptor

interceptor是拦截器的意思,可以在目标controller方法前后加一些逻辑,流程图如下图:

首先创建一个interceptor:

js 复制代码
nest g interceptor time --no-spec --flat

生成的interceptor是这样的: Interceptor需要实现NestInterceptor接口,实现intercept方法,调用nex.handle()方法就会调用目标Controller,可以在这个Controller之前和之后加入一些处理逻辑。

Controller之前之后的处理逻辑可能是异步的。Nest里通过rxjs来组织他们,所以可以使用rxjs的各种operator。

将之前全局的LoginGuard注释掉,然后启用我们新添加的interceptor:

之后页面路由切换到/bbb,刷新页面,可以看到控制台有日志打印: 这样interceptor就算生效了。

Interceptor和Middleware很像,但他们是有区别的,主要区别在于参数的不同。

inteceptor可以拿到调用的controller和handler:

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

Pipe

Pipe是管道的意思,是用来对参数做一些检验和转换的。

首先使用nestcli创建一个pipe

js 复制代码
nest g pipe validate --no-spec --flat

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

我们来实现一个:

这里value就是传入的参数,如果不能转成数字,就返回参数错误,否则就乘10再传入handler。

在AppController添加一个handler,然后应用这个pipe: 然后在浏览器中访问/ccc路由,并分别传入age=12和age=saa的参数,访问下:

可以看到,参数正确的时候会乘10并将值传入handler中,参数错误的时候会返回400响应。这就是pipe作用。

Nest内置了一些Pipea,从名字可以看出来他们的意思:

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

和Guard,Interceptor使用方式一样,Pipe也可以针对某一路由使用,也可以对Controler使用,也可以全局使用。

对某一路由生效就是我们上面的那种写法:

针对Controller生效:

全局生效:

需要注意的是,要针对controller和全局生效时,还需要在路由中,获取对应的参数,如:
*

ExceptionFilter: 待更新

相关推荐
小政爱学习!16 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。21 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼28 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093331 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石2 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程2 小时前
【前端基础】CSS基础
前端·css