Fork me on GitHub

nestjs入门学习总结(二):中间件、异常过滤器、守卫、管道、拦截器

中间件

Nest 中间件可以是一个函数,也可以是一个带有 @Injectable() 装饰器的类,且该类应该实现 NestMiddleware 接口,而函数没有任何特殊要求。

如下是一个日志中间件的简单示例:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('Request...');
    next();
  }
}
  1. 中间件中的依赖注入

与提供者(Provider)和控制器(Controller)一样,他能够通过构造函数注入属于同一模块的依赖项:

import { Injectable, Inject, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class SomeMiddleware implements NestMiddleware {
  constructor(@Inject(SomeService) private readonly someService: SomeService) {}

  use(req: Request, res: Response, next: Function) {
    // do some logic...
    this.someService.method();

    console.log('Request...');
    next();
  }
}
  1. 使用中间件

中间件是请求发出者和路由处理器之间的桥梁,我们只需要在模块类中实现 NestModule 接口:

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

在上面的例子中,我们为 /cats 路由处理器(@CatsController('/cats'))设置了日志中间件。
如果只需要给 /cats 路由中的某几个请求方法设置这个中间件,那只需要改变一下 forRoutes() 方法中的参数即可:forRoutes({ path: 'cats', method: RequestMethod.GET }),此时,只有 GET 请求才会被中间件拦截。

Nest 提供了路由通配符的功能(与 Controller 中的路由通配符一样)

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

forRoutes() 方法中还可以传入一个控制器类,如:forRoutes(CatsController),他会将 CatsController 中的所有路由拦截并使用中间件。如果需要传入多个控制器类,只需要使用 , 分割,如: forRoutes(CatsController, UserController)。

不仅如此,apply() 方法同样可以传入一个或多个(用 , 分割)中间件,如:apply(LoggerMiddleware, OtherMiddleware)。

  1. 函数中间件

Nest 中的中间件可以是类,也可以是一个函数,上述都在讲关于类的中间件,这里使用函数来声明一个中间件:

export function logger(req, res, next) {
  console.log(`Request...`);
  next();
};
  1. 全局中间件

为了将中间件一次绑定到每个注册的路由,我们可以使用 INestApplication 实例中的 use() 方法:

const app = await NestFactory.create(ApplicationModule);
// 这里必须使用函数中间件
app.use(logger);
await app.listen(3000);

异常过滤器

Nest 内置异常层负责处理整个应用程序中抛出的所有异常。当捕获到未处理的异常时,用户最终将收到适当的友好响应。

每个出现的异常都由全局异常过滤器处理,当无法识别时(既不是HttpException,也不是从HttpException继承的类),用户会收到以下JSON响应:

{
  "statusCode": 500,
  "message": "Internal server error"
}

当程序抛出一个 HttpException 对象时,它会被异常处理程序捕获,然后转换成相关的 JSON 响应。

  1. 基础异常

在讲异常过滤器前,我们先熟悉框架的基础异常类:HttpException,HttpException 类来自于 @nestjs/common。正如你所想的,当程序抛出一个 HttpException 对象时,它会被异常处理程序捕获,然后转换成相关的 JSON 响应。

如何在控制器层抛出一个 HttpException 呢?

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

此时,客户端会接收到如下 JSON 相应:

{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpException 类的构造器中第一个参数可以是 string 或 object,当你将异常改写成如下时:

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}

客户端会收到:

{
  "status": 403,
  "error": "This is a custom message"
}

这种通常用来做自定义的异常消息返回

守卫

守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。

守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。

守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。

管道

管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。

管道有两个类型:

转换:管道将输入数据转换为所需的数据输出
验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;

拦截器

拦截器是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口。

拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

在函数执行之前/之后绑定额外的逻辑
转换从函数返回的结果
转换从函数抛出的异常
扩展基本函数行为
根据所选条件完全重写函数 (例如, 缓存目的)

每个拦截器都有 intercept() 方法,它接收2个参数。 第一个是 ExecutionContext 实例(与守卫完全相同的对象),第二个参数是 CallHandler。

当我们要记录与应用程序的交互时,它很有用,例如 存储用户调用,异步调度事件或计算时间戳。

// logging.interceptor.ts

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}
  1. 绑定拦截器

为了设置拦截器, 我们使用从 @nestjs/common 包导入的 @UseInterceptors() 装饰器。与守卫一样, 拦截器可以是控制器范围内的, 方法范围内的或者全局范围内的。

// cats.controller.ts

@UseInterceptors(LoggingInterceptor)
export class CatsController {}

总结

管道:数据处理与转换,数据验证
守卫:验证用户登陆,保护路由
拦截器:统一响应内容
过滤器:异常捕获
中间件:日志打印

参考阅读

项目源码

代码已经上传到github中,欢迎大家star,持续更新,如有任何问题可以联系我v:sky201208(注明来意)

https://github.com/fozero/cloud-collect-nestjs

参考阅读

关于我&前端&node进阶交流学习群

大家好,我是阿健Kerry,一个有趣且乐于分享的人,前小鹏汽车、货拉拉高级前端工程师,长期专注前端开发,如果你对前端&Node.js 学习进阶感兴趣的话(后续有计划也可以),可以关注我,加我微信【sky201208】,拉你进交流群一起交流、学习共同进步,群内氛围特别好,定期会组织技术分享~

posted @ 2023-07-12 20:57  fozero  阅读(106)  评论(0编辑  收藏  举报