nodejs企业级开发框架nest学习总结 - 2.NestJS入门middleware、exceptionFilter、Pipe。

Nest 同时被 3 个专栏收录
7 篇文章 1 订阅
8 篇文章 0 订阅
22 篇文章 0 订阅

NestJS入门middleware、exceptionFilter、pipe。

前面一节介绍了NestJS的controller、DTO、providers、module等的学习

1.middleware

中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及next()应用程序请求 - 响应周期中的中间件功能。

中间件例子:core跨域中间件、bodyparser中间件、jwt中间件、logger日志中间件等等等
官方实例日志中间件,小改动
1.1中间件类,实现NestMiddleware接口,重写use方法
// middleware/logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

/**
 * * 依赖注入
 *  Nest中间件完全支持依赖注入。与提供者和控制器一样,他们能够注入同一模块中可用的依赖项。像往常一样,这是通过constructor。
 * @Injectable 1.注册成nest知道的提供者可以注入的
 */
@Injectable()
export class LoggerMiddleware implements NestMiddleware { // 继承NestMiddleware 实现中间件
    public use(req: Request, res: Response, next: (url?: string) => void) {
        console.log('logger...');
        next(); // 记得要next,让中间件过滤后通过
    }
}
1.2 方法中间件
// middleware/logger.middleware.ts 补充下面代码
import { Request, Response, NextFunction } from 'express';
/**
 * @Param 2.功能中间件 在您的中间件不需要任何依赖项时,请考虑使用更简单的功能中间件替代方案。
 */
export function logger(req: Request, res: Response, next: NextFunction) {
    console.log('logger...');
    next();
}
1.3 中间件的使用,先配置中间件
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerMiddleware, logger } from './middleware/logger.middleware';
@Module({
    controllers: [AppController], // 在此模块中定义的控制器集,必须进行实例化
    providers: [AppService], // 注册服务成一个提供者,提供给controller使用
    exports: [AppService], // 我们想要共享AppService服务的实例给其它模块使用,为了做到这一点,我们需要导出providers里面的服务,提供给其他模块使用。(导出proivders里面的服务,给其他模块使用)
})
export class ChildModule implements NestModule { // 实现NestModule接口,从而配置中间件
    // MiddlewareConsumer是一个帮助类。它提供了几种内置方法来管理中间件
    public configure(consumer: MiddlewareConsumer) {
        // 1.应用中间件,注册LoggerMiddleware中间件到那些路由中 * 表示任意路由
        consumer
            // 为了绑定顺序执行的多个中间件,只需在apply()方法中提供逗号分隔的列表:
            .apply(LoggerMiddleware, logger) // apply()方法可以采用单个中间件,也可以使用(多个参数来指定多个中间件。)
            // 4.1 排除某些路由使用中间件
            .exclude(
                { path: 'user', method: RequestMethod.GET },
                // { path: 'user', method: RequestMethod.POST },
            )
            .forRoutes('*');
        // 2.或者选择路由路径+请求的方式 post请求 才会通过中间件
        // 3. .forRoutes({ path: 'user', method: RequestMethod.POST });
        // 4. .forRoutes(AppController) 放入一个controller,控制整个路由都要通过中间件
    }
}
配置中间件middleware,需要实现Nest提供的NestModule接口,重写configure方法

重写的configure参数为MiddlewareConsumer类型的一个参数,用于配置中间件

consumer.apple(...arg) // 使用中间件,接收一个0-n个参数,参数为每一个中间件,并且顺序执行的多个中间件
.exclude({ path: 'user', method: RequestMethod.ALL },) // 接收0-n个排除的对象,排除哪个路由,那种请求方式不需要使用中间件
// path: 'user', method: RequestMethod.ALL表示除了/user里面的全部请求方式之外的全部路由都使用中间件
.forRoutes('*'); //表示应用了全局的路由,除了exclude之外
// .forRoutes('/user/*');表示应用于/user/*的路由
// forRoutes里面也可以接受一个和exclude需要的对象一致的参数,也可以直接放入一个控制器
RequestMethod.ALL,GET,POST,DELETE.PATCH,PUT...
全局使用中间件
const app = await NestFactory.create(AppModule);
app.use(loggerMiddleware, logger);
await app.listen(3000);

2.异常类

自带异常类:HttpException,相当于把相同默认异常返回的数据重写

(内置HttpException类从@nestjs/common包中暴露出来。)

