Loopback Interceptor值被其他请求覆盖,实例复用问题求助
问题根源
你的问题核心出在两个点:
- 错误使用类原型存储请求数据:你把
token、jwt等请求专属数据存在了invocationCtx.targetClass.prototype上——这是控制器类的共享原型空间,所有请求对应的控制器实例都会共用这里的属性,并发请求自然会互相覆盖。 - 对拦截器实例的误解: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
相关产品推荐
相关产品推荐

