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. ClsGuard | REST ✔ / 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.ts中app.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引用和方法参数 - 📌
setup与idGenerator必须写成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 秒决策清单
- REST / GraphQL(Nest ≥ 10 的 GQL)?→ 用
ClsMiddleware+mount: true,最标准 ✅ - WebSocket、微服务或其他传输层?→ 用
ClsGuard(方便)或ClsInterceptor(更安全) - Guard 里必须用 CLS 吗?→ 是:中间件或
ClsGuard;否:优先ClsInterceptor - 队列 / 定时任务 / 脚本等非请求场景?→
@UseCls()装饰器 - 只想包裹一段逻辑 / 以上都不合适?→
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),仅供参考