NestJS 核心概念

|
2|
0|
编程

主要讲下 NestJS 中 IOC 和 五种 AOP 的相关知识点,即 Middleware, Guard, Pipe, Interceptor, ExceptionFilter 的使用和概念

Provider

可以先创建下项目

bash
nest new xxx

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,我们也可以指定值或动态的对象,分别对应 useValueuseFactory,如下为 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-20230531005247226image-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-20230531204334476image-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-20230531220921807image-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-20230531225850021image-20230531225850021