004.nestjs 5个核心组件的理解与顺序

49 阅读4分钟

主要介绍NestJS的5个基本概念以及他们的流程

  1. Middleware 中间件
  2. Exception filter 异常过滤器
  3. Pipes 管道
  4. Guards 守卫
  5. Interceptors 拦截器

Middleware 中间件

引用官网一张图:

图片来自于nestjs

由此可见,中间件这个顺序是在请求路由之前的,也是最早的。

打个比方:大楼前的门卫室

典型用途:记录日志-路由请求时间,IP,地址

注意点:必须调用next() 传递给下一个处理器

执行时机:最早执行,在路由匹配之前

代码事例:

// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next(); // 注意
  }
}

// 注册中间件
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*'); // 应用到所有路由
  }
}

Guards(守卫)

继续引用官网的图片

guards图片

打个比方:类似保镖的职能,检查有没有权限进入

典型用途:身份认证(JWT Token)验证、IP白/黑名单、限流等功能

执行时机:在middleware之后,interceptor之前

以下是JWT验证代码事例

@Injectable()
export class JwtAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean | Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers['authorization'];
    
    if (!token) {
      throw new UnauthorizedException('需要登录');
    }
    
    // 验证token
    const user = this.verifyToken(token);
    request.user = user; // 注入用户信息
    
    return true; // true=允许访问, false=拒绝(403)
  }
}




// 1.全局注入

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

// 2.导入模块
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard ,
    },
  ],
})
export class AppModule {}

Interceptors(拦截器) 双向执行

interceptors

主要作用:在方法前后进行额外处理

打个比方: 录音笔,记录前后的所有的过程

典型用途: 统一响应格式、性能监控(记录执行时间)、日志记录

执行时机: 前置:在pipe之前,后置:在controller返回之后


@Injectable()
export class ResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const now = Date.now();
    
    return next.handle().pipe(
      map(data => ({
        code: 200,
        data,
        message: 'success',
        timestamp: new Date().toISOString(),
        duration: `${Date.now() - now}ms` // 计算执行时间
      }))
    );
  }
}
// 1.全局注入
app.useGlobalInterceptors(new ResponseInterceptor());


// 2.导入模块

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

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: ResponseInterceptor,
    },
  ],
})
export class AppModule {}

全局日志事例代码:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap, catchError } from "rxjs/operators";
import { Request, Response } from "express";

/**
 * 日志拦截器
 * 在请求处理前后记录日志
 * 执行顺序: 请求进入 → Before → 控制器处理 → After → 响应返回
 */
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(LoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest<Request>();
    const response = context.switchToHttp().getResponse<Response>();
    const method = request.method;
    const url = request.url;
    const body = request.body;
    const query = request.query;
    const params = request.params;
    const userAgent = request.headers["user-agent"] || "unknown";
    const ip = this.getClientIp(request);

    // 请求进入时的日志 (Before)
    const now = Date.now();
    this.logger.log(
      `[请求进入] ${method} ${url} - IP: ${ip} - UA: ${userAgent}`
    );

    // 如果有请求体,记录请求参数
    if (Object.keys(body || {}).length > 0) {
      this.logger.debug(`[请求参数 Body] ${JSON.stringify(body)}`);
    }
    if (Object.keys(query || {}).length > 0) {
      this.logger.debug(`[请求参数 Query] ${JSON.stringify(query)}`);
    }
    if (Object.keys(params || {}).length > 0) {
      this.logger.debug(`[请求参数 Params] ${JSON.stringify(params)}`);
    }

    return next.handle().pipe(
      tap((data) => {
        // 请求处理完成后的日志 (After)
        const duration = Date.now() - now;
        const statusCode = response.statusCode;

        this.logger.log(
          `[请求完成] ${method} ${url} - 状态码: ${statusCode} - 耗时: ${duration}ms`
        );

        // 可选: 记录响应数据 (注意: 生产环境可能不需要记录响应体)
        if (process.env.NODE_ENV === "development") {
          this.logger.debug(
            `[响应数据] ${JSON.stringify(data).substring(0, 200)}`
          );
        }
      }),
      catchError((error) => {
        // 捕获错误并记录
        const duration = Date.now() - now;
        const errorMessage =
          error instanceof Error ? error.message : String(error);
        this.logger.error(
          `[请求失败] ${method} ${url} - 耗时: ${duration}ms - 错误: ${errorMessage}`
        );
        throw error; // 重新抛出错误,让异常过滤器处理
      })
    );
  }

  /**
   * 获取客户端真实 IP
   * 支持代理场景
   */
  private getClientIp(request: Request): string {
    const forwarded = request.headers["x-forwarded-for"];
    if (forwarded) {
      const ips = Array.isArray(forwarded)
        ? forwarded[0]
        : forwarded.split(",")[0];
      return ips.trim();
    }

    const realIp = request.headers["x-real-ip"];
    if (realIp) {
      return Array.isArray(realIp) ? realIp[0] : realIp;
    }

    return request.ip || request.socket.remoteAddress || "unknown";
  }
}

