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

NestJS中如何向拦截器传递变量或读取业务标记值

NestJS拦截器读取数据来源标记实现方案

需求背景

需要实现用户列表接口逻辑:

  • 优先读取缓存返回数据,缓存未命中时从数据库读取
  • 根据数据实际来源(缓存/数据库)设置对应响应头
  • 响应头设置逻辑统一交由拦截器处理,不耦合在控制器/服务层业务逻辑中

现有代码

控制器代码

@UseInterceptors(CacheOrServiceHeaderInterceptor)
@Get('users')
public async users(
    @Query('id') clinicianId: number,
    @Query('name') specialtyName: string,
){
  const setFlags: MetaFlags = {setCacheHeader: false, setServiceHeader: false};

  const data = await this.service.getUsers(id, name, setFlags);
  return data;
}

服务层代码

export interface MetaFlags {
  setServiceHeader: boolean
  setCacheHeader?: boolean,
}

// 省略类其他逻辑
public async getUsers(....){
  // 内部逻辑判断走缓存还是数据库
  // 命中缓存则设置 setCacheHeader = true
  // 走数据库查询则设置 setServiceHeader = true
}

原有拦截器代码

@Injectable()
export class CacheOrServiceHeaderInterceptor implements NestInterceptor {
  public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
        .handle()
        .pipe(
            map((data) => {
                const req = context.switchToHttp().getRequest();
                // 此处需要拿到实际的标记值判断设置对应头
                if(MetaFlags.setCacheHeader)
                // 设置缓存来源响应头
                else 
                // 设置数据库来源响应头
                req.res.set('x-api-key', 'pretty secure');
                return data;
            })
        );
  }
}

实现方案

最简便无侵入的实现方式是利用HTTP请求对象在整个请求生命周期共享的特性,将标记位挂载到请求实例上传递,全程不需要修改接口返回的数据结构:

  • 第一步:补全TS类型声明(JS项目可跳过这步),在项目任意.d.ts文件中添加类型扩展,避免TS类型报错:
// 若使用Express作为HTTP底层
declare module 'express' {
  interface Request {
    sourceFlags?: MetaFlags
  }
}

// 若使用Fastify作为HTTP底层则替换为以下声明
// declare module 'fastify' {
//   interface FastifyRequest {
//     sourceFlags?: MetaFlags
//   }
// }
  • 第二步:修改控制器代码,注入请求对象,初始化标记位后挂载到请求实例上,再传入服务层:
@UseInterceptors(CacheOrServiceHeaderInterceptor)
@Get('users')
public async users(
    @Req() req: Request, // 注入请求对象
    @Query('id') clinicianId: number,
    @Query('name') specialtyName: string,
){
  // 初始化标记位直接挂载到请求对象上
  req.sourceFlags = {setCacheHeader: false, setServiceHeader: false};
  // 传入标记位,服务层修改对象属性时因为引用传递,会直接修改req上挂载的值
  const data = await this.service.getUsers(id, name, req.sourceFlags);
  return data;
}
  • 第三步:服务层代码不需要做任何改动,原有修改传入setFlags属性的逻辑可以直接复用。
  • 第四步:修改拦截器逻辑,直接从请求对象上读取标记位判断设置响应头即可:
@Injectable()
export class CacheOrServiceHeaderInterceptor implements NestInterceptor {
  public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const res = context.switchToHttp().getResponse();
    return next.handle().pipe(
      tap(() => {
        const flags = req.sourceFlags;
        if (flags?.setCacheHeader) {
          // 设置缓存来源对应的响应头,示例:
          res.set('X-Data-Source', 'cache');
        } else if (flags?.setServiceHeader) {
          // 设置数据库来源对应的响应头,示例:
          res.set('X-Data-Source', 'database');
        }
        // 其他公共响应头可以在这里统一设置
        res.set('x-api-key', 'pretty secure');
      })
    );
  }
}

这里用tap操作符替代map,因为不需要修改返回的响应数据,只需要做设响应头这类副作用操作,语义更准确,也没有额外的数据处理开销。

备选方案

如果不想在请求对象上挂载自定义属性,也可以调整服务层返回结构,将标记位和业务数据一起返回:

// 服务层返回结构调整为
return {
  data: 用户列表数据,
  meta: {
    setCacheHeader: true/false,
    setServiceHeader: true/false
  }
}

拦截器中拿到返回值后,先读取meta中的标记位设置响应头,再把data字段返回给客户端即可。这种方式的缺点是需要修改服务层、控制器的返回逻辑,侵入性更高。


内容的提问来源于stack exchange,提问作者salt-pepper

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:31:35