@Get()
async findAll() {
	//接收两个参数,第一个是string | object类型,第二个参数是接收返回的statusCode码
  throw new HttpException('This is a custom message', HttpStatus.FORBIDDEN);
  /* throw new HttpException({
    status: HttpStatus.FORBIDDEN, //status状态码
    error: 'This is a custom message', //错误信息
  }, HttpStatus.FORBIDDEN); */
}
抛出异常,异常信息: HttpStatus.FORBIDDEN:就是403
{
  "statusCode": 403,
  "message": "This is a custom message"
}
HttpStatus 自带的http请求码有以下那么多,HttpStatus是一个枚举类型的数据
export declare enum HttpStatus {
    CONTINUE = 100,
    SWITCHING_PROTOCOLS = 101,
    PROCESSING = 102,
    OK = 200,
    CREATED = 201,
    ACCEPTED = 202,
    NON_AUTHORITATIVE_INFORMATION = 203,
    NO_CONTENT = 204,
    RESET_CONTENT = 205,
    PARTIAL_CONTENT = 206,
    AMBIGUOUS = 300,
    MOVED_PERMANENTLY = 301,
    FOUND = 302,
    SEE_OTHER = 303,
    NOT_MODIFIED = 304,
    TEMPORARY_REDIRECT = 307,
    PERMANENT_REDIRECT = 308,
    BAD_REQUEST = 400,
    UNAUTHORIZED = 401,
    PAYMENT_REQUIRED = 402,
    FORBIDDEN = 403,
    NOT_FOUND = 404,
    METHOD_NOT_ALLOWED = 405,
    NOT_ACCEPTABLE = 406,
    PROXY_AUTHENTICATION_REQUIRED = 407,
    REQUEST_TIMEOUT = 408,
    CONFLICT = 409,
    GONE = 410,
    LENGTH_REQUIRED = 411,
    PRECONDITION_FAILED = 412,
    PAYLOAD_TOO_LARGE = 413,
    URI_TOO_LONG = 414,
    UNSUPPORTED_MEDIA_TYPE = 415,
    REQUESTED_RANGE_NOT_SATISFIABLE = 416,
    EXPECTATION_FAILED = 417,
    I_AM_A_TEAPOT = 418,
    UNPROCESSABLE_ENTITY = 422,
    TOO_MANY_REQUESTS = 429,
    INTERNAL_SERVER_ERROR = 500,
    NOT_IMPLEMENTED = 501,
    BAD_GATEWAY = 502,
    SERVICE_UNAVAILABLE = 503,
    GATEWAY_TIMEOUT = 504,
    HTTP_VERSION_NOT_SUPPORTED = 505
}
自定义异常类,继承HttpException异常类
export class ForbiddenException extends HttpException {
  constructor(message: string, status: number) {
    super(message, status); // 把异常数据传递给父类HttpException
  }
}
使用自定义异常类,
@Get()
async findAll() {
	new ForbiddenException('error', HttpStatus.INTERNAL_SERVER_ERROR);
}
@nestjs/common包装中暴露出来:自带的异常类,可以开箱直接使用
  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

3.exceptionFilter 异常过滤器,至于为什么没有其他过滤器,我也不知道了

定义异常过滤器,(完全控制异常层,例如,您可能希望添加日志记录或根据某些动态因素使用不同的JSON模式)
// filter/httpException.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
import { Request, Response } from 'express';
/**
 * 异常过滤器
 */
// 虽然基本(内置)异常过滤器可以自动为您处理许多情况,但您可能希望完全控制异常层。例如,您可能希望添加日志记录或根据某些动态因素使用不同的JSON模式。异常过滤器就是为此目的而设计的。
@Catch() // 该@Catch(HttpException)装饰结合所需的元数据的异常过滤器,告诉nest,这个特殊的过滤器正在寻找类型的异常HttpException
// export class HttpExceptionFilter implements ExceptionFilter<HttpException> {// 实现异常过滤器ExceptionFilter接口,重写该接口的方法catch
export class HttpExceptionFilter implements ExceptionFilter {
    // 所有异常过滤器都应实现通用ExceptionFilter<T>接口。这要求您为catch(exception: T, host: ArgumentsHost)方法提供其指示的签名。T表示异常的类型。
    public catch(exception: HttpException, host: ArgumentsHost) {
        const ctx: HttpArgumentsHost = host.switchToHttp(); // 获取上下文
        const response = ctx.getResponse<Response>(); //获取响应response
        const request = ctx.getRequest<Request>(); // 获取请求request
        const { message } = exception.message; // 获取错误信息
        const status = exception instanceof HttpException ?  exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; // 判断exception是不是HttpException的实现,如果是获取getStatus,状态码,如果不是的话,自行设置HttpStatus.INTERNAL_SERVER_ERROR
        response
            .status(status)
            .json({
                statusCode: status, // 请求状态
                timestamp: new Date().toISOString(), // 请求的当前时间
                path: request.url, // 请求的url
                message, // 错误信息
            });
    }
}
// * 为了捕获每个未处理的异常(不管异常类型),请将@Catch()装饰器的参数列表留空,例如@Catch()。
异常过滤器:@Catch()装饰器的参数列表留空,是为了捕获到每个未处理到的异常
实现异常过滤器ExceptionFilter接口,重写该接口的方法catch
exception:保存了http请求异常的message异常信息、status状态码的参数
host:也是一个对象,存储了request,response,next等数据的switchToHttp参数和其它一些属性

