NestJS 中间件与拦截器:请求处理流程详解

在上一篇文章中,我们介绍了 NestJS 的认证与授权实现。本文将深入探讨 NestJS 的请求处理流程,包括中间件、拦截器、管道和异常过滤器的使用。

请求生命周期

在 NestJS 中,请求处理流程按以下顺序执行:

  1. 中间件(Middleware)
  2. 守卫(Guards)
  3. 拦截器(Interceptors)- 前置处理
  4. 管道(Pipes)
  5. 控制器(Controller)
  6. 服务(Service)
  7. 拦截器(Interceptors)- 后置处理
  8. 异常过滤器(Exception Filters)

中间件实现

1. 函数式中间件

// src/common/middleware/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';export function loggerMiddleware(req: Request, res: Response, next: NextFunction) {const { method, originalUrl, ip } = req;const userAgent = req.get('user-agent') || '';console.log(`[${method}] ${originalUrl} - ${ip} - ${userAgent}`);// 记录请求开始时间const start = Date.now();// 响应结束后记录耗时res.on('finish', () => {const duration = Date.now() - start;console.log(`[${method}] ${originalUrl} - ${res.statusCode} - ${duration}ms`);});next();
}

2. 类中间件

// src/common/middleware/auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';@Injectable()
export class AuthMiddleware implements NestMiddleware {constructor(private jwtService: JwtService) {}async use(req: Request, res: Response, next: NextFunction) {const authHeader = req.headers.authorization;if (!authHeader) {next();return;}try {const token = authHeader.split(' ')[1];const payload = await this.jwtService.verifyAsync(token);req['user'] = payload;next();} catch (error) {throw new UnauthorizedException('Invalid token');}}
}

3. 全局中间件

// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AuthMiddleware } from './common/middleware/auth.middleware';
import { loggerMiddleware } from './common/middleware/logger.middleware';@Module({// ... 其他配置
})
export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer) {consumer.apply(loggerMiddleware, AuthMiddleware).exclude({ path: 'auth/login', method: RequestMethod.POST },{ path: 'auth/register', method: RequestMethod.POST },).forRoutes('*');}
}

拦截器实现

1. 响应转换拦截器

// src/common/interceptors/transform.interceptor.ts
import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';export interface Response<T> {data: T;meta: {timestamp: string;status: number;message: string;};
}@Injectable()
export class TransformInterceptor<T>implements NestInterceptor<T, Response<T>> {intercept(context: ExecutionContext,next: CallHandler,): Observable<Response<T>> {const ctx = context.switchToHttp();const response = ctx.getResponse();return next.handle().pipe(map(data => ({data,meta: {timestamp: new Date().toISOString(),status: response.statusCode,message: 'Success',},})),);}
}

2. 缓存拦截器

