Nest中的AOP面向切面编程

前言


🫥首先我们了解下什么是AOPAOP是面向切面编程,是一种编程范式,如果你学习过Express或者koa的话你可能能够更加容易理解,因为koa中的中间件,就是一种AOP的实现。

一.什么是AOP编程


😈我们知道我们在进行Nest开发的时候代码之间是进行分层的,比如,Controller需要从service获取数据,service需要从repository获取数据库的数据,但是如果现在你需要在在项目中增加一个日志打印的功能,或者增加一个权限验证的功能,你该如何进行实现,如果我们在已有的层级中添加代码会导致代码非常的难以维护,为了解决这个问题就在代码执行的过程中向其中动态增加一些中间件,这些中间件可以方便移除,这种实现方式就是AOP--面向切面编程。

二.中间件Middleware


🤡在Express和koa中对AOP的实现叫做中间件但是在NestAOP的实现分的更加细致主要分为5种,分别是:Middleware Guard Pipe Interceptor ExceptionFilter

🐻中间件Middleware:因为Nest底层默认使用的是Express所以自然也可以直接使用Express种的功能,中间件分为全局中间件和路由中间件。

  1. 全局中间件:我们直接像写Express一样直接在全局进行中间件的定义。

然后我们随便请求一个接口,然后查看控制台是否能够打印出下面这些东西。


  1. 路由中间件:当访问某个路由的时候才会触发这个路由中间件的内容,首先使用Nest生成一个路由中间件。
ts 复制代码
nest g middleware log --no-spec --flat

生成的代码内容如下:

目录的内容如下:--no-spec 是不生成测试文件,--flat 是平铺,不生成目录。

然后我们在AppModule中使用这个中间件,然后在configure中进行配置在哪些路由中生效。

ts 复制代码
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LogMiddleware } from './log.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule{

  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LogMiddleware).forRoutes('aaa*');
  }

}

三.Guard


😈Guard是守卫的意思,主要是在Controller之前进行某些判断,是否进行放行,如果为true就放行,如果为false就不放行(此图来源《Nest通关秘籍》)

我们直接使用命令来生成一个Guard文档

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

生成如下代码内容,Guard 要实现 CanActivate 接口,实现 canActivate 方法,可以从 context 拿到请求的信息,然后做一些权限验证等处理之后返回 true 或者 false。

ts 复制代码
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class LoginGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

🥱我们进行打印一个数据然后直接返回fasle然后看下结果的内容。

ts 复制代码
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
  export class LoginGuard implements CanActivate {
    canActivate(
      context: ExecutionContext,
    ): boolean | Promise<boolean> | Observable<boolean> {
      console.log('check login');
      return false;
    }
  }

我们直接在AppController中进行使用,然后请求下某个接口看下效果。

🫥看后我们看到了这个接口被触发了,但是返回的结果是如下的内容,因为我们直接在Guard中返回了false所以相当于Cotroller并没有什么改动但是却直接通过一个装饰器就加上了一个权限的判断。


👹我们知道中间件是包含全局级别的中间件和路由级别的中间件,当然Guard中也有全局级别的权限控制也有针对某个路由进行的权限控制。

  1. 全局启用方式
  1. 另一种全局启用方式,主要的区别在于这种方式通过Provider方式会在IOC容器里面,能够注入到其他的provider上面那种不在IOC容器中。

四.Interceptor拦截器


😀interceptor是拦截器的意思可以在Controller前后增加一些逻辑

通过下面这种方式创建一个例子。

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

我们会看到生成了如下这个内容。

我们使用RXJS中的Operator操作符进行组织如下代码

ts 复制代码
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class TimeInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    const startTime = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log('time: ', Date.now() - startTime)
      })
    );
  }
}

我们在AppController中进行注册使用

😣我们可以看到这个被触发了,是不是和中间件很相似?其实是不一样的,interceptor 可以拿到调用的 controllerhandler

五.Pipe管道


😋我们依然像上述那样来创建Pipe并且实现 transform 方法,里面可以对传入的参数值 value 做参数验证,比如格式、类型是否正确,不正确就抛出异常。

ts 复制代码
nest g pipe validate --no-spec --flat
ts 复制代码
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ValidatePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {

    if(Number.isNaN(parseInt(value))) {
      throw new BadRequestException(`参数${metadata.data}错误`)
    }

    return typeof value === 'number' ? value * 10 : parseInt(value) * 10;
  }
}

😈在AppController中创建一个方法,然后访问下这个接口。

less 复制代码
  @Get('ccc')
  ccc(@Query('num', ValidatePipe) num: number) {
    return num + 1;
  }

🤣那么如果我们传入英文哪?我们看到进行了参数的验证,报错!

🐻其实在Nest中也有很多内置的Pipe我们可以直接进行使用

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

六.ExceptionFilter


🫥ExceptionFilter可以对抛出的异常进行处理,刚才的内容能够返回400就是Exception Filter做的。

😎我们像上述内容一样创建一个文件

ts 复制代码
nest g filter test --no-spec --flat

我们实现下如下代码:

ts 复制代码
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
import { Response } from 'express';

@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {

    const response: Response = host.switchToHttp().getResponse();

    response.status(400).json({
      statusCode: 400,
      message: 'test: ' + exception.message
    })
  }
}

我们在AppController中引用下

再次进行访问错误内容就会变成如下:

Nest 内置了很多 http 相关的异常,都是 HttpException 的子类:

BadRequestException

UnauthorizedException

NotFoundException

ForbiddenException

NotAcceptableException

RequestTimeoutException

ConflictException

GoneException

PayloadTooLargeException

UnsupportedMediaTypeException

UnprocessableException

InternalServerErrorException

NotImplementedException

BadGatewayException

ServiceUnavailableException

GatewayTimeoutException

🥱并且不仅仅可以用于某个路由还可以进行全局的使用

七.AOP的调用顺序


八.总结


😣Nest中不仅仅实现了IOC并且还是基于AOP架构的,这种架构的好处就是可以透明的增加某些权限和限制,由于NEST底层是Express也拥有了中间件的能力,在Nest中提供了5种,分别是Middleware Guard Pipe Interceptor ExceptionFilter等,都不仅仅能够在全局使用,而且能够在具体路由进行使用。

相关推荐
Victor3568 分钟前
Redis(14)Redis的列表(List)类型有哪些常用命令?
后端
Victor3568 分钟前
Redis(15)Redis的集合(Set)类型有哪些常用命令?
后端
卷福同学9 分钟前
来上海三个月,我在马路边上遇到了阿里前同事...
java·后端
bobz9659 小时前
小语言模型是真正的未来
后端
DevYK9 小时前
企业级 Agent 开发实战(一) LangGraph 快速入门
后端·llm·agent
一只叫煤球的猫10 小时前
🕰 一个案例带你彻底搞懂延迟双删
java·后端·面试
冒泡的肥皂10 小时前
MVCC初学demo(一
数据库·后端·mysql
颜如玉11 小时前
ElasticSearch关键参数备忘
后端·elasticsearch·搜索引擎
卡拉叽里呱啦12 小时前
缓存-变更事件捕捉、更新策略、本地缓存和热key问题
分布式·后端·缓存
David爱编程12 小时前
线程调度策略详解:时间片轮转 vs 优先级机制,面试常考!
java·后端