1.什么是MVC(Model View Controller)
?
在MVC
架构下,请求会先发给Controller
,然后通过Modle
层的Service
来调用数据库,完成业务逻辑,最后返回对应的View
。
2.nest
的AOP
(Aspect Oriented Programming
: 面向切面编程)
正常的流程是一个请求过来之后,会经过Controller
(控制器), Service
(服务),Reposity
(数据访问):
这时如果要在这个正常的流程中加一些通用逻辑
放在哪里比较好?
通常都是放在Controller
之前或者之后,比如加了一个权限验证的,如果没通过,则Controller
后面的流程都进不去。 这就相当于在Controller之前切了一刀,增加了一个步骤。这种透明的加入一些切面逻辑的编程方式就叫做AOP(面向切面编程)
。
AOP
的好处是可以把一些通用的逻辑分离到切面中,保持业务逻辑的纯粹性,这样切面逻辑还可以复用,可以进行动态的增删。
Express
的中间件的洋葱模型也是一种AOP
的实现,实现方式也是透明的在外面包一层,加入一些逻辑,但是在内层是感知不到的。
Nest
实现AOP
的方式更多,一共有五种,包含Middleware
、Guard
、Pipe
、Interceptor
、ExceptionFilter
。
3.Nest
下的AOP
方式
中间件 Middleware
:
-
全局中间件:全局中间件就是在main.ts中使用app.use使用:
jsimport { 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
之前判断权限,通过返回ture
或false
来决定是否放行。
创建一个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和全局生效时,还需要在路由中,获取对应的参数,如:
*