// 1.全局注入
app.useGlobalInterceptors(new LoggingInterceptor());


// 2.导入模块

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

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}

与middleware的区别就是,拦截器记录的日志记录用于更详细的开发日志

Pipes 管道

pipe管道

主要作用: 数据转换,数据验证

执行时机: 在参数传给控制器之前执行

NestJS内置管道:

管道名作用
ValidationPipe验证DTO
ParseIntPipe转换为整数
ParseFloatPipe转换为浮点数
ParseBoolPipe转换为布尔值
ParseArrayPipe转换为数组
ParseUUIDPipe转换为UUID
ParseEnumPipe转换为数组
DefaultValuePipe设置默认值
ParseFilePipe转换为文件
ParseDatePipe转换为日期

都是从@nestjs/common导出

事例:

  evvent(@Query("bool", new DefaultValuePipe(true), ParseBoolPipe) bool?: boolean)
  {
  
  }

ClassValidator 类别验证器:

参考链接 : validation 验证器

npm i --save class-validator class-transformer

用法:


import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  age: number;

  @IsString()
  breed: string;
}

// 1.全局注入

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

// 2 导入模块

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

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}


Exception filters 异常过滤器

exception filters

主要作用: 最后的兜底处理

打个比方:统一错误响应格式,记录错误日志

执行时机: 任何阶段抛出异常时执行

内置异常: 在 @nestjs/common李敏

异常类别
HttpException
BadRequestException
UnauthorizedException
NotFoundException
ForbiddenException
...还有很多 就不一一列举了 可参考 异常记录

代码案例:


import { ExceptionFilter, Catch, ArgumentsHost, 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();

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

// 1.全局注入
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

// 2.导入模块


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

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}


Custome decorator 自定义装饰器

Nest提供的参数装饰器 (Param decorators):

名字引用
@Request(), @Req()req
@Response(),@Res()res
@Nest()nest
@Session()req.session
@Param(param?:string)req.params/req.params[param]
@Boby(param?:string)req.body/req.body[param]
@Query(param?:string)req.query/req.query[param]
@Header(param?:string)req.headers/req.headers[param]
@IP()req.ip
@HostParamreq.hosts

事例代码:


import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);


// 使用
@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

总结

不同点

机制主要作用执行时机典型用途
Middleware最早,路由前最早,路由前日志、cors
Guards权限、认证判断控制器前鉴权、橘色控制
Interceptor扩展、包装请求响应
AOP 面向切面
控制器前后日志、统一响应
Pipe数据转换与校验控制器参数绑定前DTO校验、转换
Exception Filter统一异常处理异常被抛出时错误格式化

相同点

  1. 都支持全局、模块、控制器、方法级别的注册
  2. 全局注册方式相同
    1. useGlobalxxx
    2. providers:[]
  3. 都是用于增强应用到可维护性和可扩展性

正常请求流程

请求流程:
客户端 
  
Middleware (中间件)  最早执行,在路由之前
  
Guards (守卫)
  
Interceptor - Before (拦截器前置)
  
Pipes (管道)
  
Controller (控制器处理)
  
Interceptor - After (拦截器后置)  可以转换响应数据
  
Exception Filters (异常过滤器)
  
客户端

好了,完结,撒花🎉🎉🎉,谢谢大家观看到这里