NestJS 7.x 折腾记: (6) 异常过滤器,取其精华去其糟粕!比如响应异常数据的包装~

前言

正如官方所说:
内置的异常层负责处理整个应用程序中的所有抛出的异常。
当捕获到未处理的异常时,最终用户将收到友好的响应。
image.png
NestJS提供了一波拿来即用的内置异常过滤器;
@nestjs/common里面,搜索下Exception就有~
我们来一个具体的例子(全局异常过滤),
基于内置的异常过滤器实现,采用第三方日志(pino)记录异常日志,
做一些处理并包裹返回信息;

效果图

image.png

实战

1
2
# 基于cli生成filter模板
nest g f common/filters/http-exception

http-exception.filter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 日期格式化库,很小巧,类moment 风格api
import * as dayjs from 'dayjs';

import {<!-- -->
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
// nest默认底层是基于express封装,所以可以直接引入
import {<!-- --> Request, Response } from 'express';
// 第三方logger
import {<!-- --> Logger } from 'nestjs-pino';

// 捕获请求异常类型
// 可以传递多个参数,所以你可以通过逗号分隔来为多个类型的异常设置过滤器。
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {<!-- -->
  // 我们要把异常塞到自定义logger,必须引入对应的实例
  // 在构建函数声明定义下,从外部传入
  constructor(private readonly logger: Logger) {<!-- -->}
 
  catch(exception: HttpException, host: ArgumentsHost) {<!-- -->
    // 把请求相关的参数转成标准http的上下文
    // 有兴趣可以点进去,GPRC,WEBSOCKET都能直接转换
    // 也能直接拿到一些参数的及返回上下文类型
    const ctx = host.switchToHttp();
    // 响应体
    const response = ctx.getResponse<Response>();
    // 请求体
    const request = ctx.getRequest<Request>();
   
    // 判断状态是否为请求异常,否则直接抛回来服务内部错误
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
   
    // 此刻的时间
    const nowDate = dayjs(Date.now()).format('YYYY-MM-DDTHH:mm:ss');
   
    // 包装异常信息
    const errorResponse = {<!-- -->
      statusCode: status,
      message: exception.message,
      error: exception.name,
      date: nowDate,
      path: request.url,
    };

    // 记录异常信息到第三方logger
    this.logger.error(
      `【${<!-- -->nowDate}】${<!-- -->request.method} ${<!-- -->request.url} query:${<!-- -->JSON.stringify(
        request.query,
      )} params:${<!-- -->JSON.stringify(request.params)} body:${<!-- -->JSON.stringify(
        request.body,
      )}`,
      JSON.stringify(errorResponse),
      'HttpExceptionFilter',
    );
   
    // 塞回去响应体,也就是客户端请求可以感知到的
    response.status(status).json(errorResponse);
  }
}

主入口(main.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {<!-- --> AppModule } from './app.module';
import {<!-- --> HttpExceptionFilter } from './common/filters/http-exception.filter';
import {<!-- --> Logger } from 'nestjs-pino';
import {<!-- --> NestFactory } from '@nestjs/core';
async function bootstrap() {<!-- -->
  const app = await NestFactory.create(AppModule, {<!-- -->
    cors: false,
    logger: false,
  });
  // 获取pino logger实例
  const logger = app.get(Logger);
  // nestjs-pino 取代nest logger
  app.useLogger(logger);


  // 设置全局异常过滤器
  app.useGlobalFilters(new HttpExceptionFilter(logger));
  await app.listen(configService.get('SERVE_LISTENER_PORT'));
}
bootstrap()

app.module.ts

若是只要特定模块生效可以使用Providers去实现,从核心模块导出;

1
2
3
4
5
6
7
8
9
10
11
12
13
import {<!-- --> Module } from '@nestjs/common';
// 包括网关,拦截器都可以走这种模式!
import {<!-- --> APP_FILTER } from '@nestjs/core';
import {<!-- --> HttpExceptionFilter } from './common/filters/http-exception.filter';
@Module({<!-- -->
  providers: [
    {<!-- -->
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {<!-- -->}

若是加上@Global装饰器也能达到全局模块的效果!
这个东东一般不推荐用在app.module,而是你需要导出的复用模块!
这里只是演示写法

1
2
3
4
5
6
7
8
9
10
11
12
13
import {<!-- --> Module,Global } from '@nestjs/common';
import {<!-- --> APP_FILTER } from '@nestjs/core';

@Global()
@Module({<!-- -->
  providers: [
    {<!-- -->
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {<!-- -->}

@Global() 推荐用于给外部复用,又需要变成全局模块的,比如

1
2
3
4
5
6
7
8
9
10
11
import {<!-- --> Module, Global } from '@nestjs/common';
import {<!-- --> CatsController } from './cats.controller';
import {<!-- --> CatsService } from './cats.service';

@Global()
@Module({<!-- -->
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {<!-- -->}

总结

有不对之处请留言,会及时修正!谢谢阅读~