Nest带有一个内置的异常层,该层负责处理整个应用程序中所有未处理的异常。当您的应用程序代码未处理异常时,该层将捕获该异常,然后自动发送适当的用户友好响应。
Nest提供的异常处理是开箱即用的,此操作由全局异常过滤器处理,该过滤器只能处理HttpException(及其子类)的异常,如果无法识别异常(既不HttpException是继承自的类也不是HttpException),则内置异常过滤器会生成以下默认JSON响应:

  1. {
  2. "statusCode" :500 ,
  3. "message" :"内部服务器错误"
  4. }

Nest提供了一个内置HttpException类,从@nestjs/common包中公开。对于典型的基于HTTP REST / GraphQL API的应用程序,最佳实践是在发生某些错误情况时发送标准HTTP响应对象。
HttpException构造函数接收两个参数,第一个参数是JSON格式的响应主体,可以是字符串,也可以是对象;第二个参数是Http状态码。默认情况JSON响应主体包含两个属性响应状态码和响应信息,如果仅要覆盖JSON响应主体的消息部分, 则在response参数提供一个字符串,要覆盖整个JSON响应主体,请在response参数中传递一个对象。Nest将序列化对象,并将其作为JSON响应正文返回。

  1. import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
  2. import { AppService } from './app.service';
  3. /**
  4. * @Controller可以接收一个参数用于区分路由分组
  5. */
  6. @Controller('/app')
  7. export class AppController {
  8. constructor(private readonly appService: AppService) {}
  9. @Get('/hello')
  10. getHello(): string {
  11. /**
  12. * 故意抛出一个非HttpException(及其子类)的异常,Nest使用全局异常过滤器对其处理,
  13. * 默认以json格式响应以下信息:
  14. * {
  15. "statusCode": 500,
  16. "message": "Internal server error"
  17. }
  18. */
  19. throw new Error('手动抛出一个异常....');
  20. return this.appService.getHello();
  21. }
  22. /**
  23. * 抛出HttpException异常,此异常从@nestjs/common包中导入,HttpException构造函数接收
  24. * 两个参数,第一个参数是JSON响应主体(response),可以是字符串也可以是对象,
  25. * 第二个参数是HTTP状态码。
  26. *
  27. * 以下例子中当访问 localhost:3000/app/findUser 就会响应如下信息:
  28. * {
  29. * "statusCode": 403,
  30. * "message": "禁止访问"
  31. * }
  32. * 默认情况JSON响应主体包含两个属性响应状态码和响应信息,如果仅要覆盖JSON响应主体的消息部分,
  33. * 则在response参数提供一个字符串,要覆盖整个JSON响应主体,
  34. * 请在response参数中传递一个对象。Nest将序列化对象,并将其作为JSON响应正文返回。
  35. */
  36. @Get('/findUser')
  37. findUser() {
  38. throw new HttpException(
  39. {
  40. status: HttpStatus.FORBIDDEN,
  41. error: '这是自定义异常信息',
  42. },
  43. HttpStatus.FORBIDDEN,
  44. );
  45. }
  46. }

1.自定义异常

自定义异常非常简单,只需要继承HttpException类即可,然后调用HttpException类的构造函数。

  1. import { HttpException, HttpStatus } from '@nestjs/common';
  2. /**
  3. * 自定义异常过滤器,需要继承HttpException
  4. */
  5. export class ForbiddenException extends HttpException {
  6. constructor(message?: string) {
  7. //调用父类构造
  8. super(message || '禁止访问', HttpStatus.FORBIDDEN);
  9. }
  10. }
  1. import { ForbiddenException } from './common/exception/ForbiddenException';
  2. /**
  3. * 测试自定义异常,响应信息为:
  4. {
  5. "statusCode": 403,
  6. "message": "禁止访问"
  7. }
  8. */
  9. @Get('/testCustomException')
  10. testCustomException() {
  11. throw new ForbiddenException();
  12. }

