【Nest全栈之旅】第三章:理解Nest请求流程,连贯你的知识点

从请求流程看Nest分层

内行人都知道,NestJS俗称SpringJS,又或者说面向angularJS编程,涉及到了很多概念如IOC控制反转、DI依赖注入、面向AOP编程等等,设计者的确参考了其他框架的设计思想,对Node企业级应用进行了标准化架构,解决了Node生态中架构缺失问题。

除了上述提到的概念之外,我们多少了解关于Nest部分模块,如常见的控制器controller服务service过滤器filter守卫guard拦截器interceptor管道Pipe中间件middleware,知道这些模块的基本使用,或许也产生了另个几个问题:

  1. 在实际应用中这些模块起到什么作用呢?
  2. 在一个实际的请求过来的时候,他们的执行顺序又是怎么样?
  3. 我应该在开发中怎么利用这些模块去搭建一套标准的项目架构?

这节就先从全局的视角来梳理一下Nest分层。

Nest分层

当一个请求打过来的时候,通过指定路由进入对应模块的Controller,这里维护着一些列的服务端函数,通过指定的method进入对应的Service层,后续进行操作db或者返回数据给客户端,完成大概的请求流程。

Nest在这个基础上,通过AOP的思想引入了过滤器filter守卫guard拦截器interceptor管道Pipe中间件middleware等,他们分别承担着不同的责任来解决不同的实际问题,形成了以下分层:

中间件(middleware)

请求最先经过的是中间件,Nest默认使用的是express中间件,而express拥有比较完善生态,在Nest中依然可以使用这些中间件来应用实际问题。 比如中间件可以用来解决以下问题:

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

比如使用Loggercors中间件解决日志和请求跨域问题,这也是为什么放在前面的原因。

使用Logger解决统计日志问题,本文主要在于抛砖引玉,关于如何使用Log4js,因篇幅问题不详细展开,自行查阅~

typescript 复制代码
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) {
    next();
    // 组装日志信息
    let logStr = `[method: ${req.method}; path: ${req.url}; 
      params: ${req.params}; query: ${req.query} status: ${res.statusCode};]`
    // 调用Log4js来统计日志信息
    // Logger.access(logStr);
  }
}

守卫(guard)

守卫,顾名思义就像古代的帝皇身边的贴身守卫,他们要保证没有授权的人是不能够接近目标。在Nest中,守卫的职责也有单一的责任,就是用来处理授权问题(如权限、角色控制),决定是否可以请求某个路由。

守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。

由于中间件无法知道调用next之后将会由哪个程序进行处理,而守卫可以访问ExecutionContext从而知道接下来要执行哪些操作。

typescript 复制代码
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class PersonGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    console.log('进入守卫');
    
    return true;
  }
}

拦截器(interceptor)

从上图中可以看到,controller方法前后都可以能够被interceptor拦截到,所以可以在处理函数之前或之后做以下处理操作:

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

下面演示操作函数返回的结果:

typescript 复制代码
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, map, timeout } from "rxjs";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor{
  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<any>{
    console.log('进入拦截器');
    
    return next.handle().pipe(
      map((value) => {
        console.log('value: ' + JSON.stringify(value));
      })
    );
  }
}

管道(Pipe)

管道的主要职责在于两种:

  • 转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数)
  • 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常

管道验证的参数会由controller服务处理函数调用的时候进行处理,Nest会在调用路由方法之前插入管道拦截方法的参数进行验证或转换,处理好之后再进行调用原方法。

内置管道

Nest 自带九个开箱即用的管道,即

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe
  • ParseFilePipe

当然,我们可以自定义管道对参数进行验证和转换,必要的时候抛出指定的异常:

typescript 复制代码
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from "@nestjs/common";

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    console.log('进入管道', value, metadata);
    
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

关于管道的验证器有很多种,上面演示的是在单独的路由方法中验证指定的参数,当需要验证的参数有多个的时候,显然会变的臃肿,这时候采用DTO方式进行验证,并且在全局注册ValidationPipe对所有的绑定DTO的方法进行统一验证。

过滤器(filter)

从上图可以看到,异常过滤器是包含着几个模块,表示其他的拦截器或者守卫中抛出异常,异常过滤器都将会捕获到。

Exception filters异常过滤器可以捕获在后端接受处理任何阶段所跑出的异常,捕获到异常后,然后返回处理过的异常结果给客户端(比如返回错误码,错误提示信息等等)。

我们需要再请求异常的时候统一返回相同格式的异常,而不是统一的500服务端错误,基于这个需求可以自定义HttpException过滤器:

typescript 复制代码
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common";
import { Request, Response } from "express";

@Catch(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();
        const message = exception.message;
        let resMessage: string | Record<string, any> = exception.getResponse();
        console.log('进入异常过滤器');
        
        if (typeof resMessage === 'object') {
            resMessage = resMessage.message
        }

        response.status(status).json({
            message: resMessage || message,
            success: false,
            path: request.url,
            status
        });
    }
}

Nest中,为了减少开发者手动定义过滤器,内置了多种请求异常过滤器,开发者可以自行抛出:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

其他异常

除了HttpExceptionFilter之外,其他所有的异常都应该被捕获,那么可自定义AllExceptionsFilter,并且在全局中进行绑定。

总结

梳理了Node分层之后,对整体的请求流程有了清晰的路线,在发送请求到响应请求,经过了层层校验和转换,最终回到客户端。 接下来的就是去深入每一个环节,在实际企业应用中完善应用场景。

相关推荐
耶啵奶膘27 分钟前
uniapp-是否删除
linux·前端·uni-app
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
王哈哈^_^2 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
cs_dn_Jie2 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic3 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿3 小时前
webWorker基本用法
前端·javascript·vue.js