主要讲下 NestJS 中 IOC 和 五种 AOP 的相关知识点,即 Middleware, Guard, Pipe, Interceptor, ExceptionFilter 的使用和概念
Provider 可以先创建下项目
providers 是可以注入的对象,我们可以把带有 @Injectable() 的 class 放到 Module 的 providers 里声明,因为 Nest 实现了 IOC,这样就会被它给识别到,从而实现依赖注入。
typescript 复制
import { Module } from '@nestjs/common' ;
import { AppController } from './app.controller' ;
import { AppService } from './app.service' ;
@ Module ( {
imports : [ ] ,
controllers : [ AppController ] ,
providers : [ AppService ] ,
} )
export class AppModule { }
当然这是一种简写,原本是这样:
typescript 复制
import { Module } from '@nestjs/common' ;
import { AppController } from './app.controller' ;
import { AppService } from './app.service' ;
@ Module ( {
imports : [ ] ,
controllers : [ AppController ] ,
providers : [
{
provide : AppService ,
useClass : AppService ,
} ,
] ,
} )
export class AppModule { }
这样就实现了依赖注入,我们就可以通过 @Inject() 在其他地方使用了,比如在 Controller 里使用
typescript 复制
import { Controller , Get , Inject } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor ( @ Inject ( AppService ) private readonly appService : AppService ) { }
//或者直接写,不用构造器
// @Inject(AppService) private readonly appService: AppService
@ Get ( )
getHello ( ) : string {
return this . appService . getHello ( ) ;
}
}
但因为我们的 provide 和 useClass 是相同的,所以可以省略,所以可以简写成如下:
typescript 复制
import { Controller , Get } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor ( private readonly appService : AppService ) { }
@ Get ( )
getHello ( ) : string {
return this . appService . getHello ( ) ;
}
}
我们可以实验一下,把 provide 的值改为一个字符串
typescript 复制
import { Module } from '@nestjs/common' ;
import { AppController } from './app.controller' ;
import { AppService } from './app.service' ;
@ Module ( {
imports : [ ] ,
controllers : [ AppController ] ,
providers : [
{
provide : 'suemor' ,
useClass : AppService ,
} ,
] ,
} )
export class AppModule { }
这样就没办法省略了,就得老老实实写 @Inject()
typescript 复制
import { Controller , Get , Inject } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor ( @ Inject ( 'suemor' ) private readonly appService : AppService ) { }
@ Get ( )
getHello ( ) : string {
return this . appService . getHello ( ) ;
}
}
除了在 providers 里指定 class,我们也可以指定值或动态的对象,分别对应 useValue 和 useFactory,如下为 useFactory写法
typescript 复制
import { Inject , Module } from '@nestjs/common' ;
import { AppController } from './app.controller' ;
import { AppService } from './app.service' ;
@ Module ( {
imports : [ ] ,
controllers : [ AppController ] ,
providers : [
{
provide : 'suemor' ,
useClass : AppService ,
} ,
{
provide : 'suemor2' ,
useFactory ( appService : AppService ) {
return {
age : 19 ,
gender : 'male' ,
say : appService . getHello ( ) ,
} ;
} ,
inject : [ 'suemor' ] ,
} ,
] ,
} )
export class AppModule { }
修改 controller
typescript 复制
import { Controller , Get , Inject } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor (
@ Inject ( 'suemor' ) private readonly appService : AppService ,
@ Inject ( 'suemor2' )
private readonly user : { age : number ; gender : string ; say : string } ,
) { }
@ Get ( )
getHello ( ) {
return this . user ;
}
}
image-20230531005247226 五种 AOP 因为 NestJS 使用的 MVC 架构,所以有 AOP 的能力,其中 Nest 的实现主要包括如下五种(按执行顺序排列 )
Middleware Guard Interceptor Pipe ExceptionFilte Middleware 即中间件,但这并不是 Nest 独有的,你用 Express 或者 Fastify 当请求的库他们本身也都拥有 middleware,概念基本相同,我们一般会用它处理些通用逻辑(如日志)。
bash 复制
nest g middleware logger --no-spec --flat
应该会生成如下代码,这里的 req 和 res 都是 any 是因为 Nest 并不知道你用的是 Express 还是 Fastify 当请求库,所以类型得自行补全。
typescript 复制
import { Injectable , NestMiddleware } from '@nestjs/common' ;
@ Injectable ( )
export class LoggerMiddleware implements NestMiddleware {
use ( req : any , res : any , next : ( ) => void ) {
next ( ) ;
}
}
修改类型,并添加下 console.log,然后我们得去注册这个 middleware
typescript 复制
import { Injectable , NestMiddleware } from '@nestjs/common' ;
import { Request , Response } from 'express' ;
@ Injectable ( )
export class LoggerMiddleware implements NestMiddleware {
use ( req : Request , res : Response , next : ( ) => void ) {
console . log ( 'start' ) ;
next ( ) ;
}
}
去 app.module.ts里全局注册
typescript 复制
import { MiddlewareConsumer , Module } from '@nestjs/common' ;
import { AppController } from './app.controller' ;
import { AppService } from './app.service' ;
import { NestModule } from '@nestjs/common' ;
import { LoggerMiddleware } from './logger.middleware' ;
@ Module ( {
imports : [ ] ,
controllers : [ AppController ] ,
providers : [
{
provide : 'suemor' ,
useClass : AppService ,
} ,
{
provide : 'suemor2' ,
useFactory ( appService : AppService ) {
return {
age : 19 ,
gender : 'male' ,
say : appService . getHello ( ) ,
} ;
} ,
inject : [ 'suemor' ] ,
} ,
] ,
} )
export class AppModule implements NestModule {
configure ( consumer : MiddlewareConsumer ) {
consumer . apply ( LoggerMiddleware ) . forRoutes ( '*' ) ;
}
}
然后去浏览器访问,我们可以看到控制台正确输出了
image-20230531204334476 Guard 即路由守卫的意思,一般在 Controller 之前鉴权,返回 true 和 false
bash 复制
nest g guard roles --no-spec --flat
typescript 复制
import { CanActivate , ExecutionContext , Injectable } from '@nestjs/common' ;
import { Observable } from 'rxjs' ;
@ Injectable ( )
export class RolesGuard implements CanActivate {
canActivate (
context : ExecutionContext ,
) : boolean | Promise < boolean > | Observable < boolean > {
return true ;
}
}
修改 Controller,UseGuards 和 SetMetadata。
typescript 复制
import {
Controller ,
Get ,
Inject ,
SetMetadata ,
UseGuards ,
} from '@nestjs/common' ;
import { AppService } from './app.service' ;
import { RolesGuard } from './roles.guard' ;
@ Controller ( )
export class AppController {
constructor (
@ Inject ( 'suemor' ) private readonly appService : AppService ,
@ Inject ( 'suemor2' )
private readonly user : { age : number ; gender : string ; say : string } ,
) { }
@ Get ( )
@ UseGuards ( RolesGuard )
@ SetMetadata ( 'roles' , [ 'admin' ] )
getHello ( ) {
return this . appService . getHello ( ) ;
}
}
修改 RolesGuard,这里我们会引 Reflector 的 Metadata 概念,这目前还没有被 ES 标准化,还处于草案阶段,Nest 应该是使用 reflect-metadata 这个 polyfill 包,这可以说是 Nest 的核心了,它的 IOC 基本都是靠这个实现的,这里大意就是从 ExecutationContext 取到 handle,然后注入 reflector,通过 reflector.get 取出 handler 上的 metadata,这样就可以获取到当前路由的权限了。
typescript 复制
import { CanActivate , ExecutionContext , Injectable } from '@nestjs/common' ;
import { Reflector } from '@nestjs/core' ;
import { Observable } from 'rxjs' ;
@ Injectable ( )
export class RolesGuard implements CanActivate {
constructor ( private reflector : Reflector ) { }
canActivate (
context : ExecutionContext ,
) : boolean | Promise < boolean > | Observable < boolean > {
console . log ( this . reflector . get < string [ ] > ( 'roles' , context . getHandler ( ) ) ) ; // [ 'admin' ]
return true ;
}
}
我们现在可以获得当前路由的权限了,那接下来就要用这个与请求的权限进行比较了,关于解析请求的权限要用到 @nestjs/passport库,就是通过 token 获取到当前的 user,但这不是本文重点,我们假设已经在 req 赋上 user 字段了,就可以通过 context.switchToHttp().getRequest().user 获取到当前的请求的 user 了,然后进行 if 判断下就可以了。
typescript 复制
import { CanActivate , ExecutionContext , Injectable } from '@nestjs/common' ;
import { Reflector } from '@nestjs/core' ;
import { Observable } from 'rxjs' ;
@ Injectable ( )
export class RolesGuard implements CanActivate {
constructor ( private reflector : Reflector ) { }
canActivate (
context : ExecutionContext ,
) : boolean | Promise < boolean > | Observable < boolean > {
const routerRole = this . reflector . get < string [ ] > (
'roles' ,
context . getHandler ( ) ,
) ;
const ReqUser = context . switchToHttp ( ) . getRequest ( ) . user ;
console . log ( routerRole , ReqUser ) ;
if ( routerRole [ 0 ] == ReqUser . role ) {
return true ;
}
return false ;
}
}
image-20230531220921807 Interceptor 即拦截器,它可以在 Controller 方法前后加入一些逻辑,其实和 Middleware 挺像的,也可以用来记录日志等,不过它也可以在 Controller 之后进行拦截,比如统一返回结果之类的。
bash 复制
nest g interceptor logging --flat --no-spec
它还有些优势就是可以使用 rxjs 的各种 operator,且也可以使用 reflector 和 ExecutionContext。
typescript 复制
import {
CallHandler ,
ExecutionContext ,
Injectable ,
NestInterceptor ,
} from '@nestjs/common' ;
import { Observable , tap } from 'rxjs' ;
@ Injectable ( )
export class LoggingInterceptor implements NestInterceptor {
intercept ( context : ExecutionContext , next : CallHandler ) : Observable < any > {
console . log ( 'before' ) ;
return next . handle ( ) . pipe ( tap ( ( ) => console . log ( 'after' ) ) ) ;
}
}
然后去 app 注册
typescript 复制
import { NestFactory } from '@nestjs/core' ;
import { AppModule } from './app.module' ;
import { LoggingInterceptor } from './logging.interceptor' ;
async function bootstrap ( ) {
const app = await NestFactory . create ( AppModule ) ;
app . useGlobalInterceptors ( new LoggingInterceptor ( ) ) ;
await app . listen ( 3000 ) ;
}
bootstrap ( ) ;
Pipe 即管道,可以对参数进行校验,比如我们说 age 不能是字符串,就可以这样写。
typescript 复制
import { Controller , Get , Inject , ParseIntPipe , Query } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor ( @ Inject ( 'suemor' ) private readonly appService : AppService ) { }
@ Get ( )
getHello2 ( @ Query ( 'age' , ParseIntPipe ) age : number ) {
return age ;
}
}
但实际业务我们一般会用 class-validator 这个库,它就是利用的 Pipe。
ExceptionFilter 即异常过滤器,它是很强大的,除了 middleware 以外,只要出现异常,都可以被它给捕获到。
typescript 复制
import { ArgumentsHost , ExceptionFilter } from '@nestjs/common' ;
import { Catch , HttpException } from '@nestjs/common' ;
@ Catch ( HttpException )
export class HttpExceptionFilter implements ExceptionFilter {
catch ( exception : HttpException , host : ArgumentsHost ) {
const ctx = host . switchToHttp ( ) ;
const response = ctx . getResponse ( ) ;
const request = ctx . getRequest ( ) ;
const status = exception . getStatus ( ) ;
response . status ( status ) . json ( {
statusCode : status ,
timestamp : new Date ( ) . toISOString ( ) ,
path : request . url ,
} ) ;
}
}
去 app 注册
typescript 复制
import { NestFactory } from '@nestjs/core' ;
import { AppModule } from './app.module' ;
import { HttpExceptionFilter } from './any-exception.filter' ;
async function bootstrap ( ) {
const app = await NestFactory . create ( AppModule ) ;
app . useGlobalFilters ( new HttpExceptionFilter ( ) ) ;
await app . listen ( 3000 ) ;
}
bootstrap ( ) ;
我们 throw 个异常
typescript 复制
import { BadRequestException , Controller , Get , Inject } from '@nestjs/common' ;
import { AppService } from './app.service' ;
@ Controller ( )
export class AppController {
constructor ( @ Inject ( 'suemor' ) private readonly appService : AppService ) { }
@ Get ( )
getHello2 ( ) {
throw new BadRequestException ( ) ;
return this . appService . getHello ( ) ;
}
}
被正确捕获
image-20230531225850021