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

相关推荐
2402_8575893621 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊29 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso32 分钟前
Rust 快速入门(一)
开发语言·后端·rust
sco528233 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
寻找09之夏1 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
吾日三省吾码1 小时前
详解JVM类加载机制
后端
努力的布布1 小时前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU1 小时前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛2 小时前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala