You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nest.js任意位置获取请求遇问题:请求作用域致onModuleInit失效

问题背景

我需要在调用链深处的Service中获取Request对象,但不想通过参数传递(这会带来大量修改)。我尝试使用请求作用域提供者实现该需求:

@Injectable({ scope: Scope.REQUEST })
export class FetcherService {
  constructor(
    private defaultProductFetcher: DefaultProductFetcher,
    private loopbackFetcher: LoopbackFetcher,
    @Inject(REQUEST) private readonly request: Request,
  ) {}

  // ...use this.request here for proxying headers to other services
}

但此举却导致应用中所有Service的onModuleInit方法无法被调用,进而导致应用无法正常运行。现提出以下问题:

  1. 为何所有onModuleInit方法都会受到影响?
  2. 是否存在更优的获取请求信息的方式?

问题1解答

当你把FetcherService标记为请求作用域(Scope.REQUEST)后,如果其他单例作用域的Service依赖了它,这些单例Service会被强制降级为请求作用域。

NestJS里,单例Service会在应用启动时初始化,此时会触发onModuleInit钩子。但如果单例被降级为请求作用域,它的实例只会在每次请求到来时才创建,应用启动阶段不会初始化这些Service实例,自然也就不会调用onModuleInit方法。如果你的应用里很多核心Service都依赖了FetcherService,就会出现所有onModuleInit都不执行的情况。

问题2解答

有两种更优的方案可以避免这种问题:

方案1:使用异步上下文(AsyncLocalStorage)

Node.js的async_hooks模块提供的AsyncLocalStorage,可以在整个请求生命周期中存储和获取请求上下文,不需要修改Service的作用域。

示例实现:

  1. 创建上下文管理Service:
import { Injectable } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';

@Injectable()
export class RequestContextService {
  private readonly als = new AsyncLocalStorage<Map<string, any>>();

  createContext(request: Request) {
    const context = new Map();
    context.set('request', request);
    return context;
  }

  run(context: Map<string, any>, callback: () => void) {
    this.als.run(context, callback);
  }

  getRequest(): Request {
    return this.als.getStore()?.get('request');
  }
}
  1. 创建拦截器,在请求进入时初始化上下文:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { RequestContextService } from './request-context.service';

@Injectable()
export class RequestContextInterceptor implements NestInterceptor {
  constructor(private readonly contextService: RequestContextService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const ctx = this.contextService.createContext(request);
    return this.contextService.run(ctx, () => next.handle());
  }
}
  1. 在模块中全局注册拦截器,之后任何Service都可以注入RequestContextService并调用getRequest()获取请求对象,所有Service依然保持单例作用域,onModuleInit正常执行。

方案2:使用请求作用域的中间Service

不要直接让核心单例Service依赖请求作用域的FetcherService,而是创建一个仅负责获取请求上下文的请求作用域Service,然后在需要使用请求信息的地方,通过ModuleRef动态获取这个请求作用域Service的实例,避免单例Service被降级。

示例:

  1. 创建请求作用域的上下文Service:
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class RequestScopedContext {
  constructor(@Inject(REQUEST) public readonly request: Request) {}
}
  1. 在需要使用请求信息的Service中,注入ModuleRef来动态获取实例:
import { Injectable, ModuleRef } from '@nestjs/common';
import { RequestScopedContext } from './request-scoped-context.service';

@Injectable()
export class FetcherService {
  constructor(private readonly moduleRef: ModuleRef) {}

  async fetchProduct() {
    const context = await this.moduleRef.resolve(RequestScopedContext);
    const request = context.request;
    // 使用request处理逻辑
  }
}

这种方式下,FetcherService依然是单例,onModuleInit正常执行,只有在实际需要请求信息时才会创建请求作用域的上下文实例。


内容的提问来源于stack exchange,提问作者comonadd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 00:57:47