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: 待更新

相关推荐
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax