当前位置: 首页 > news >正文

5 种方式设置 CLS 上下文:nestjs-cls 中间件、Guard、拦截器与 @UseCls 装饰器终极对比

5 种方式设置 CLS 上下文:nestjs-cls 中间件、Guard、拦截器与 @UseCls 装饰器终极对比

【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJS's dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls

nestjs-cls 是一个与 NestJS 依赖注入无缝兼容的 continuation-local storage(异步上下文)模块。本文带你用一张对比表 + 5 分钟,彻底搞懂它的 5 种 CLS 上下文设置方式——中间件、Guard、拦截器、@UseCls 装饰器与手动 ClsService.run(),帮你选对方案、少走弯路。🚀

为什么需要"设置 CLS 上下文"?

CLS(continuation-local storage)让你在一次请求的生命周期内,跨回调、跨 Promise、跨服务共享数据——请求 ID、当前用户、多租户数据库连接、事务,统统不用层层传参。它类似其他语言的线程本地存储(thread-local storage),但专为 JavaScript 的异步模型设计。

核心原理一句话:先在某处调用一次上下文初始化(cls.run()cls.enter()),之后同一条调用链上的所有代码都能通过cls.set()/cls.get()读写同一份存储。🧠

详细说明见 docs/docs/01_introduction/03_how-it-works.md

而 nestjs-cls 官方提供了5 种初始化上下文的方式,各自适用于不同传输层(REST / GraphQL / WebSocket / 微服务)和非 Web 场景。

5 种 CLS 上下文设置方式:一张表看懂差异

方式适用传输层底层机制安全性上下文可用范围
1. ClsMiddleware 中间件REST ✔ / GQL ✔ / WS ✖ / 微服务 ✖run⭐⭐⭐全链路(Guard、拦截器、控制器、服务、过滤器)
2. ClsGuardREST ✔ / GQL ✔ / WS ✔ / 微服务 ✔enterWith⭐⭐全链路
3. ClsInterceptor 拦截器REST ✔ / GQL ✔ / WS ✔ / 微服务 ✔run⭐⭐⭐拦截器之后(Guard 中不可用,REST 下异常过滤器也不可用)
4. @UseCls 装饰器非 Web 请求(队列、定时任务等)run⭐⭐⭐装饰的方法及其调用链
5. ClsService.run() 手动调用任意场景run⭐⭐⭐你包裹的代码块

💡 关键点:中间件是 HTTP 请求最先经过的环节,所以REST/GraphQL 首选中间件;Guard 和拦截器是"全能选手",支持所有传输层;后两种则面向 Web 请求之外的场景。

方式一:ClsMiddleware 中间件——REST 与 GraphQL 的最优解

NestJS 中 HTTP 中间件是请求到达后最先执行的代码,因此初始化 CLS 上下文的理想位置。官方提供ClsMiddleware,可在挂载路由的next()调用前完成上下文建立。

自动挂载(最省事):ClsModule.forRoot()中传middleware: { mount: true },中间件会自动挂到所有路由。

手动挂载(要精细控制时):在模块的configure(consumer)里用consumer.apply(ClsMiddleware).forRoutes('自定义路由')只挂到指定路由;若与其他中间件有顺序冲突(例如 API 版本化),可直接在main.tsapp.use(new ClsMiddleware({...}).use)手动挂载。

⚠️ 注意:通过app.use()挂载时,不会继承forRoot()里的中间件配置,需要在构造函数中自行提供。

实现源码:packages/core/src/lib/cls-initializers/cls.middleware.ts

方式二:ClsGuard——全传输层的"第二选择"

ClsGuard严格说不是守卫,但它初始化上下文后,是请求命中的第二早的代码(仅次于中间件)。它通过AsyncLocalStorage#enterWith工作,因此WebSocket 网关、微服务等中间件无法触及的场景都能用

自动挂载:guard: { mount: true }

手动挂载:在根模块通过APP_GUARD提供ClsGuard作为全局守卫;或直接@UseGuards(ClsGuard)挂到控制器/Resolver 上。

⚠️ 安全提示:因为使用enterWith方法,ClsGuard存在一些 安全性考虑(例如上下文可能在await挂起期间被其他请求污染),生产环境建议评估后使用。

实现源码:packages/core/src/lib/cls-initializers/cls.guard.ts

方式三:ClsInterceptor 拦截器——用 run 机制的更稳替代

ClsInterceptor与 Guard 的差别在于:它使用AsyncLocalStorage#run包裹后续代码,而不是enterWith——run 是官方公认更安全的模式,上下文生命周期被严格限制在包裹范围内。

自动挂载:interceptor: { mount: true }

手动挂载:通过APP_INTERCEPTOR提供ClsInterceptor,或@UseInterceptors(ClsInterceptor)挂到具体控制器/Resolver(WebSocket 网关必须手动挂)。

⚠️ 代价:NestJS 的拦截器运行在守卫之后,所以这种方式下Guard 中拿不到 CLS 上下文(REST 控制器中异常过滤器也不行)。

实现源码:packages/core/src/lib/cls-initializers/cls.interceptor.ts

方式四:@UseCls 装饰器——Web 请求之外的场景

