NestJS中的Aop面向切面编程。不侵入业务逻辑,高复用通用逻辑。
哈喽哈喽,我是你们的金樽清酒。寒假在家一直在学习nest。现在是已经学完了nest基础部分,开始往进阶学习了。我是通过看神光的文章学习的。基础知识呢又是最重要的。今天呢?我带大家了解一下什么是nestJs中的Aop编程,也叫面向切面编程。
声明:这是看了神光的课学习到的。大家想要更深入的了解可以看他的课。如有侵权行为必删。
什么叫面向切面编程呢?
nestjs采用的是MVC架构。相比于现代前端MVVM不同。
以前我们介绍过controller层是处理参数和路由的。Module层里面的provider是处理业务逻辑的。然后返回view视图。
在后端开发中,经常会有一些通用型的逻辑。比如打印日志呀,权限校验,异常处理。 那这些通用型逻辑写在什么地方呢?写在service里面,或者repository里面不够通过,难道每次都要调用这些方法嘛。那就写在controller里面,写在controller里面也会侵入业务逻辑,复用性不高。为此,我们得跳出业务逻辑,也就是在controller之前或之后进行统一的处理。nestjs提供了一种新的方式,叫AOP,面向切面编程。
什么叫面向切面编程呢?我们来看一下这个图。
在controller的前后,切一刀,来处理通用的逻辑,这就是AOP编程。这样复用性高,也不会侵入业务代码。
AOP编程的几种实现方式
AOP编程的实现方式呢有五种。
这是nestjs的官方的。我们来一一的了解一下吧。
Middware(中间件)
我们可以看到官方的图解,Middleware是发生在controller之前的。它可以获取请求数据和和响应数据。并且有一个next()函数。
我们可以尝试一下Middeware的用法。其实Middleware是express里面的功能。Nest的底层默认不是express嘛,还能切换fastify。
首先我们先创建一个项目。
npx nest new Aop
cd aop
npm run start
安装完之后将项目启动。 访问localhost:3000可以看到成功启动服务。
然后我们在main.ts里面全局启用Middleware。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Response, Request, NextFunction } from 'express';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(function (req: Request, res: Response, next: NextFunction) {
console.log(req.url, 'before');
next();
console.log('after');
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
在路由里面也打印一下
我们再重启一下项目,访问一下页面。
可以看到打印结果。在cotroller之前和之后执行。不侵入业务代码。
在app.controller里面新增两个路由,aaa和bbb,访问localhost:3000/aaa和localhost:3000/bbb 可以看到控制台里面的打印结果。中间件在aaa路由和bbb路由的前后也会触发,说明可以高度服用逻辑。
这是全局使用Middleware,还可以在路由级别使用。 我们用nest cli创建一个路由中间件。
nest g middleware log --no-spec --flat
--no-spec是不生成测试文件。--flat是平铺,不生成文件夹。
我们分析一下怎么实现的。原来是继承实现了NestMiddleware类,用了@Injectable声明说明可以当成依赖注入。可以放到ioc容器中自动创建组装。
我们拿到app.module里面去使用。
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*');
}
}
这样只有aaa开头的路由会生效这个中间件。
Guard
Guard相当于路由守卫,返回true或false来判断用户是否登录,有什么权限。
我们来看看怎么用。
Guards可以访问ExecutionContext实例,因此确切地知道接下来要执行的内容。它们的设计与异常过滤器、管道和拦截器一样,允许您在请求/响应周期的正确点插入处理逻辑,并声明性地这样做。这是官方说的,guard呢必须实现一个CanActivate类。然后根据获取到的ExecutionContext判断是否符合权限,返回true或false确定是否可以启用该路由。
那ExecutionContext里面又能获取到什么信息呢?
我们可以创建一个Guard看一下。
nest g guard login --no-spec --flat
这是生成的guard文件。它实现了canActive方法,里面有个参数ExecutionContext拿到我们想要的信息,进行权限验证,返回true或false决定是否执行这个路由。打印一下checked。
我们在controller里面用一下。
由于我们返回的是true,说明能走这个路由,再看一下打印的信息。
我们可以拿到请求的方式Get,请求的路由’/‘等等。
我们返回false试一下。 再访问这个路由就访问不通啦。
这就是Guard的作用。
Guard这种验证规则肯定要全局应用的。在main.ts里面全局应用这个实例。
这样所有的路由都可以使用了。
当然也可以作为provider全局启用。毕竟使用@Injectable装饰的。可以放进ioc容器,让ioc容器帮我们实例化并组装。
这样也能全局应用。而且可以注入其他的依赖。
Interceptor
Interceptor是拦截器,可以在controller之前或之后执行一些逻辑。
官方说要用@Injectable装饰器装饰,并且实现NestInterceptor接口。 我们来创建一个interceptor。
nest intorceptor
我们可以看到生成的代码实现了NestInterceptore接口。并且有两个参数,一个是上下文信息,跟guard里面是一样的。一个是执行目标路由的函数。我们在里面添加该路由执行时间的代码。并在app.controller里面应用。
然后访问这个路由。
记得把前面全局应用的Guard注释掉,因为我们返回的是false,访问就是404报错了,不会走这个路由。 当然main.ts里面的Guard如果你没注释也要注释掉。
可以看到打印出开来的time。
ofcurse,也能全局应用,也有两种方式。
手动实例化 在module里面声明依赖关系
有时候我们需要对参数进行一些校验转换的一些通用逻辑,这时候要用上另一种AOP,pipe。而且用的特别多。对传入的表单数据进行校验之类的。
Pipe
pipe是管道的意思,就是对参数做一些验证和转换。 我们先创建一个pipe。
这是我们生成的代码。value呢是从路由获取的参数,我们改一下。
对获取的参数value进行判断,如果可以被数字就转成数字乘10,不能就报错。 然后在controller里面写路由,用@Query获取url后面携带的参数。
拿到21的结果。我在url后面是num = 2.显然乘了10倍。
然后一样的可以用于某个参数,也可以用于全局。 手动实例化
还可以在app.module里面声明依赖。
ExceptionFilter
exceptionFilter是用来抛出异常的。像前面我们用pipe的时候打印出来的异常就是它返回的。给出用户友好的错误反馈。
我们创建一个Exception filters。
nest g fliter test --no-spec --flat
用@Catch捕获错误。 对代码进行修改
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,
});
}
}
在handler上使用
可以看到刚才pipe抛出的错误信息发生了变化。
ExceptionFilter可以在controler层使用
也可以全局应用。 手动实例化。
也可以声明模块
各种AOP的执行顺序
我们可以看到,每种AOP有自己的功能,它的执行顺序呀肯定也是按照它的功能来决定的。我们画个图来捋一捋。
先中间件在controller之前可以做一些逻辑,然后是身份校验是否访问路由,请求拦截,对参数进行验证。通过路由处理业务逻辑,响应拦截,对前面抛出的错误进行响应,然后做出响应。
总结
nestjs中的AOP面向切面编程。可以在不改变业务逻辑的情况下,对一些通用逻辑进行一些切面的增删。提高了对通用逻辑的复用,又不影响业务逻辑。