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

Loopback v3中RangeError:调用栈溢出问题排查求助

问题根源:重复绑定全局观察者导致无限循环/栈溢出

你的问题核心出在每次请求进来时都会重复给PersistedModel绑定access观察者,而且这个观察者会在所有模型操作(包括你中间件里的User.findById)时触发,最终形成循环调用,导致调用栈不断累积直到超出上限。

具体分析

  1. 重复绑定观察者:每次带accessToken的请求进入,你的中间件都会执行PM.observe("access", ...),这意味着每处理一个请求就会多绑定一次全局的access观察者。随着请求量增加,观察者数量会越来越多,每次模型操作都会触发成百上千个观察者回调,直接撑爆调用栈。
  2. 循环调用触发:当你在中间件里调用User.findById时,这个模型查询操作会触发PersistedModel的access观察者,而观察者回调的执行又可能间接引发新的模型操作,加上重复绑定的观察者,最终形成无限递归调用。

解决方向&代码修正

方案1:将全局观察者绑定移到应用启动时,只绑定一次

不要在请求中间件里动态绑定观察者,而是在应用启动入口(比如server/server.js)里只绑定一次:

// server/server.js - 应用启动完成后添加全局观察者
module.exports = function(app) {
  // 其他启动逻辑...
  
  const PM = app.registry.getModel("PersistedModel");
  PM.observe("access", function(ctx, next) {
    // 仅当请求上下文存在accessToken时才赋值
    if (ctx.req?.accessToken?.data) {
      ctx.options.data = ctx.req.accessToken.data;
    }
    next();
  });
};

然后修改中间件,只负责加载当前用户,不再绑定观察者:

// server/middleware/store-current-user.js
module.exports = function (options) { 
  return function storeCurrentUser(req, res, next) { 
    if (!req.accessToken) { 
      return next(); 
    }
    app.models.User.findById(req.accessToken.userId, function (err, user) { 
      if (err) { 
        return next(err); 
      } 
      if (!user) { 
        return next(new Error('No user with this access token was found.')); 
      }
      // 将用户信息挂载到req上,方便其他逻辑使用
      req.currentUser = user;
      next(); 
    }); 
  }; 
};

方案2:放弃全局观察者,直接在请求上下文传递数据

如果不需要给所有模型操作传递accessToken.data,可以直接把数据挂载到req对象上,在需要的远程方法或模型钩子中通过ctx.req获取:

// 中间件里直接挂载数据
module.exports = function (options) { 
  return function storeCurrentUser(req, res, next) { 
    if (!req.accessToken) { 
      return next(); 
    }
    // 直接把accessToken数据挂载到req上
    req.tokenData = req.accessToken.data;
    // 加载用户信息(如果业务需要)
    app.models.User.findById(req.accessToken.userId, function (err, user) { 
      if (err) { 
        return next(err); 
      } 
      if (!user) { 
        return next(new Error('No user with this access token was found.')); 
      }
      req.currentUser = user;
      next(); 
    }); 
  }; 
};

在远程方法中使用示例:

// 某模型的远程方法
MyModel.myRemoteMethod = function(ctx, callback) {
  const tokenData = ctx.req.tokenData;
  const currentUser = ctx.req.currentUser;
  // 业务逻辑处理...
};

MyModel.remoteMethod('myRemoteMethod', {
  accepts: [{arg: 'ctx', type: 'object', http: {source: 'context'}}],
  // 其他配置...
});

方案3:针对特定模型绑定观察者(而非所有PersistedModel)

如果只有部分模型需要这个数据,不要给PersistedModel加全局观察者,而是给单个模型单独绑定,且只绑定一次:

// common/models/my-model.js - 在目标模型文件中添加观察者
module.exports = function(MyModel) {
  MyModel.observe('access', function(ctx, next) {
    if (ctx.req?.accessToken?.data) {
      ctx.options.data = ctx.req.accessToken.data;
    }
    next();
  });
};

额外注意点

  • 你的原中间件存在异步逻辑处理不严谨:User.findById是异步操作,但回调中调用next()时没有加return,虽然这里不会直接出问题,但养成return next()的习惯可以避免后续逻辑意外执行。
  • 日志中反复出现store-current-user.js:62:25,正是你绑定观察者的代码行,这直接证明了每次请求都在重复绑定观察者,是导致栈溢出的直接诱因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:31:52