host 的对象属性如下 ArgumentsHost

export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost; // 请求上下文
  switchToWs(): WsArgumentsHost;
}

绑定异常过滤器到路由方法上

import { UseFilters, Post, Body, Controller } from '@nestjs/common';
import { HttpExceptionFilter } from './filter/httpException.filter.ts';
import { CreateBodyDto } from './DTO/body.dto';
 	// @UseFilters()装饰器从导入的@nestjs/common包
	@Post()
	@UseFilters(new HttpExceptionFilter())
	async create(@Body() createCatDto: CreateCatDto) {
	  throw new ForbiddenException();
	}
@UseFilters(new HttpExceptionFilter())可以装饰在控制器类中

全局配置异常过滤器

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
配置模块作用域异常过滤器,在providers里面配置
// app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerMiddleware, logger } from './middleware/logger.middleware';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './filter/httpException.filter';
@Module({
    controllers: [AppController], // 在此模块中定义的控制器集,必须进行实例化
    providers: [AppService,
        // 5.1对于每个控制器和每个路由处理程序,在整个应用程序中使用全局范围的过滤器。在依赖注入方面,(从任何模块外部注册的全局过滤器(useGlobalFilters()如上例所示)不能注入依赖关系,)
        // 5.2因为这是在任何模块的上下文之外完成的。为了解决此问题,您可以使用以下构造直接从任何模块注册全局范围的过滤器
        {
            provide: APP_FILTER,
            useClass: HttpExceptionFilter,
        },
    ], // 注册服务成一个提供者,提供给controller使用
    exports: [AppService], // 我们想要共享AppService服务的实例给其它模块使用,为了做到这一点,我们需要导出providers里面的服务,提供给其他模块使用。(导出proivders里面的服务,给其他模块使用)
})
export class ChildModule implements NestModule { // 实现NestModule接口,从而配置中间件
    // MiddlewareConsumer是一个帮助类 (使用类) 。它提供了几种内置方法来管理中间件
    public configure(consumer: MiddlewareConsumer) {
        // 1.应用中间件,注册LoggerMiddleware中间件到那些路由中 * 表示任意路由
        consumer
            // 为了绑定顺序执行的多个中间件,只需在apply()方法中提供逗号分隔的列表:
            .apply(LoggerMiddleware, logger) // apply()方法可以采用单个中间件,也可以使用(多个参数来指定多个中间件。)
            // 4.1 排除某些路由使用中间件
            .exclude(
                { path: 'user', method: RequestMethod.ALL },
                // { path: 'user', method: RequestMethod.POST },
            )
            .forRoutes('*');
        // 2.或者选择路由路径+请求的方式 post请求 才会通过中间件
        // 3. .forRoutes({ path: 'user', method: RequestMethod.POST });
        // 4. .forRoutes(AppController) 放入一个controller,控制整个路由都要通过中间件
    }
}

定义全部异常过滤器,让Nest自动实例化它们

但是,如果您只想扩展内置的默认全局异常过滤器,并根据某些因素覆盖行为,则可能存在用例。
继承BaseExceptionFilter并重写catch()方法
// 但是,如果您只想扩展内置的默认全局异常过滤器,并根据某些因素覆盖行为,则可能存在用例。
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
/**
 *  让框架自动实例化它们
 */
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
全局使用全部异常过滤器,(Nest内部处理的异常过滤器)
// main.ts
import { NestFactory, HttpAdapterHost } from '@nestjs/core';
import { AllExceptionsFilter } from './filter/allException.filter';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();
4.管道

管道是用@Injectable()装饰器注释的类。管道应该实现PipeTransform接口

管道有两个典型的用例:
* 转换:将输入数据转换为所需的输出
* 验证:评估输入数据,如果有效,只需将其传递给未更改; 否则,在数据不正确时抛出异常
NestJS自带可用的三根pipe:ValidationPipe,ParseIntPipe和ParseUUIDPipe。它们是从@nestjs/common包装中输出的。

为了更好地理解它们的工作原理,让我们从头开始构建它们。

4.1验证示例
验证的依赖包
  npm i --save class-validator class-transformer
class-validator : 一个验证参数和类中的属性是否一致、是否是电话号码等等等的一个验证类的库

