NestJS 学习笔记 2 (中间件、异常过滤器)

122 阅读2分钟

在上一节里, 我们学习了模块控制器提供者, 讲述了最简 API 编写流程. 这一节里, 我们学习 AOP(面向切面编程), NestJS 是 MVC 与 AOP 结合的框架结构.

AOP(Aspect Oriented Programming): 是一种面向切面编程的编程思想.

在 NestJS 中, 一个请求进来, 会经过控制器 C 到模型 M 的流程, AOP 是从切面的插进去, AOP 在 NestJS 用到的功能为中间件(日志等)、异常过滤器(异常处理)、管道(数据验证、转换)、守卫守卫(权限)、拦截器(日志、数据转换、Stream).

image.png

中间件

中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示。

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。
  • 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。

写法

logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

    @Injectable()
    export class LoggerMiddleware implements NestMiddleware {
      use(req: Request, res: Response, next: NextFunction) {
        console.log('Request...');
        next();
      }
    }

引入方法

依赖注入

app.module.ts

    import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
    import { LoggerMiddleware } from './common/middleware/logger.middleware';
    import { CatsModule } from './cats/cats.module';

    @Module({
      imports: [CatsModule],
    })
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(LoggerMiddleware)
          .forRoutes('cats'); // 包含路由路径
          // .forRoutes({ path: 'cats', method: RequestMethod.GET }); // 请求方法
          // .forRoutes({ path: 'ab*cd', method: RequestMethod.ALL }); // 通配符
      }
    }

全局

main.ts

const app = await NestFactory.create(AppModule);
app.use(logger);  // 注册中间件
await app.listen(3000);

异常过滤器

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

cats.controller.ts

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

响应

{ "statusCode": 403, "message": "Forbidden" }

自定义异常过滤器

http-exception.filter.ts

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

  @Catch(HttpException)  // 抛出的 HttpException 异常都到这里, 传递多个参数,用逗号分隔
  export class HttpExceptionFilter implements ExceptionFilter {
    catch(exception: HttpException, host: ArgumentsHost) {
      const ctx = host.switchToHttp();
      const response = ctx.getResponse<Response>();
      const request = ctx.getRequest<Request>();
      const status = exception.getStatus();

      response
        .status(status)
        .json({
          statusCode: status,
          timestamp: new Date().toISOString(),
          path: request.url,
        });
    }
  }

绑定过滤器

cats.controller.ts

@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

全局

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter()); // 全局绑定过滤器
  await app.listen(3000);
}
bootstrap();

捕获异常

为了捕获每一个未处理的异常(不管异常类型如何),将 @Catch() 装饰器的参数列表设为空,例如 @Catch()

any-exception.filter.ts

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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

以上就是简单的记录一下, 详情可以看官方文档