NestJS(二):异常过滤器和拦截器

662 阅读4分钟

异常过滤器

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

image.png 开箱即用,此操作由内置的全局异常过滤器执行,该过滤器处理类型 HttpException(及其子类)的异常。每个发生的异常都由全局异常过滤器处理, 当这个异常无法被识别时 (既不是 HttpException 也不是继承自 HttpException的类 ) , 用户将收到以下 JSON 响应:

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

HttpException 构造函数有两个必要的参数来决定响应:

  • response: string | object 定义 JSON 响应体;
  • status: number定义HTTPHTTP响应状态码,最佳实践是使用内置的 HttpStatus枚举。

默认情况下,JSON 响应体包含两个属性:

  • statusCode:默认为 status 参数中提供的 HTTP 状态码
  • message:基于状态的 HTTP 错误的简短描述

如果response类型为string,则仅覆盖 JSON 响应主体的message部分; 如果response类型为objectNest将序列化对象,并将其作为响应体返回。

自定义异常过滤器

import {
  ArgumentsHost,
  ExceptionFilter,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { Response, Request } from 'express';

export class HttpFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const statusCode =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    response.status(statusCode).json({
      statusCode,
      data: null,
      success: false,
      message: exception?.message ?? 'Internal server error',
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

ArgumentsHost类提供了获取传递给处理程序参数的方法,可以把ArgumentsHost看作处理程序参数的抽象,调用其getArgs方法可获取封装Express的 [request, response, next]数组对象。

Nest提供了ArgumentsHost的实例,作为host参数提供给catch方法,host.switchToHttp()返回适用于HTTP应用程序上下文的 HttpArgumentsHost类型的ctx对象,ctx对象有两个有用的方法,可以用来获取所需的requsetresponse对象,这里利用这两个对象对异常进行统一错误返回。

拦截器可以是控制器作用域内的, 方法作用域内的或者全局作用域内的。如果是作用于控制器或者方法,可直接使用从 @nestjs/common 包导入的 @UseFilters() 装饰器并传入类型(让Nest创建实例并依赖注入)或者传入匿名对象;如果是作用于全局的,则需要在入口文件使用Nest应用程序实例的 useGlobalFilters() 方法注册:

const app = await NestFactory.create(AppModule);  
app.useGlobalFilters(new HttpFilter());

拦截器

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

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

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

定义拦截器

定义一个规范返回数据的拦截器:

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

interface IResponseData<T> {
  data: T;
}

@Injectable()
export class HttpInterceptor<T> implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler<T>,
  ): Observable<IResponseData<T>> {
    return next.handle().pipe(
      map((data) => ({
        statusCode: HttpStatus.OK,
        data,
        success: true,
        message: 'success',
      })),
    );
  }
}

ExecutionContext扩展了ArgumentsHost。和ArgumentsHost一样,Nest提供了ExecutionContext的实例,作为context参数提供给intercept方法。

 CallHandler。如果不手动调用 handle() 方法,则处理程序根本不会被执行,只有 handle() 被调用(并且已返回其 Observable ),才会触发处理程序。一旦通过 Observable 接收到响应流,就可以对流执行其他操作,并将最终结果返回给调用方。

拦截器可以是控制器作用域内的, 方法作用域内的或者全局作用域内的。如果是作用于控制器或者方法,可直接使用从 @nestjs/common 包导入的 @UseInterceptors() 装饰器并传入类型(让Nest创建实例并依赖注入)或者传入匿名对象;如果是作用于全局的,则需要在入口文件使用Nest应用程序实例的 useGlobalInterceptors() 方法注册:

const app = await NestFactory.create(AppModule);  
app.useGlobalInterceptors(new HttpInterceptor());

总结

本文主要记录了Nest异常过滤器和拦截器的基本概念和基础用法,对于拦截器的参数中CallHandler对象,由于涉及到rxjs,需进一步了解。