怎样自定义 Exception Filter?

0 阅读2分钟

Exception Filter 在 Nest 应用抛异常时,捕获它并返回

比如

image.png

新建项目

nest new exception-filter-demo

自己定义个 exception filter

@Catch 指定要捕获的异常,这里指定 BadRequestException

import {
  ArgumentsHost,
  BadRequestException,
  Catch,
  ExceptionFilter,
} from '@nestjs/common';

@Catch(BadRequestException)
export class HelloFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    debugger;
  }
}

image.png

hello.filter.ts 更新

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

@Catch(BadRequestException)
export class HelloFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const http = host.switchToHttp();
    const response = http.getResponse<Response>();

    const statusCode = exception.getStatus();

    response.status(statusCode).json({
      code: statusCode,
      message: exception.message,
      error: 'Bad Request',
      addOthers: 222,
    });
  }
}

image.png

但其实这也有个问题。

当我们用 ValidationPipe 时

比如加一个路由

@Post('aaa') 
aaa(@Body() aaaDto: AaaDto ){
    return 'success';
}

添加 src/aaa.dto.ts

export class AaaDto {
    aaa: string;
    
    bbb: number;
}

安装

npm install --save class-validator class-transformer

更新 src/aaa.dto.ts

import { IsEmail, IsNotEmpty, IsNumber } from 'class-validator';

export class AaaDto {
  @IsNotEmpty({ message: 'aaa 不能为空' })
  @IsEmail({}, { message: 'aaa 不是邮箱格式' })
  aaa: string;

  @IsNumber({}, { message: 'bbb 不是数字' })
  @IsNotEmpty({ message: 'bbb 不能为空' })
  bbb: number;
}

使用

image.png

这个是 filter 的

image.png

打断点先看下

image.png

根据结构 更新内容

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

@Catch(HttpException)
export class HelloFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const http = host.switchToHttp();
    const response = http.getResponse<Response>();

    const statusCode = exception.getStatus();

    const res = exception.getResponse() as { message: string[] };
    
    response.status(statusCode).json({
       code: statusCode,
       message: res?.message?.join ? res?.message?.join(',') : exception.message,
       error: 'Bad Request',
       xxx: 111
    })
  }
}

image.png

nice!

现在,ValidationPipe 的错误和其他的错误就都返回了正确的格式

想在 Filter 里注入 AppService 怎么做?

image.png

其余的全局 Guard、Interceptor、Pipe 也是这样注册

image.png

Nest 会把所有 token 为 APP_FILTER 的 provider 注册为全局 Exception Filter

比如我注入了 AppService,然后调用它的 getHello 方法

image.png

image.png

hello.filter.ts

import {
  ArgumentsHost,
  BadRequestException,
  Catch,
  ExceptionFilter,
  HttpException,
  Inject,
} from '@nestjs/common';
import { Response } from 'express';
import { AppService } from './app.service';

@Catch(HttpException)
export class HelloFilter implements ExceptionFilter {
  @Inject(AppService)
  private readonly appService: AppService;

  catch(exception: HttpException, host: ArgumentsHost) {
    const http = host.switchToHttp();
    const response = http.getResponse<Response>();

    const statusCode = exception.getStatus();

    const res = exception.getResponse() as { message: string[] };

    response.status(statusCode).json({
      code: statusCode,
      message: res?.message?.join ? res?.message?.join(',') : exception.message,
      error: 'Bad Request',
      xxx: 111,
      yyy: this.appService.getHello(),
    });
  }
}

自定义 Exception 也是可以的

比如添加一个 src/unlogin.filter.ts

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

export class UnLoginException {
  message: string;

  constructor(message?) {
    this.message = message;
  }
}

@Catch(UnLoginException)
export class UnloginFilter implements ExceptionFilter {
  catch(exception: UnLoginException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse<Response>();

    response
      .status(HttpStatus.UNAUTHORIZED)
      .json({
        code: HttpStatus.UNAUTHORIZED,
        message: 'fail 用户未登录',
        data: exception.message || '用户未登录',
      })
      .end();
  }
}

创建了一个 UnloginException 的异常。

然后在 ExceptionFilter 里 @Catch 了它。

在 AppModule 里注册这个全局 Filter

image.png

在 AppController 里抛出这个异常

image.png

image.png