我有以下代码:
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
}
答案 0 :(得分:0)
您可以在此处进行批次改进。一个重要的考虑因素是你在这里处理“异步”方法,这些方法基本上将作用于“回调”或“承诺”,这将在执行完成时解决。
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"))
}
});
}
您正在寻找的基本逻辑会询问这些事情:
获取当前Server
的数据,我们只是需要,以了解allPlayers
数组中已有_players
个成员。此处.populate()
被更改为“仅”返回这些匹配项,而allPlayers
中没有包含不的其他数据。了解"last_online"
仅更新以及确定可能创建的allPlayers
非常有用,后者将是列表之间的差异。
一旦您知道allPlayers
中哪些成员已经在_players
中而哪些成员已经在Player
中,您可以将其分解为两类更新。为:
更新“可能”需要“创建”新"steam_id"
(如果不存在)。这些是“upsert”的单独查找,其中找不到"last_online"
。
可以仅应用于现有玩家的更新,只需更新$in
值即可。这些“批量”修改可以通过单个请求将所有列出的文档发送到Promise.all()
。
使用“Promises”数组构造的“all”请求(许多upserts和“one”multi update),用_id
解析它们并从修改中返回数据。完整的修改响应集合可以告诉您Player
的“upserted”文档的_players
值,因此需要将哪些值添加到Server
数组中。
一旦您知道要添加哪些内容,就可以使用$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"))
}
});
}
数组的情况下没有“重复”数据。
_id
这使用了相同的过程,但前一个列表创建了一个“数组承诺”以应用于服务器,此表单“构建”实际发送所有的单个请求立即对服务器的操作。由于还有一个响应,因此无需等待每个请求完成,因为它只是一个请求开始。这提供了巨大的性能提升。
同样的情况是“单个响应”包含所创建的任何Player
的所有“upserted”$addToSet
值。然后,可以使用相同的_players
操作将其附加到.findById()
数组。
这是一种更好的使用方法,但确实有一个问题。由于“批量”操作“直接”使用底层驱动程序中的方法,因此在数据库连接上没有与所有mongoose模型方法中相同的安全措施。
实际上,这意味着“mongoose方法”(例如对模型的.findById()
调用)必须先执行,以便底层驱动程序方法引用解析为正确的对象,或者您需要采取其他步骤来确保建立连接。通过列表,这就是这里的情况,因为mongoose.connection.on("once",function() {
// Init application in here
});
已被调用。
因此,尽管mongoose“隐藏”了故意连接的复杂性,但在应用程序初始化时包含“包装器”通常是“良好实践”,“确保”在应用程序的其余部分运行之前连接数据库。
在您需要的地方,最好按如下方式设置此事件处理程序:
callback
但是这个代码的结构并不是必需的。
这里的另一个主要问题是“整个”过程“应该”包含在一个函数中,该函数将返回Promise
或return
,如所示。
其中的所有操作都是“异步”方法,所以这里没有任何内容可以return
结果,因为“异步”调用需要解决才能实际提供任何结果。所以你应该“总是”以这些方式之一包装使用这种“异步”方法的东西,传入参数然后处理“解析器”函数中的响应。不是{{1}}。
这可能需要一段时间来消化,但值得理解这些原则并进一步使用它们。即使在第一个上市案例中,结果也可以满足您的需求,并且运行速度更快,效率更高。
另外,通过直截了当的方法,代码本身更加清晰。