将新成员添加到阵列并创建/更新参考文档

时间:2016-03-29 22:10:02

标签: node.js mongodb mongoose mongodb-query

我有以下代码:

function() {

  Server.findOne({
      _id: id
    })
    .populate('_players')
    .exec(function(err, server) {
      if (err) {
        return console.log(err);
      } else if (server) {
        console.log('Server: ' + server.name);
        for (elem in allPlayers) {
          if (allPlayers.hasOwnProperty(elem)) {
            //console.log('DEBUG: allPlayers has Property');
            var player = allPlayers[elem];

            var obj = server._players.find(function(obj) {
              return obj.steam_id === player.steamID;
            });
            if (obj) {
              console.log('|| ' + obj.name + ' was online at: ' + obj.last_online);
              console.log('-->                 update to: ' + new Date());
              obj.last_online = new Date();
              obj.save(function(err, obj) {
                if (err) return console.log(err);
                console.log('DEBUG: existing Player safed: ', obj.name);
              })
            } else {
              var newPlayer = new Player();
              newPlayer._id = new mongoose.Types.ObjectId();
              newPlayer.name = player.name;
              newPlayer.steam_id = player.steamID;
              newPlayer.last_online = new Date();

              newPlayer.save(function(err, newPlayer) {
                if (err) return console.log(err);
                console.log('DEBUG: newPlayer safed: ', newPlayer.name);
              });

              server._players.push(newPlayer);
            }

          } else {
            console.log('There is no Property (allPlayers)');
          }
        }

        server.save(function(err, server) {
          if (err) return console.log(err);
          console.log('DEBUG: server safed: ', server.name);
        })
      } else {
        console.log('There is no such server (updatePlayers)');
      }
    })
}

这样做的目的是让所有属于特定服务器的现有玩家。如果allPlayers数组中有新的播放器,则它们应保存在数据库中。如果玩家已经存在,则last_online时间刚刚更新。除了一个问题,它基本上应该工作。如果我尝试从数据库更新现有播放器,则在保存现有播放器之前保护服务器。因此他们实际上没有更新,我绝对不明白为什么会这样。新玩家正确安全... ...

很想得到任何建议!

//调试输出如下:

{
    > DEBUG: server safed:  TestServer
    DEBUG: existing Player safed:  How's it going
    DEBUG: existing Player safed:  skullsoul2
    DEBUG: existing Player safed:  Terra
    DEBUG: existing Player safed:  Rundas
    DEBUG: existing Player safed:  [OG]GreyForce
    DEBUG: existing Player safed:  Thyrenos

}

//但应该是这样的

{
    DEBUG: existing Player safed:  How's it going
    DEBUG: existing Player safed:  skullsoul2
    DEBUG: existing Player safed:  Terra
    DEBUG: existing Player safed:  Rundas
    DEBUG: existing Player safed:  [OG]GreyForce
    DEBUG: existing Player safed:  Thyrenos
    > DEBUG: server safed:  TestServer
}

1 个答案:

答案 0 :(得分:0)

您可以在此处进行批次改进。一个重要的考虑因素是你在这里处理“异步”方法,这些方法基本上将作用于“回调”或“承诺”,这将在执行完成时解决。

使用Promise.all解决“很多”更新

function updateServerPlayers(id,allPlayers,callback) {

  // Get an array of steamID to use in the query to .populate()
  var steamIds = allPlayers.map(function(player) {
    return player.steamID;
  })

  /*
    findById is pretty much to the point

    Also want to remove any players not present in allPlayers from the array
    response. This makes comparison easy.
  */

  Server.findById(id).populate({
    "path": "_players",
    "match": {
        "steam_id": { "$in": steamIds }
    }
  }).exec(function(err,server) {
    if (err) { 
      callback(err);     // should handle the err in the callback response
    } else if (server) {
      // Actually working on the object here

      var lastOnline = new Date();

      var updatePlayers = _players.map(function(player){ return player.steam_id });
      var allUpdates = allPlayers.filter(function(player) {
        return updatePlayers.indexOf(player.steamID) == -1
      }).map(function(player) {
        /* 
          Make an .update() request for all players not present on the server
          using the "upsert" option to create them if not found
        */
        return Player.update(
          { "steam_id": player.steamID },
          {
            "$setOnInsert": {
              "name": player.name,
            },
            "$set": { "last_online": lastOnline }
          },
          { "upsert": true }
        )
      })

      /* 
        Updating existing server players in one operation
        but appending to the list of allUpdates
      */
      allUpdates.push(
        Player.update(
          { "steam_id": { "$in": updatePlayers } },
          { "$set": { "last_online": lastOnline } },
          { "multi": true }
        )
      );

      // This is where we actually resolve things
      Promise.all(allUpdates).then(function(responses) {

        // Just get the upsert responses
        var upserted = [];
        responses.filter(function(response) {
          return res.hasOwnProperty("upserted");
        }).forEach(function(response) {
          upserted = upserted.concat(response.upserted);
        });

        // Then filter back to just the _id values
        upserted = upserted.map(function(upsert) {
          return upsert._id;
        });

        // Then "atomically" add to the server array with $addToSet
        Server.update(
          { "_id": id },
          { "$addToSet": { "_players": upserted } },
          callback
        );

      }).catch(callback);   // return any error

    } else {
      // Not found should still be an error response
      callback(new Error("server not found"))
    }
  });

}

您正在寻找的基本逻辑会询问这些事情:

  1. 获取当前Server的数据,我们只是需要,以了解allPlayers数组中已有_players个成员。此处.populate()被更改为“仅”返回这些匹配项,而allPlayers中没有包含的其他数据。了解"last_online"仅更新以及确定可能创建的allPlayers非常有用,后者将是列表之间的差异。

  2. 一旦您知道allPlayers中哪些成员已经在_players中而哪些成员已经在Player中,您可以将其分解为两类更新。为:

    • 更新“可能”需要“创建”新"steam_id"(如果不存在)。这些是“upsert”的单独查找,其中找不到"last_online"

    • 可以仅应用于现有玩家的更新,只需更新$in值即可。这些“批量”修改可以通过单个请求将所有列出的文档发送到Promise.all()

  3. 使用“Promises”数组构造的“all”请求(许多upserts和“one”multi update),用_id解析它们并从修改中返回数据。完整的修改响应集合可以告诉您Player的“upserted”文档的_players值,因此需要将哪些值添加到Server数组中。

  4. 一旦您知道要添加哪些内容,就可以使用$addToSet“安全地”将其应用于_id文档。这将添加引用每个Player的新$addToSet成员“如果”该值尚未存在。它不应该根据逻辑存在,但_players确保在其他东西可能已经添加到该文档上的function updateServerPlayers(id,allPlayers,callback) { // Get an array of steamID to use in the query to .populate() var steamIds = allPlayers.map(function(player) { return player.steamID; }) /* findById is pretty much to the point Also want to remove any players not present in allPlayers from the array response. This makes comparison easy. */ Server.findById(id).populate({ "path": "_players", "match": { "steam_id": { "$in": steamIds } } }).exec(function(err,server) { if (err) { callback(err); // should handle the err in the callback response } else if (server) { // Actually working on the object here var bulk = Player.collection.initializeUnorderedBulkOp(); var updatePlayers = _players.map(function(player){ return player.steam_id }); // Build the operations for possible upsert allPlayers.filter(function(player) { return updatePlayers.indexOf(player.steamID) == -1 }).forEach(function(player) { bulk.find({ "steam_id": player.steamID }).upsert().updateOne({ "$setOnInsert": { "name": player.name }, "$currentDate": { "last_online": true } }); }); // Append the operation for bulk.find({ "steam_id": { "$in": updatePlayers } }).update({ "$currentDate": { "last_online": true } }); bulk.execute(function(err,result) { if (err) callback(err); // Get only the "cast" ObjectId values that were upserted var upserted = result.getUpSertedIds().map(function(upsert) { return mongoose.Types.ObjectId( upsert._id.valueOf() ) }) // Atomically update the server object with $addToSet Server.update( { "_id": id }, { "$addToSet": { "_players": upserted } }, callback ); }); } else { callback(new Error("server not found")) } }); } 数组的情况下没有“重复”数据。

  5. 使用MongoDB的“批量”操作一次更新“许多”

    _id

    这使用了相同的过程,但前一个列表创建了一个“数组承诺”以应用于服务器,此表单“构建”实际发送所有的单个请求立即对服务器的操作。由于还有一个响应,因此无需等待每个请求完成,因为它只是一个请求开始。这提供了巨大的性能提升。

    同样的情况是“单个响应”包含所创建的任何Player的所有“upserted”$addToSet值。然后,可以使用相同的_players操作将其附加到.findById()数组。

    这是一种更好的使用方法,但确实有一个问题。由于“批量”操作“直接”使用底层驱动程序中的方法,因此在数据库连接上没有与所有mongoose模型方法中相同的安全措施。

    实际上,这意味着“mongoose方法”(例如对模型的.findById()调用)必须先执行,以便底层驱动程序方法引用解析为正确的对象,或者您需要采取其他步骤来确保建立连接。通过列表,这就是这里的情况,因为mongoose.connection.on("once",function() { // Init application in here }); 已被调用。

    因此,尽管mongoose“隐藏”了故意连​​接的复杂性,但在应用程序初始化时包含“包装器”通常是“良好实践”,“确保”在应用程序的其余部分运行之前连接数据库。

    在您需要的地方,最好按如下方式设置此事件处理程序:

    callback

    但是这个代码的结构并不是必需的。

    这里的另一个主要问题是“整个”过程“应该”包含在一个函数中,该函数将返回Promisereturn,如所示。

    其中的所有操作都是“异步”方法,所以这里没有任何内容可以return结果,因为“异步”调用需要解决才能实际提供任何结果。所以你应该“总是”以这些方式之一包装使用这种“异步”方法的东西,传入参数然后处理“解析器”函数中的响应。不是{{1}}。

    这可能需要一段时间来消化,但值得理解这些原则并进一步使用它们。即使在第一个上市案例中,结果也可以满足您的需求,并且运行速度更快,效率更高。

    另外,通过直截了当的方法,代码本身更加清晰。