前言
🫥首先我们了解下什么是AOP
,AOP
是面向切面编程,是一种编程范式,如果你学习过Express
或者koa
的话你可能能够更加容易理解,因为koa
中的中间件,就是一种AOP
的实现。
一.什么是AOP编程
😈我们知道我们在进行Nest
开发的时候代码之间是进行分层的,比如,Controller
需要从service
获取数据,service
需要从repository
获取数据库的数据,但是如果现在你需要在在项目中增加一个日志打印的功能,或者增加一个权限验证的功能,你该如何进行实现,如果我们在已有的层级中添加代码会导致代码非常的难以维护,为了解决这个问题就在代码执行的过程中向其中动态增加一些中间件,这些中间件可以方便移除,这种实现方式就是AOP--面向切面编程。
二.中间件Middleware
🤡在Express和koa中对AOP的实现叫做中间件
但是在Nest
中AOP
的实现分的更加细致主要分为5种,分别是:Middleware
Guard
Pipe
Interceptor
ExceptionFilter
🐻中间件Middleware
:因为Nest底层默认使用的是Express
所以自然也可以直接使用Express种的功能,中间件分为全局中间件和路由中间件。
- 全局中间件:我们直接像写Express一样直接在全局进行中间件的定义。
然后我们随便请求一个接口,然后查看控制台是否能够打印出下面这些东西。
- 路由中间件:当访问某个路由的时候才会触发这个路由中间件的内容,首先使用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
中也有全局级别的权限控制也有针对某个路由进行的权限控制。
- 全局启用方式
- 另一种全局启用方式,主要的区别在于这种方式通过
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 可以拿到调用的 controller
和 handler
五.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
等,都不仅仅能够在全局使用,而且能够在具体路由进行使用。