当你的代码运行在Web 请求上下文之外(队列消费者、定时任务、后台工作流),没有req对象可用,@UseCls()就是为你准备的:它声明式地把一个 async 方法包裹进cls.run()

@UseCls<[string]>({ generateId: true, setup: function (this: SomeService, cls: ClsService, value: string) { cls.set('some-key', 'some-value'); }, }) async startContextualWorkflow(value: string) { return this.otherService.doSomething(value); }

使用要点:

  • 📌 只能用于async 方法(返回 Promise),因为上下文初始化可以是异步的
  • 📌 没有请求对象,setup收到的是this实例、ClsService引用和方法参数
  • 📌setupidGenerator必须写成function而非箭头函数,否则this绑定失效

实现源码:packages/core/src/lib/cls-initializers/use-cls.decorator.ts

方式五:ClsService.run()——终极手动控制

前 4 种方式最终都是对ClsService#run(或#enter)的封装。当你需要最细粒度的控制——只包裹某一段代码、或者前面所有方式都不适用时,直接注入ClsService实例:

await this.cls.run({ id: crypto.randomUUID() }, async () => { this.cls.set('user', user); // 这段调用链内所有代码都能读到 'user' return this.orderService.create(); });

这是"万能兜底"方案,也是理解前面 4 种方式如何工作的钥匙。🔑

ClsService核心接口:packages/core/src/cls.service.ts

如何选择:30 秒决策清单

  1. REST / GraphQL(Nest ≥ 10 的 GQL)?→ 用ClsMiddleware+mount: true,最标准 ✅
  2. WebSocket、微服务或其他传输层?→ 用ClsGuard(方便)或ClsInterceptor(更安全)
  3. Guard 里必须用 CLS 吗?→ 是:中间件或ClsGuard;否:优先ClsInterceptor
  4. 队列 / 定时任务 / 脚本等非请求场景?@UseCls()装饰器
  5. 只想包裹一段逻辑 / 以上都不合适?cls.run()手动包裹

⚠️ GraphQL 额外提醒:一个 GQL 请求可能包含多个查询,拦截器/Guard 可能多次触发,请确保setup里的操作是幂等的(推荐setIfUndefined())。

参考

  • 官方文档(设置上下文章节):docs/docs/02_setting-up-cls-context/index.md
  • 各方式详解:中间件 · Guard · 拦截器 · 装饰器 · 手动实例
  • 兼容性矩阵:docs/docs/05_considerations/02_compatibility.md
  • 核心实现:packages/core/src/lib/cls-initializers/packages/core/src/cls.service.ts

【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJS's dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

http://www.cnnetsun.cn/news/4190105.html

相关文章:

  • Node.js 全栈 API 设计与 GraphQL 实:版本升级最怕忽略什么
  • AgentHazard基准:评估计算机操作型AI智能体安全性的关键挑战与实践
  • 数学建模竞赛实战:从校赛到国赛的降维策略与团队协作
  • 选 v2_1000 还是 clean_3000?minimax-h3-spatial-physics-lora 两大版本对比测评
  • quadtree-js快速上手教程:5分钟安装并跑通你的第一个四叉树
  • STM32以太网实战:从MII/RMII接口到LWIP排错全解析
  • Web安全入门:从查看源代码到漏洞挖掘的实战指南
  • vue-mc Model 完全指南:defaults、mutations、validation 三大核心概念详解
  • 排队论模型:从数学建模到仿真优化的完整指南
  • 告别Rust冗余Ok()包裹:fehler新手完全指南与5个入门技巧
  • RPCS3 汉化补丁手把手安装教程:不再吃字符,中文畅玩 PS3 经典
  • TransPixar 安装指南:让 RGBA 视频生成在你自己的机器上跑起来
  • 华为S5720交换机密码修改与安全配置全流程实操指南
  • AI编程助手上下文选择策略:双智能体消融实验与工程实践
  • C++类模板:从通用蓝图到可变参数模板的深度解析与实践
  • 深入 cdk-constructs 构建原理:jsii 多语言支持与 cdkdx 打包完整流程
  • smallpath Blog图片优化流水线:七牛上传+WebP自动转换,省流量只需3行配置
  • 不止于JS导入:用responsive-loader查询参数打造CSS响应式背景图
  • synology-spk-repo.json是怎么生成的?homebridge-syno-spk官方SPK源工作原理与开源贡献指南
  • Minimus云存储揭秘:Firestore天气应用按用户隔离城市列表的完整教程
  • 美赛D题深度复盘:如何将团队合作量化建模与策略优化
  • 美赛B题建模实战:从沙堡持久性问题看交叉学科建模心法
  • C++函数模板深度解析:从泛型编程原理到工程实践避坑指南
  • SDC命令详解:使用set_max_transition命令进行约束
  • AI代码助手静默语义失败:成因剖析与防御实践指南
  • DeepResearch-9K:AI智能体深度研究能力的标准化评估基准
  • htop 主题定制:改 3 个开关,默认界面一眼看清谁在吃 CPU
  • AI编码代理的“自信且错误”陷阱:静默语义失败与防御策略
  • TranAD对比8大基线模型:LSTM_AD、OmniAnomaly、USAD、GDN等异常检测算法实测分析
  • 应广PMS132B单片机入门:从寄存器操作到点灯实战