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

Loopback Interceptor值被其他请求覆盖,实例复用问题求助

问题根源

你的问题核心出在两个点:

  1. 错误使用类原型存储请求数据:你把token、jwt等请求专属数据存在了invocationCtx.targetClass.prototype上——这是控制器类的共享原型空间,所有请求对应的控制器实例都会共用这里的属性,并发请求自然会互相覆盖。
  2. 对拦截器实例的误解:LoopBack中通过Provider提供的拦截器默认是单例作用域,也就是整个应用只会创建一个拦截器实例,所有请求复用这个实例。但每个请求会生成独立的InvocationContext,这才是存储请求级数据的正确容器。
解决方案:用InvocationContext存储请求专属数据

每个请求的InvocationContext是完全独立的,你可以通过它绑定自定义数据,后续在控制器中从这个上下文里获取即可。

修改后的拦截器代码

import {Provider, Interceptor, InvocationContext, ValueOrPromise} from '@loopback/core';
import {RestBindings} from '@loopback/rest';
import jwtDecode from 'jwt-decode';

// 定义自定义绑定键,用于标识拦截器处理后的数据
export const INTERCEPTOR_REQUEST_DATA = 'interceptor.request.data';

export class ExtractTokenInterceptor implements Provider<Interceptor> {
  constructor() { }

  value(): Interceptor {
    return this.intercept.bind(this);
  }

  async intercept<T>(
    invocationCtx: InvocationContext,
    next: () => ValueOrPromise<T>,
  ) {
    const req: any = await invocationCtx.get(RestBindings.Http.REQUEST, {
      optional: true,
    });

    // 提取请求头数据
    const authorization = req.headers.authorization ?? null;
    const userName = req.headers['x-username'] ?? null;
    const token = authorization ? authorization.split(' ')[1] : null;
    const referer = req.headers.referer;
    const clientIp = req.headers['x-forwarded-for'];

    // 组装拦截器处理后的数据
    const requestData = {
      token,
      referer,
      clientIp,
      userName,
      jwt: null,
    };

    if (token) {
      const decodedJwt = jwtDecode(token);
      requestData.jwt = decodedJwt;

      // 仅对user1添加延迟逻辑
      if (decodedJwt.preferred_username === 'user1') {
        let a = 0;
        const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
        while (a < 20) {
          await timeout(1000);
          console.log(`[${a}] user is`, decodedJwt.preferred_username);
          a++;
        }
      }
    }

    // 将数据绑定到当前请求的InvocationContext
    invocationCtx.bind(INTERCEPTOR_REQUEST_DATA).to(requestData);

    const result = await next();
    return result;
  }
}

在控制器中获取处理后的数据

通过注入InvocationContext,从绑定的自定义键中取出数据:

import {inject, InvocationContext} from '@loopback/core';
import {get} from '@loopback/rest';
import {INTERCEPTOR_REQUEST_DATA} from './extract-token.interceptor';

export class SampleController {
  @get('/protected-endpoint')
  async protectedEndpoint(
    @inject(InvocationContext) ctx: InvocationContext,
  ) {
    // 获取拦截器处理后的请求数据
    const requestData = await ctx.get(INTERCEPTOR_REQUEST_DATA);
    return {
      currentUser: requestData.jwt?.preferred_username,
      clientIp: requestData.clientIp,
      token: requestData.token,
    };
  }
}

关键注意事项

  • 永远不要用targetClass.prototype存储请求相关数据,这是类级别的共享空间,并发场景下必然导致数据污染。
  • 拦截器实例是单例,但每个请求的InvocationContext是独立的,所有请求专属数据都应该存在这个上下文里。
  • 使用自定义绑定键(如INTERCEPTOR_REQUEST_DATA)比直接给InvocationContext加属性更规范,便于类型维护和代码可读性。

内容的提问来源于stack exchange,提问作者John Christian De Chavez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 23:12:26