Nest提供了一组从base继承的标准异常HttpException。这些是从@nestjs/common程序包中公开的,它们代表许多最常见的HTTP异常:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

    2.异常过滤器

    虽然基本(内置)异常过滤器可以为您自动处理许多情况,但是我们希望能完全控制异常层,例如通过日志记录异常信息或基于一个动态条件使用其他JSON模式,异常过滤器正是为此目的而设计的。它们使您可以控制精确的控制流程以及发送回客户端的响应内容。
    实现异常过滤器就两步:
    (1).实现ExceptionFilter接口,并重写catch(exception: T, host: ArgumentsHost)方法,其中T表示捕获异常的类型,exception表示当前正在处理的异常对象,host是一个ArgumentsHost对象,ArgumentsHost是一个功能强大的实用工具对象,这里通过它来获取执行上下文,从而得到请求对象和响应对象。
    (2).使用@Catch装饰器装饰要处理的异常类型。例如@Catch(HttpException)这会告诉Nest,这个特殊的过滤器正在寻找异常类型为HttpException的异常,@Catch()装饰可以采用单个参数或逗号分隔的列表。例如@Catch(HttpException,ForbiddenException)。一般为了捕获所有异常(无论异常类型是什么),请将@Catch()的参数列表设置为空,例如@Catch(),过滤器将捕获抛出的每个异常,无论其类型如何。 ```typescript // src/common/exception/HttpExceptionFilter.ts import { ExceptionFilter, ArgumentsHost, HttpException, Catch } from ‘@nestjs/common’; import { Request, Response } from ‘express’; import { HttpArgumentsHost } from ‘@nestjs/common/interfaces’;

@Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { /*

  • @param exception 为捕获的异常类型,exception:HttpException表示捕获HttpException异常
  • @param host */ catch(exception: HttpException, host: ArgumentsHost) { //获取上下文 const ctx: HttpArgumentsHost = 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, }); } }
  1. 使用自定义异常过滤器需要借助@UseFilters()装饰器,@UseFilters()装饰器由@nestjs/common包提供,@UseFilters()可以接收一个或多个过滤器类或过滤器类的实例(对过滤器进行了new操作),Nest官方推荐使用过滤器类的方式,解释是:由于Nest可以轻松地在整个模块中重用同一类的实例,因此可以减少内存使用。
  2. ```typescript
  3. import {
  4. Controller,
  5. Get,
  6. HttpException,
  7. HttpStatus,
  8. UseFilters,
  9. } from '@nestjs/common';
  10. import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
  11. @Controller('/app')
  12. export class AppController {
  13. constructor(private readonly appService: AppService) {}
  14. /**
  15. * 测试自定义异常
  16. */
  17. @Get('/testCustomException')
  18. testCustomException() {
  19. throw new ForbiddenException();
  20. }
  21. /**
  22. * 测试自定义异常过滤器
  23. */
  24. @Get('/testCustomExceptionFilter')
  25. @UseFilters(new HttpExceptionFilter())
  26. testCustomExceptionFilter() {
  27. throw new ForbiddenException();
  28. }
  29. }

结果如下图:
kkkk.jpg

3.异常过滤器的作用域

异常过滤器的作用域三种,分别是路由方法作用域、控制器作用域、全局作用域,由于上面例子中@UseFilters()放在控制器的路由方法上,所以只会对该路由方法有效,对于其他路由方法是无效的。如果将@UseFilters()放在Controller上则表示会对当前Controller所有的方法进行异常过滤处理。如果将异常过滤器注册到全局则会对整个应用进行异常处理。

  1. import {
  2. Controller,
  3. Get,
  4. HttpException,
  5. HttpStatus,
  6. UseFilters,
  7. } from '@nestjs/common';
  8. import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
  9. @Controller('/app')
  10. @UseFilters(HttpExceptionFilter) // 控制器作用域,能对当前控制器发生异常进行异常处理
  11. export class AppController {
  12. constructor(private readonly appService: AppService) {}
  13. /**
  14. * 测试自定义异常
  15. */
  16. @Get('/testCustomException')
  17. testCustomException() {
  18. throw new ForbiddenException();
  19. }
  20. /**
  21. * 测试自定义异常过滤器
  22. */
  23. @Get('/testCustomExceptionFilter')
  24. @UseFilters(HttpExceptionFilter) // 路由作用域,只能针对当前方法进行异常处理
  25. testCustomExceptionFilter() {
  26. throw new ForbiddenException();
  27. }
  28. }
  1. //main.ts
  2. import { NestFactory } from '@nestjs/core';
  3. import { AppModule } from './app.module';
  4. import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
  5. async function bootstrap() {
  6. const app = await NestFactory.create(AppModule);
  7. //使用全局异常过滤器,网关或混合应用程序设置过滤器
  8. app.useGlobalFilters(new HttpExceptionFilter());
  9. await app.listen(3000);
  10. }
  11. bootstrap();

4.对外提供异常过滤器

就依赖关系注入而言,从任何模块外部注册的全局过滤器(useGlobalFilters()如上例所示)都不能注入依赖关系,因为这是在任何模块的上下文之外完成的。为了解决此问题,您可以使用以下结构直接从任何模块中注册全局范围的过滤器。

  1. import { Module } from '@nestjs/common';
  2. import { APP_FILTER } from '@nestjs/core';
  3. import { AppController } from './app.controller';
  4. import { AppService } from './app.service';
  5. import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
  6. @Module({
  7. imports: [],
  8. controllers: [AppController],
  9. providers: [
  10. {
  11. provide: APP_FILTER,
  12. useClass: HttpExceptionFilter,
  13. },
  14. AppService,
  15. ],
  16. })
  17. export class AppModule {}

5.继承

如果想扩展内置的默认全局异常过滤器并根据某些因素覆盖行为时,可以实现BaseExceptionFilter接口并重写catch(),BaseExceptionFilter由@nestjs/core包提供。

  1. import { BaseExceptionFilter } from '@nestjs/core';
  2. import { Catch, ArgumentsHost } from '@nestjs/common';
  3. @Catch()
  4. export class AllExceptionsFilter extends BaseExceptionFilter {
  5. catch(exception: unknown, host: ArgumentsHost): void {
  6. super.catch(exception, host);
  7. }
  8. }

全局过滤器可以扩展基本过滤器。这可以通过两种方式之一来完成。第一种方法是HttpServer在实例化自定义全局过滤器时注入引用:

  1. //方式1
  2. async function bootstrap() {
  3. const app = await NestFactory.create(AppModule);
  4. const { httpAdapter } = app.get(HttpAdapterHost);
  5. app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
  6. await app.listen(3000);
  7. }
  8. bootstrap();

第二种方法是使用APP_FILTER令牌: