nestjs[一例看懂中间件、守卫、管道、异常过滤器、拦截器]

nestjs入门学习规划:https://blog.csdn.net/lxy869718069/article/details/114028195

前置说明

从客户端发送一个post请求,路径为:/user/login,请求参数为:{userinfo: ‘xx’,password: ‘xx’},到服务器接收请求内容,触发绑定的函数并且执行相关逻辑完毕,然后返回内容给客户端的整个过程大体上要经过如下几个步骤:
在这里插入图片描述

项目示例:

项目需要包支持:

npm install --save rxjs xml2js class-validator class-transformer

rxjs 针对JavaScript的反应式扩展,支持更多的转换运算
xml2js 转换xml内容变成json格式
class-validator、class-transformer 管道验证包和转换器

第一步

建立user模块:模块内容结构:
在这里插入图片描述
user.controller.ts文件
在这里插入图片描述
user.module.ts文件
在这里插入图片描述
user.service.ts文件

在这里插入图片描述
user.login.dto.ts文件
在这里插入图片描述
app.module.ts文件
在这里插入图片描述

第二步

新建shared文件夹里面分别建立对应的文件夹以及文件:
中间件(middleware) — xml.middleware.ts
守卫(guard) — auth.guard.ts
管道(pipe) — validation.pipe.ts
异常过滤器(filters) — http-exception.filter.ts
拦截器(interceptor) — response.interceptor.ts
在这里插入图片描述
文件内容对应如下:
http-exception.filter.ts文件
在这里插入图片描述
auth.guard.ts文件
在这里插入图片描述
response.interceptor.ts文件
在这里插入图片描述
xml.middleware.ts文件
在这里插入图片描述
validation.pipe.ts文件
在这里插入图片描述
核心文件main.ts内容
在这里插入图片描述

第三步

所有内容已经准备完毕,现在需要开始进行操作了。

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

本例中:使用中间件让express支持xml请求并且将xml内容转换为json数组
代码如下:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
const xml2js = require('xml2js');
const parser = new xml2js.Parser();

@Injectable()
export class XMLMiddleware implements NestMiddleware {
  // 参数是固定的Request/Response/next,
  // Request/Response/next对应请求体和响应体和下一步函数
  use(req: Request, res: Response, next: Function) {
    console.log('进入全局xml中间件...');
    // 获取express原生请求对象req,找到其请求头内容,如果包含application/xml,则执行转换
    if(req.headers['content-type'] && req.headers['content-type'].includes('application/xml')){
      // 监听data方法获取到对应的参数数据(这里的方法是express的原生方法)
      req.on('data', mreq => {
        // 使用xml2js对xml数据进行转换
        parser.parseString(mreq,function(err,result){
          // 将转换后的数据放入到请求对象的req中
          console.log('parseString转换后的数据',result);
          // 这里之后可以根据需要对result做一些补充完善
          req['body']= result;
        })
      })
    }
    // 调用next方法进入到下一个中间件或者路由
    next();
  }
}
注册方式

1、全局注册:
在main.ts中导入需要的中间件模块如:XMLMiddleware
然后使用 app.use(new XMLMiddleware().use)即可
2、模块注册:
在对应的模块中注册如:user.module.ts
在这里插入图片描述
注意:
同一路由注册多个中间件的执行顺序为,先是全局中间件执行,然后是模块中间件执行,模块中的中间件顺序按照.apply中注册的顺序执行

守卫是第二道关卡

守卫控制一些权限内容,如:一些接口需要带上token标记,才能够调用,守卫则是对这个标记进行验证操作的。
本例中代码如下:

import {Injectable,CanActivate,HttpException,HttpStatus,ExecutionContext,} from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
  // context 请求的(Response/Request)的引用
  async canActivate(context: ExecutionContext): Promise<boolean> {
    console.log('进入全局权限守卫...');
    // 获取请求对象
    const request = context.switchToHttp().getRequest();
    // 获取请求头中的token字段
    const token = context.switchToRpc().getData().headers.token;
    // 如果白名单内的路由就不拦截直接通过
    if (this.hasUrl(this.urlList, request.url)) {
      return true;
    }
    // 验证token的合理性以及根据token做出相应的操作
    if (token) {
      try {
        // 这里可以添加验证逻辑
        return true;
      } catch (e) {
        throw new HttpException(
          '没有授权访问,请先登录',
          HttpStatus.UNAUTHORIZED,
        );
      }
    } else {
      throw new HttpException(
        '没有授权访问,请先登录',
        HttpStatus.UNAUTHORIZED,
      );
    }
  };
  // 白名单数组
  private urlList: string[] = [
    '/user/login'
  ];

  // 验证该次请求是否为白名单内的路由
  private hasUrl(urlList: string[], url: string): boolean {
    let flag: boolean = false;
    if (urlList.indexOf(url) >= 0) {
      flag = true;
    }
    return flag;
  }
};
注册方式

1、全局注册:
在main.ts中导入需要的守卫模块如:AuthGuard
然后使用 app.useGlobalGuards(new AuthGuard()) 即可
2、模块注册:
在需要注册的controller控制器中导入AuthGuard
然后从@nestjs/common中导入UseGuards装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
在这里插入图片描述
注意:
同一路由注册多个守卫的执行顺序为,先是全局守卫执行,然后是模块中守卫执行

拦截器是第三道关卡

我想到自定义返回内容如:

{
    "statusCode": 400,
    "timestamp": "2020-01-14T08:06:45.265Z",
    "path": "/user/login",
    "message": "请求失败",
    "data": {
        "isNotIn": "账号不能为空"
    }
}

这个时候就可以使用拦截器来做一下处理了。
拦截器作用:

  1. 在函数执行之前/之后绑定额外的逻辑
  2. 转换从函数返回的结果
  3. 转换从函数抛出的异常
  4. 扩展基本函数行为
  5. 根据所选条件完全重写函数 (例如, 缓存目的)

拦截器的执行顺序分为两个部分:
第一个部分在管道和自定义逻辑(next.handle()方法)之前。
第二个部分在管道和自定义逻辑(next.handle()方法)之后。
如:在这里插入图片描述
在这里插入图片描述
中间多了个全局管道以及自定义逻辑,即只有路由绑定的函数有正确的返回值之后才会有next.handle()之后的内容

注册方式

1、全局注册:
在main.ts中导入需要的模块如:ResponseInterceptor
然后使用 app.useGlobalInterceptors(new ResponseInterceptor()) 即可
2、模块注册:
在需要注册的controller控制器中导入ResponseInterceptor
然后从@nestjs/common中导入UseInterceptors装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
在这里插入图片描述
注意:
同一路由注册多个拦截器时候,优先执行模块中绑定的拦截器,然后其拦截器转换的内容将作为全局拦截器的内容,即包裹两次返回内容如:

{ // 全局拦截器效果
    "statusCode": 0,
    "timestamp": "2020-01-14T08:20:06.159Z",
    "path": "/user/login",
    "message": "请求成功",
    "data": {
    	"pagenum": 1, // 模块中拦截器包裹效果
    	“pageSize": 10
        "list": []
    }
}
管道是第四道关卡

管道是请求过程中的第四个内容,主要用于对请求参数的验证和转换操作。
项目中使用class-validator class-transformer进行配合验证相关的输入操作内容
认识官方的三个内置管道
1.ValidationPipe:基于class-validator和class-transformer这两个npm包编写的一个常规的验证管道,可以从class-validator导入配置规则,然后直接使用验证(当前不需要了解ValidationPipe的原理,只需要知道从class-validator引规则,设定到对应字段,然后使用ValidationPipe即可)
2.ParseIntPipe:转换传入的参数为数字
在这里插入图片描述
如:传递过来的是/test?id=‘123’"这里会将字符串‘123’转换成数字123
3.ParseUUIDPipe:验证字符串是否是 UUID(通用唯一识别码,百度了解)
在这里插入图片描述
如:传递过来的是/test?id=‘xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx’"这里会验证格式是否正确,不正确则抛出错误,否则调用findOne方法
本例中管道使用如下:

