2-5:NestJS中的异常处理机制

3 阅读3分钟

NestJS本身内置了异常处理,但是它只会处理HttpException,如果一个异常,不是HttpException,那么内置的异常处理机制会统一抛出

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

这种情况下,就需要我们手动编写一个Filter类。NestCli已经提供了创建方式,执行nest g filter db-exception --no-spec
src目录下就创建了一个db-exception文件夹。这里推荐创建一个filter文件夹,然后将db-exception放在db-exception下。 复制官网的模板代码

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

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
}

假设我们的异常是在数据库中插入了一个主键重复的元素。那么对应的异常就是UniqueConstraintViolationException我们就可以简单的将代码改造为:

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { UniqueConstraintViolationException } from '@mikro-orm/core';
@Catch()
export class DbExceptionFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    if (exception instanceof UniqueConstraintViolationException) {
      response.status(HttpStatus.CONFLICT).json({
        message: exception.message,
      });
    }
  }
}

filter一样,有两种使用方式,第一种是在对应的controller中的方法使用装饰器 @UseFilters(new DbExceptionFilter())
第二种就是全局使用,在main.ts中添加:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

但是这样做很显然不明智,因为前端的message会收到没有经过处理的错误信息,既杂乱,也很可能暴露数据库中的数据。 因此,可以对message做简单的包装:

response.status(HttpStatus.CONFLICT).json({
   message: "Resource already exists",
});

如果异常信息只有1,2个这样做可行,但是如果对每一个异常都用if判断一下,就显得不太聪明。因此我们可以简单创建一个映射表。

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import {
  DriverException,
} from '@mikro-orm/core';

//key就是异常的名字,value是http状态码和返回的消息
const DB_HTTP_MAP: Record<string, { status: number; message: string }> = {
  UniqueConstraintViolationException: {
    status: HttpStatus.CONFLICT,
    message: 'Unique constraint violation',
  },
};

@Catch()
export class DbExceptionFilter<T> implements ExceptionFilter {
  catch(exception: DriverException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const { status, message } = DB_HTTP_MAP[exception.name];

    response.status(status).json({ message });
  }
}

这里我们把异常的类型设置为了DriverException,但是这样做会把所有的异常都当作是Mikro-ORM中的异常进行识别和处理。如果抛出的异常不是Mikro-ORM中的异常,例如是最简单的Http中的异常,其实不需要我们这里进行处理,而是应该直接交给nestJS内置的工具自行处理。
因此,尽可能的不要将filter作用于全局,或者filter上写明具体匹配的异常的类,这样就能保证异常处理器只匹配这一类异常。
这样,我们可以把Mikro-ORM中所有的异常类都放在@Catch

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import {
  UniqueConstraintViolationException,
  CheckConstraintViolationException,
  DriverException,
} from '@mikro-orm/core';

//key就是异常的名字,value是http状态码和返回的消息
const DB_HTTP_MAP: Record<string, { status: number; message: string }> = {
  UniqueConstraintViolationException: {
    status: HttpStatus.CONFLICT,
    message: 'Unique constraint violation',
  },
};

@Catch(UniqueConstraintViolationException, CheckConstraintViolationException)
export class DbExceptionFilter<T> implements ExceptionFilter {
  catch(exception: DriverException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const { status, message } = DB_HTTP_MAP[exception.name];

    response.status(status).json({ message });
  }
}