如何在处理请求的Service中获取所属模块名称?含全局模块场景
NestJS 跨模块获取当前请求所属业务模块名实现方案
核心说明
Execution Context仅提供当前请求链路中正在执行的控制器、路由处理器、入参等运行时信息,没有内置的业务模块元数据映射能力,因此无法直接通过官方API拿到所属业务模块名称,需要结合自定义元数据和异步上下文存储实现。
实现步骤
1. 自定义模块标记装饰器
用于给业务模块/控制器打模块名标记,基于NestJS元数据能力实现:
import { SetMetadata } from '@nestjs/common'; export const MODULE_NAME_KEY = 'module_name'; export const ModuleMeta = (moduleName: string) => SetMetadata(MODULE_NAME_KEY, moduleName);
2. 实现请求上下文存储
用Node.js原生异步本地存储实现跨调用栈的请求数据传递,适配Service层的多层调用场景:
// src/common/request-context.ts import { AsyncLocalStorage } from 'async_hooks'; export const requestContext = new AsyncLocalStorage<{ moduleName: string; // 可扩展存储其他请求维度数据,如请求ID、用户信息等 }>();
3. 实现全局拦截器注入模块名
在请求入口处提取控制器上标记的模块名,存入异步上下文:
// src/common/module-name.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { MODULE_NAME_KEY } from './module-meta.decorator'; import { requestContext } from './request-context'; import { Observable } from 'rxjs'; @Injectable() export class ModuleNameInterceptor implements NestInterceptor { constructor(private reflector: Reflector) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { // 提取控制器上标记的模块名,也可扩展支持从模块类上提取 const moduleName = this.reflector.get<string>(MODULE_NAME_KEY, context.getClass()) || 'unknown'; // 绑定上下文传递给后续调用链 return new Observable(observer => { requestContext.run({ moduleName }, () => { next.handle().subscribe({ next: val => observer.next(val), error: err => observer.error(err), complete: () => observer.complete(), }); }); }); } }
4. 全局注册拦截器
在应用启动时注册全局拦截器,确保所有请求都走模块名注入逻辑:
// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ModuleNameInterceptor } from './common/module-name.interceptor'; import { Reflector } from '@nestjs/core'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalInterceptors(new ModuleNameInterceptor(app.get(Reflector))); await app.listen(3000); } bootstrap();
5. 业务层使用示例
给业务控制器标记模块名
// src/user/user.controller.ts import { Controller, Get } from '@nestjs/common'; import { ModuleMeta } from '../common/module-meta.decorator'; import { CommonService } from '../common/common.service'; @Controller('user') @ModuleMeta('user-module') // 标记当前控制器所属业务模块名 export class UserController { constructor(private commonService: CommonService) {} @Get() getList() { // 调用全局通用Service return this.commonService.getList(); } }
全局通用Service中获取模块名
// src/common/common.service.ts import { Injectable } from '@nestjs/common'; import { requestContext } from './request-context'; @Injectable() export class CommonService { getList() { // 直接从异步上下文获取当前请求所属业务模块名 const currentModuleName = requestContext.getStore()?.moduleName || 'unknown'; console.log('调用当前通用服务的业务模块为:', currentModuleName); // 后续业务逻辑 return []; } }
注意事项
- 该方案默认适配HTTP请求场景,若涉及微服务消息消费、定时任务等非请求场景,需要对应调整上下文注入逻辑
- 若Service中存在脱离请求上下文的异步调用(如未等待的异步任务、setTimeout回调),需要提前取出模块名手动传递,或绑定异步上下文
- 若需要给
@Module装饰的模块类直接打标记,只需调整拦截器中通过ModuleRef查找控制器所属模块的逻辑即可
内容的提问来源于stack exchange,提问作者Vladimir Golub
相关产品推荐
相关产品推荐