import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform<any>{
  // value 是当前处理的参数,而 metatype 是属性的元类型
  async transform(value: any, { metatype }: ArgumentMetadata) {
    console.log('进入全局管道...');
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    // plainToClass方法将普通的javascript对象转换为特定类的实例
    const object = plainToClass(metatype, value);
    // 验证该对象返回出错的数组
    const errors = await validate(object);
    if (errors.length > 0) {
      // 将错误信息数组中的第一个内容返回给异常过滤器
      let errormsg = errors.shift().constraints;
      throw new BadRequestException(errormsg);
    }
    return value;
  }
  // 验证属性值的元类型是否是String, Boolean, Number, Array, Object中的一种
  private toValidate(metatype: any): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
注册方式

1、全局注册:
在main.ts中导入需要的模块如:ValidationPipe
然后使用 app.useGlobalPipes(new ValidationPipe()) 即可
2、模块注册:
在需要注册的controller控制器中导入ValidationPipe
然后从@nestjs/common中导入UsePipes装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可,管道还允许注册在相关的参数上如:@Body/@Query… 等
在这里插入图片描述
在这里插入图片描述
注意:
同一路由注册多个管道的时候,优先执行全局管道,然后再执行模块管道:

异常过滤器是所有抛出的异常的统一处理方案

简单来讲就是捕获系统抛出的所有异常,然后自定义修改异常内容,抛出友好的提示。

内置异常类

系统提供了不少内置的系统异常类,需要的时候直接使用throw new XXX(描述,状态)这样的方式即可抛出对应的异常,一旦抛出异常,当前请求将会终止。
注意每个异常抛出的状态码有所不同。如:
BadRequestException — 400
UnauthorizedException — 401
ForbiddenException — 403
NotFoundException — 404
NotAcceptableException — 406
RequestTimeoutException — 408
ConflictException — 409
GoneException — 410
PayloadTooLargeException — 413
UnsupportedMediaTypeException — 415
UnprocessableEntityException — 422
InternalServerErrorException — 500
NotImplementedException — 501
BadGatewayException — 502
ServiceUnavailableException — 503
GatewayTimeoutException — 504

本例中使用的是自定义的异常类,代码如下:

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

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  // exception 当前正在处理的异常对象
  // host 是传递给原始处理程序的参数的一个包装(Response/Request)的引用
  catch(exception: HttpException, host: ArgumentsHost) {
    console.log('进入全局异常过滤器...');
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    // HttpException 属于基础异常类,可自定义内容
    // 如果是自定义的异常类则抛出自定义的status 
    // 否则就是内置HTTP异常类,然后抛出其对应的内置Status内容
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    // 抛出错误信息
    const message =
      exception.message ||
      exception.message.message ||
      exception.message.error ||
      null;
    let msgLog = {
      statusCode: status, // 系统错误状态
      timestamp: new Date().toISOString(), // 错误日期
      path: request.url, // 错误路由
      message: '请求失败', 
      data: message // 错误消息内容体(争取和拦截器中定义的响应体一样)
    }
     // 打印错误综合日志
     Logger.error(
      '错误信息',
      JSON.stringify(msgLog),
      'HttpExceptionFilter',
    );
    response
      .status(status)
      .json(msgLog);
  }
}
注册方式

1、全局注册:
在main.ts中导入需要的模块如:HttpExceptionFilter
然后使用 app.useGlobalFilters(new HttpExceptionFilter()) 即可
2、模块注册:
在需要注册的controller控制器中导入HttpExceptionFilter
然后从@nestjs/common中导入UseFilters装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
在这里插入图片描述
注意:
同一路由注册多个管道的时候,只会执行一个异常过滤器,优先执行模块中绑定的异常过滤器,如果模块中无绑定异常过滤则执行全局异常过滤器

源码示例

https://gitee.com/twang-gitee/nest-study

nest入门学习步骤

https://blog.csdn.net/lxy869718069/article/details/114028195

  • 39
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
拦截器过滤器是在开发中常用的两种设计模式,用于在请求处理的不同阶段对请求进行处理和拦截。它们的区别主要体现在使用场景和功能上。 拦截器(Interceptor)是一种在请求处理的不同阶段进行拦截并插入自定义逻辑的机制。它通常用于对请求进行预处理或后处理,例如记录日志、验证权限、设置上下文环境等。拦截器可以在请求的开始、结束或异常抛出等时机进行拦截,并且可以被链式调用,即一个拦截器可以调用多个其他拦截器拦截器通常与框架或中间件紧密结合,在请求的处理过程中按照一定的顺序依次执行。 过滤器(Filter)是一种在请求到达目标处理程序之前或之后对请求进行过滤和处理的机制。它通常用于对请求进行预处理、过滤或修改,例如字符编码转换、参数校验、防止跨站点脚本攻击等。过滤器可以在请求被处理前进行处理(前置过滤器),也可以在请求被处理后进行处理(后置过滤器)。过滤器通常与Web容器(如Servlet容器)紧密结合,在请求的处理过程中按照一定的顺序依次执行。 总结来说,拦截器过滤器的主要区别在于使用场景和功能。拦截器更适合进行请求的预处理和后处理,而过滤器更适合对请求进行过滤和修改。拦截器一般与框架紧密结合,可以链式调用,而过滤器一般与Web容器紧密结合,按照一定的顺序依次执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值