routing-controllers、class-validator、typedi的使用总结(博客地址,有class-validator的一些介绍)

修改之前的body.dto.ts文件,加入class-validator验证类
// DTO/body.dto.ts
/**
 * DTO(数据传输对象)架构
 */
import { Length } from 'class-validator';
export class CreateBodyDto {
    @Length(3, 10, {
        message: '用户名的长度在3到10个字符之间',
    })
    public readonly username: string;
    @Length(6, 16, {
        message: '密码的长度在6到16个字符之间',
    })
    public readonly password: string;
}

创建validation.pipe.ts文件,放入验证管道

参数value看使用场景的不同而不同

// 添加 pipe/validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate, ValidationError } from 'class-validator';
import { plainToClass } from 'class-transformer';
/**
 * 当出现异常的情况下,会转而通过自定义的HttpExceptionFilter异常过滤器,返回指定的异常对象到前端
 *  管道验证:
 * @Pram - 1.可以把验证函数实例在Body装饰器、Headers装饰器等方法的参数中
 *       - 2.可以写在验证方法的头上,使用UsePipes装饰器,传入实例pipe
 *       - 3.也可以写在验证的整个controller类上,同样使用UsePipes装饰器,传入实例pipe
 *       - 4.当然也可以全局创建pipe,在mian.ts文件的bootstrap里面加入app.useGlobalPipes(new ValidatePipe());来实例管道
 */
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
    // Nest支持同步和异步管道
    public async transform(value: any, { metatype }: ArgumentMetadata) {
        if (!metatype || !this.toValidate(metatype)) { // 当metatype不存在或者,验证metatype不是以下类型,直接返回value
            return value;
        }
        const object = plainToClass(metatype, value); // 类转换器函数plainToClass()将我们的普通JavaScript参数对象转换为类型化对象,以便我们可以应用验证。
        const errors: ValidationError[] = await validate(object); // 认证这些属性是否通过
        if (errors.length > 0) {
            throw new BadRequestException(errors);
        }
        return value;
    }

    private toValidate(metatype: any): boolean { // 验证metatype是不是以下类型
        const types: any[] = [String, Boolean, Number, Array, Object];
        return !types.includes(metatype);
    }
}
plainToClass:类转换器函数plainToClass()将我们的普通JavaScript参数对象转换为类型化对象,以便我们可以应用验证。
validate(class-validator自带验证器)验证是否一致,不一致则抛出异常,error存在的情况下,不一致,error为null的情况下表示验证通过
metadata对象的数据类型如下:

export interface ArgumentMetadata {
readonly type: ‘body’ | ‘query’ | ‘param’ | ‘custom’;
readonly metatype?: Type;
readonly data?: string;
}

type:指示参数是正文@Body(),查询@Query(),参数@Param()还是自定义参数(在此处阅读更多内容)。
metatype:例如,提供参数的元类型String。注意:undefined如果您在路由处理程序方法签名中省略了类型声明,或者使用vanilla JavaScript,则该值为。
data:例如,传递给装饰器的字符串@Body('string')。这是undefined,如果你离开装饰圆括号空。
使用验证管道类,在参数中使用,例如Body,Param,Query对象这些的验证,(注意:对象,表示没有给这些传递第一个参数,是一个body对象等形式才能验证,如果是一个数据就无法验证)
@Post()
async create(@Body(new ValidationPipe()) createBodyDto: CreateBodyDto ) : CreateBodyDto {
  return createBodyDto;
}
在路由方法中使用

@UsePipes()装饰器从导入的@nestjs/common包。

@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createBodyDto: CreateBodyDto) : CreateBodyDto {
  return createBodyDto;
}
全局使用 (和上面的exceptionFilter异常过滤器使用一致方式,只是使用了useGlobalPipes)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
在模块中使用 (和上面的exceptionFilter异常过滤器使用一致方式)

import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}
4.2 转型实例
把字符串value转换成int类型数值
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
简单使用
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}
4.3内置的管道ValidationPipe

ValidationPipe从进口@nestjs/common包。

简单使用
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createBodyDto: CreateBodyDto ) : CreateBodyDto {
  return createBodyDto;
}
和自定义管道类似,就是可以传递一个对象属性,确认是否开启这些验证

transform:是否开启类转换器函数plainToClass()将我们的普通JavaScript参数对象转换为类型化对象,以便我们可以应用验证。

skipMissingProperties 如果设置为true,则验证程序将跳过验证对象中缺少的所有属性的验证。

其它验证规则等,查看官方API(链接地址:https://docs.nestjs.com/pipes)

下一节学习NestJS的Guards警卫,Interceptors拦截器,Custom decorators自定义装饰器
其它博客点击头像即可查看?
  • 1
    点赞
  • 1
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值