// src/common/interceptors/cache.interceptor.ts
import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RedisService } from '../services/redis.service';@Injectable()
export class CacheInterceptor implements NestInterceptor {constructor(private redisService: RedisService) {}async intercept(context: ExecutionContext,next: CallHandler,): Promise<Observable<any>> {const request = context.switchToHttp().getRequest();const cacheKey = `cache:${request.url}`;// 尝试从缓存获取数据const cachedData = await this.redisService.get(cacheKey);if (cachedData) {return of(JSON.parse(cachedData));}// 如果没有缓存,执行请求并缓存结果return next.handle().pipe(tap(async response => {await this.redisService.set(cacheKey,JSON.stringify(response),60 * 5, // 5分钟缓存);}),);}
}

3. 性能监控拦截器

// src/common/interceptors/logging.interceptor.ts
import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { PrometheusService } from '../services/prometheus.service';@Injectable()
export class LoggingInterceptor implements NestInterceptor {constructor(private prometheusService: PrometheusService) {}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();const { method, url } = request;const start = Date.now();return next.handle().pipe(tap({next: () => {const duration = Date.now() - start;// 记录请求耗时this.prometheusService.recordHttpRequestDuration(method,url,duration,);},error: error => {const duration = Date.now() - start;// 记录错误请求this.prometheusService.recordHttpRequestError(method,url,error.status,duration,);},}),);}
}

管道实现

1. 验证管道

// src/common/pipes/validation.pipe.ts
import {PipeTransform,Injectable,ArgumentMetadata,BadRequestException,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';@Injectable()
export class ValidationPipe implements PipeTransform<any> {async transform(value: any, { metatype }: ArgumentMetadata) {if (!metatype || !this.toValidate(metatype)) {return value;}const object = plainToClass(metatype, value);const errors = await validate(object);if (errors.length > 0) {const messages = errors.map(error => ({property: error.property,constraints: error.constraints,}));throw new BadRequestException({message: 'Validation failed',errors: messages,});}return value;}private toValidate(metatype: Function): boolean {const types: Function[] = [String, Boolean, Number, Array, Object];return !types.includes(metatype);}
}

2. 转换管道

// src/common/pipes/parse-int.pipe.ts
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. "${value}" is not an integer.`,);}return val;}
}// src/common/pipes/parse-boolean.pipe.ts
@Injectable()
export class ParseBooleanPipe implements PipeTransform<string, boolean> {transform(value: string, metadata: ArgumentMetadata): boolean {if (value === 'true') return true;if (value === 'false') return false;throw new BadRequestException(`Validation failed. "${value}" is not a boolean.`,);}
}

异常过滤器

1. 全局异常过滤器

// src/common/filters/http-exception.filter.ts
import {ExceptionFilter,Catch,ArgumentsHost,HttpException,HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { LoggerService } from '../services/logger.service';@Catch()
export class HttpExceptionFilter implements ExceptionFilter {constructor(private logger: LoggerService) {}catch(exception: unknown, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse<Response>();const request = ctx.getRequest<Request>();const status =exception instanceof HttpException? exception.getStatus(): HttpStatus.INTERNAL_SERVER_ERROR;const message =exception instanceof HttpException? exception.getResponse(): 'Internal server error';// 记录错误日志this.logger.error(`${request.method} ${request.url}`,exception instanceof Error ? exception.stack : 'Unknown error','HttpExceptionFilter',);response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,message,});}
}

2. 业务异常过滤器

// src/common/filters/business-exception.filter.ts
import { Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { BusinessException } from '../exceptions/business.exception';
import { LoggerService } from '../services/logger.service';@Catch(BusinessException)
export class BusinessExceptionFilter extends BaseExceptionFilter {constructor(private logger: LoggerService) {super();}catch(exception: BusinessException, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse();const request = ctx.getRequest();const status = HttpStatus.BAD_REQUEST;// 记录业务错误日志this.logger.warn(`Business Exception: ${exception.message}`,exception.stack,'BusinessExceptionFilter',);response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,error: 'Business Error',message: exception.message,code: exception.code,});}
}

实践应用

1. 请求链路追踪

// src/common/middleware/trace.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';@Injectable()
export class TraceMiddleware implements NestMiddleware {use(req: Request, res: Response, next: NextFunction) {const traceId = req.headers['x-trace-id'] || uuidv4();req['traceId'] = traceId;res.setHeader('X-Trace-Id', traceId);next();}
}// src/common/interceptors/trace.interceptor.ts
@Injectable()
export class TraceInterceptor implements NestInterceptor {constructor(private logger: LoggerService) {}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();const traceId = request['traceId'];return next.handle().pipe(tap(data => {this.logger.log(`[${traceId}] Response data: ${JSON.stringify(data)}`,'TraceInterceptor',);}),);}
}

2. 请求速率限制

// src/common/guards/throttler.guard.ts
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {protected getTracker(req: Record<string, any>): string {return req.ips.length ? req.ips[0] : req.ip;}
}// src/app.module.ts
import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';@Module({imports: [ThrottlerModule.forRoot({ttl: 60,limit: 10,}),],
})
export class AppModule {}

3. API 版本控制

// src/common/decorators/api-version.decorator.ts
import { SetMetadata } from '@nestjs/common';export const API_VERSION = 'api_version';
export const ApiVersion = (version: string) => SetMetadata(API_VERSION, version);// src/common/guards/api-version.guard.ts
@Injectable()
export class ApiVersionGuard implements CanActivate {constructor(private reflector: Reflector) {}canActivate(context: ExecutionContext): boolean {const version = this.reflector.get<string>(API_VERSION,context.getHandler(),);if (!version) {return true;}const request = context.switchToHttp().getRequest();const requestVersion = request.headers['api-version'];return version === requestVersion;}
}

写在最后

本文详细介绍了 NestJS 中的请求处理流程和各个组件的实现:

  1. 中间件的不同实现方式
  2. 拦截器的应用场景
  3. 管道的数据转换和验证
  4. 异常过滤器的错误处理
  5. 实际应用案例

在下一篇文章中,我们将探讨 NestJS 的微服务架构实现。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/497419.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

overleaf中文生僻字显示不正确,显示双线F

我是不想换全文字体的&#xff0c;只是一个生僻字显示不出来&#xff0c;就想要像word一样&#xff0c;把这个生僻字用包含这个生僻字的字体来显示就好了。 解决步骤&#xff1a; 1、使用如下宏包&#xff1a; \usepackage{xeCJK} %声明宏包&#xff0c;主要用于支持在XeTeX…

音视频入门基础:MPEG2-TS专题(24)——FFmpeg源码中,显示TS流每个packet的pts、dts的实现

音视频入门基础&#xff1a;MPEG2-TS专题系列文章&#xff1a; 音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;1&#xff09;——MPEG2-TS官方文档下载 音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;2&#xff09;——使用FFmpeg命令生成ts文件 音视频入门基础…

Boost之log日志使用

不讲理论&#xff0c;直接上在程序中可用代码&#xff1a; 一、引入Boost模块 开发环境&#xff1a;Visual Studio 2017 Boost库版本&#xff1a;1.68.0 安装方式&#xff1a;Nuget 安装命令&#xff1a; #只安装下面几个即可 Install-package boost -version 1.68.0 Install…

QWidget应用封装为qt插件,供其他qt应用调用

在之前的文章中,有介绍通过QProcess的方式启动QWidget应用,然后将其窗口嵌入到其他的qt应用中,作为子窗口使用.这篇文章主要介绍qt插件的方式将QWidget应用的窗口封装为插件,然后作为其他Qt应用中的子窗口使用. 插件优点: 与主程序为同一个进程,免去了进程间繁琐的通信方式,…

大数据-261 实时数仓 - 业务数据库表结构 交易订单、订单产品、产品分类、商家店铺、地域组织表

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; H…

Pyside6 在 pycharm 中的配置

打开文件->设置 找到 工具->外部工具 点击 号 创建一个外部工具 QtDesigner 名称:QtDesigner 程序&#xff1a;D:\miniconda\envs\ergoAI-qt\Lib\site-packages\PySide6\designer.exe 实参&#xff1a;$FileName$ 工作目录&#xff1a;$FileDir$ PyUIC 名称&#xf…

Linux系统编程——线程

目录 一、前言 二、线程 1、线程的理解 三、线程相关的接口 1、线程的创建 2、线程的等待 3、实验 四、总结 1、线程优点 2、线程缺点 3、线程异常 4、Linux下的进程与线程对比 一、前言 之前的文章中我们已经对进程相关的概念做了认识&#xff0c;从创建进程、子进…

SD ComfyUI工作流 对人物图像进行抠图并替换背景

文章目录 人物抠图与换背景SD模型Node节点工作流程工作流下载效果展示人物抠图与换背景 此工作流旨在通过深度学习模型完成精确的人物抠图及背景替换操作。整个流程包括图像加载、遮罩生成、抠图处理、背景替换以及最终的图像优化。其核心基于 SAM(Segment Anything Model)与…

微服务-1 认识微服务

目录​​​​​​​ 1 认识微服务 1.1 单体架构 1.2 微服务 1.3 SpringCloud 2 服务拆分原则 2.1 什么时候拆 2.2 怎么拆 2.3 服务调用 3. 服务注册与发现 3.1 注册中心原理 3.2 Nacos注册中心 3.3 服务注册 3.3.1 添加依赖 3.3.2 配置Nacos 3.3.3 启动服务实例 …

02-18.python入门基础一基础算法

&#xff08;一&#xff09;排序算法 简述&#xff1a; 在 Python 中&#xff0c;有多种常用的排序算法&#xff0c;下面为你详细介绍几种常见的排序算法及其原理、实现代码、时间复杂度以及稳定性等特点&#xff0c;并对比它们适用的场景。 冒泡排序&#xff08;Bubble Sor…

深度学习blog-卷积神经网络(CNN)

卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种广泛应用于计算机视觉领域&#xff0c;如图像分类、目标检测和图像分割等任务中的深度学习模型。 1. 结构 卷积神经网络一般由以下几个主要层组成&#xff1a; 输入层&#xff1a;接收…

三维扫描在汽车/航空行业应用

三维扫描技术应用范围广泛&#xff0c;从小型精密零件到大型工业设备&#xff0c;都能实现快速、准确的测量。 通过先进三维扫描技术获取产品和物体的形面三维数据&#xff0c;建立实物的三维图档&#xff0c;满足各种实物3D模型数据获取、三维数字化展示、3D多媒体开发、三维…

【Axure视频教程】中继器表格间传值

今天教大家在Axure制作中继器表格间传值的原型模板&#xff0c;可以将一个中继器表格里的行数据传递到另外一个中继器表格里&#xff0c;包括传值按钮在中继器内部和外部两中案例。 这个原型模板是用中继器制作的&#xff0c;所以使用也很简单&#xff0c;只需要在中继器表格里…

【测试】接口测试

长期更新好文&#xff0c;建议关注收藏&#xff01; 目录 接口规范接口测试用例设计postmanRequests 复习HTTP超文本传输协议 复习cookiesession 实现方式 1.工具 如postman ,JMeter&#xff08;后者功能更全&#xff09; 2.代码 pythonrequests / javahttpclient【高级】 接…

目录 1、常用系统数据类型 1. int或integer 2. tinyint 3. decimal[(p[,s])]或numeric[(p[,s])] 4. char(n) 5. varchar(n|max) 6. datetime 2、T-SQL创建表 3、T-SQL修改表 4、T-SQL表数据的操作 4.1 插入数据 4.2 修改数据 4.3 删除数据 5、删除表 1、常用系统…

【LLM】OpenAI 的DAY12汇总和o3介绍

note o3 体现出的编程和数学能力&#xff0c;不仅达到了 AGI 的门槛&#xff0c;甚至摸到了 ASI&#xff08;超级人工智能&#xff09;的边。 Day 1&#xff1a;o1完全版&#xff0c;开场即巅峰 12天发布会的开场即是“炸场级”更新——o1完全版。相比此前的预览版本&#x…

Redis缓存知识点汇总

Redis缓存知识点汇总 请先思考如下问题 1.Redis的缓存击穿&#xff0c;穿透&#xff0c;雪崩是什么意思&#xff1f;原因和解决方案有哪些&#xff1f; 2.Redis支持宕机数据恢复&#xff0c;他的持久化方式及其原理是什么&#xff1f; 3.如何保证双写一致性&#xff0c;即如何保…

Gitlab17.7+Jenkins2.4.91实现Fastapi/Django项目持续发布版本详细操作(亲测可用)

一、gitlab设置&#xff1a; 1、进入gitlab选择主页在左侧菜单的下面点击管理员按钮。 2、选择左侧菜单的设置&#xff0c;选择网络&#xff0c;在右侧选择出站请求后选择允许来自webhooks和集成对本地网络的请求 3、webhook设置 进入你自己的项目选择左侧菜单的设置&#xff…

仓颉编程笔记1:变量函数定义,常用关键字,实际编写示例

本文就在网页版上体验一下仓颉编程&#xff0c;就先不下载它的SDK了 基本围绕着实际摸索的编程规则来写的 也没心思多看它的文档&#xff0c;写的不太明确&#xff0c;至少我是看的一知半解的 文章提供测试代码讲解、测试效果图&#xff1a; 目录 仓颉编程在线体验网址&…

Linux 文件 I/O 基础

目录 前言 一、文件描述符&#xff08;File Descriptor&#xff09; 二、打开文件&#xff08;open 函数&#xff09; 三、读取文件&#xff08;read 函数&#xff09; 四、写入文件&#xff08;write 函数&#xff09; 五、关闭文件&#xff08;close 函数&#xff09; …