5

创建新用户帐户时,我会创建 newUser,这是一个 Mongoose 模型实例,如下所示:

_events: Object
errors: undefined
isNew: false
save: function () {
arguments: null
caller: null
_doc: Object
  name: 'Joe Smith'
...

对象的实际数据在_doc属性中,尽管 getter 和 setter 存在,因此您可以运行:

user.name = 'Jane Doe' 

这将正常工作。我跑:

request.session.user = newUser;

将用户保存到会话中。到目前为止,一切都很好。

但是,在随后的请求中,request.session.user 似乎只是 _doc 中的内容。例如:

name: 'Joe Smith'

这很好,但这意味着我不能运行例如request.session.user.save()来保存更改。

我可以简单地制作一些中间件来查找与数据关联的用户。但我想更多地了解 Express 和 Mongoose 在这里所做的事情。

如何使 request.session.user 成为 Mongoose 模型?

更新:我当前的中间件黑客:

// Use as a middleware on routes which need users
// TODO: only needed due to request.session.user being saved weirdly
var rehydrateUser = function(request, response, next) {
  if ( request.session.user ) {
    var hydrated = request.session.user.save
    if ( ! hydrated ) {
      console.log('Rehydrating user...');
      models.User.findOne({ somefield: request.session.user.somefield }, function (err, user) {
        if ( err ) {
          request.session.error = 'User not found in DB!';
          request.redirect('/');
        } else {
          request.session.user = user;          
          console.log('Rehydrated user! All repos already being monitored.');
          next();
        }
      })
    } else {
      next();
    }
  } else {
    console.log('No user in session')
    next();
  }
}
4

3 回答 3

11

Express session 存储为单独的存储,并且对可以存储的内容有限制。它不能存储原型数据(方法)。看它更像是key<>value存储。

这样做的原因是会话可以存储在任何地方(几乎),例如:进程内存;数据库(mongodb);雷迪斯;等等

因此,一旦您将任何内容放入会话对象并且请求完成,express 将获取会话数据,对其进行解析并像纯对象一样粘贴到会话存储中。默认情况下它是进程内存,但如前所述 - 可以是任何其他存储。

因此,当下一个 http 请求发生时,会话中间件将尝试使用请求标头中的 cookie(如果使用的话)数据来恢复会话,并要求会话存储返回数据。因为它已经格式化 - 它不会有 .prototype 的东西,而只有数据(object, array, string,number和其他简单的东西)。

因此,您需要再次使用该数据创建模型。
您可以通过中间件在 express 中完成。所以在app.configure定义之后session 你可以这样做:

app.use(function(req, res, next) {
  if (req.session && req.session.user) {
    req.session.user = new UserModel(req.session.user);
  }
  return next();
});

这将检查是否定义了会话以及用户。然后将根据该数据重新创建猫鼬模型。

如评论中所述 - 您不应将用户数据存储在会话中,因为由于对数据所有权的责任划分,这将导致不一致。所以只存储ID用户,然后使用中间件从数据库加载它。

app.use(function(req, res, next) {
  if (req.session && req.session.userID) {
    UserModel.findById(req.session.userID, function(err, user) {
      if (!err && user) {
        req.user = user;
        next();
      } else {
        next(new Error('Could not restore User from Session.'));
      }
    });
  } else {
    next();
  }
});

在您可以创建中间件以检查用户是否已登录之后:

function userRequired(req, res, next) {
  if (req.user) {
    next();
  } else {
    next(new Error('Not Logged In');
  }
}

并像这样使用它:

app.get('/items', userRequired, function(req, res, next) {
  // will only come here if user is logged in (req.user !== undefined)
});
于 2013-09-06T16:47:48.430 回答
2

即使它有效(由于 Maksims 详细说明的原因,它不会有效),在会话中持久化用户模型实例也是一个坏主意。如果同一用户多次登录,则两个会话的用户实例将不同步并继续相互覆盖。

_id正确的做法是在会话中持久化用户(例如request.session.userId)并安装 Express 中间件以将用户模型实例加载到request.user其中,以便其他中间件和路由处理程序可用于当前调用。与您现在所做的类似,但更清洁。

通过它查找单个对象_id非常快,并且因为您经常这样做,所以它已经在内存中,因此开销为零。

于 2013-09-06T19:39:20.547 回答
0

尽管依赖会话存储来保存整个用户对象存在复杂性问题,但应该注意的是,您可以使用该方法从普通 JS 对象初始化mongoose 对象init()(在撰写本文时,v4.4.14)。

req.user = new UserModel().init(req.session.user);
req.user.name = 'James';
req.user.save( ... );

不像这样(它会尝试保存一个相同的文档而不是_id 更新):

req.session.user = new UserModel(req.session.user);
req.user.name = 'James';
req.user.save( ... );
于 2016-05-02T20:30:45.137 回答