我正在尝试实施一个简单的抽奖系统,在那里我进行GET /测试,返回一个随机用户,他(1)以前没有赢得抽奖,(2)在过去一小时内注册。
在Mongo中,可能存在与用户相关联的多个文档,因为用户可以注册多个主题。例如{id: 1, name: 'John', subject: 'math',...}
和{id: 1, name: 'John', subject: 'english',...}
。如果约翰因为数学抽奖而被选中,那么他将没有资格参加随后的所有抽奖活动,因此他无法多次获胜。从本质上讲,抽奖获奖者的身份必须是独一无二的。
我的问题是:我如何做检查John先前是否获胜的逻辑?如果John已经赢了,我想从顶部重新启动承诺链并再次Math.random
,直到选出一个独特的赢家。如果没有获胜者符合条件,那么我想返回res.status(500)
。
app.get('/test', function(req, res, next) {
var currentTimestamp = new Date()
var oneHourAgo = new Date(currentTimestamp - oneHour)
var query = { "timeStamp": { $lte: currentTimestamp, $gte: oneHourAgo }, "isWinner": false }
var winna = {}
var winnerSelected = false
var collection = db.collection('entries');
while (!winnerSelected) { // how can I do this
collection.find(query).toArray().then( result => {
var winner = result[Math.floor(Math.random() * result.length)];
var query2 = {"id" : winner.id};
winna['id'] = winner.id
winna['name'] = winner.name
winna['subject'] = winner.subject
winna['timeStamp'] = winner.timeStamp
winna['isWinner'] = winner.isWinner
winna['raffleTimestamp'] = winner.raffleTimestamp
return collection.find(query2).toArray();
}).then( result => {
for (var i in result) { // a winner can enter the raffle for multiple subjects, but if he already won once, then I want to redraw by doing a rand function again
if (i.isWinner) { // until a winner who is eligible is found, or if none are eligible, res.status(500)
console.log("i already won")
break
// how do I make it go back to the beginning of the while loop and pick a new random winner?
}
}
console.log("unique winner")
winnerSelected = true // break out of while loop
var query3 = { id: winna.id, subject: winna.subject }
var raffleTimestamp = new Date()
var update = { $set: { isWinner: true, raffleTimestamp: raffleTimestamp } }
winna['isWinner'] = true
winna['raffleTimestamp'] = raffleTimestamp
res.send(winna) // send the winner with the updated fields to clientside
return collection.updateOne(query3, update); // update the isWinner and raffleTimestamp fields
}).then( result => {
res.status(200);
// res.send(result);
}).catch( err => {
console.log(err);
res.status(500);
});
}
})
答案 0 :(得分:0)
简而言之,在这种情况下,您实际上并不需要这样做。但是有更长的解释。
如果您的MongoDB版本支持它,那么您可以在初始查询条件之后使用$sample
聚合管道,以获得“随机”选择。
当然,在任何情况下,如果某人因为已经“赢了”而没有资格,那么只需将其标记为这样,直接在另一组表格结果中。但这里“排除”的一般情况是简单地修改查询以将“获胜者”排除在可能的结果之外。
但是,我实际上至少会在“现代”的意义上演示“打破一个循环”,即使你实际上并不需要在这里实际需要做什么,这实际上是修改了要排除的查询。
const MongoClient = require('mongodb').MongoClient,
whilst = require('async').whilst,
BPromise = require('bluebird');
const users = [
'Bill',
'Ted',
'Fred',
'Fleur',
'Ginny',
'Harry'
];
function log (data) {
console.log(JSON.stringify(data,undefined,2))
}
const oneHour = ( 1000 * 60 * 60 );
(async function() {
let db;
try {
db = await MongoClient.connect('mongodb://localhost/raffle');
const collection = db.collection('users');
// Clean data
await collection.remove({});
// Insert some data
let inserted = await collection.insertMany(
users.map( name =>
Object.assign({ name },
( name !== 'Harry' )
? { updated: new Date() }
: { updated: new Date( new Date() - (oneHour * 2) ) }
)
)
);
log(inserted);
// Loop with aggregate $sample
console.log("Aggregate $sample");
while (true) {
let winner = (await collection.aggregate([
{ "$match": {
"updated": {
"$gte": new Date( new Date() - oneHour ),
"$lt": new Date()
},
"isWinner": { "$ne": true }
}},
{ "$sample": { "size": 1 } }
]).toArray())[0];
if ( winner !== undefined ) {
log(winner); // Picked winner
await collection.update(
{ "_id": winner._id },
{ "$set": { "isWinner": true } }
);
continue;
}
break;
}
// Reset data state
await collection.updateMany({},{ "$unset": { "isWinner": "" } });
// Loop with random length
console.log("Math random selection");
while (true) {
let winners = await collection.find({
"updated": {
"$gte": new Date( new Date() - oneHour ),
"$lt": new Date()
},
"isWinner": { "$ne": true }
}).toArray();
if ( winners.length > 0 ) {
let winner = winners[Math.floor(Math.random() * winners.length)];
log(winner);
await collection.update(
{ "_id": winner._id },
{ "$set": { "isWinner": true } }
);
continue;
}
break;
}
// Reset data state
await collection.updateMany({},{ "$unset": { "isWinner": "" } });
// Loop async.whilst
console.log("async.whilst");
// Wrap in a promise to await
await new Promise((resolve,reject) => {
var looping = true;
whilst(
() => looping,
(callback) => {
collection.find({
"updated": {
"$gte": new Date( new Date() - oneHour ),
"$lt": new Date()
},
"isWinner": { "$ne": true }
})
.toArray()
.then(winners => {
if ( winners.length > 0 ) {
let winner = winners[Math.floor(Math.random() * winners.length)];
log(winner);
return collection.update(
{ "_id": winner._id },
{ "$set": { "isWinner": true } }
);
} else {
looping = false;
return
}
})
.then(() => callback())
.catch(err => callback(err))
},
(err) => {
if (err) reject(err);
resolve();
}
);
});
// Reset data state
await collection.updateMany({},{ "$unset": { "isWinner": "" } });
// Or synatax for Bluebird coroutine where no async/await
console.log("Bluebird coroutine");
await BPromise.coroutine(function* () {
while(true) {
let winners = yield collection.find({
"updated": {
"$gte": new Date( new Date() - oneHour ),
"$lt": new Date()
},
"isWinner": { "$ne": true }
}).toArray();
if ( winners.length > 0 ) {
let winner = winners[Math.floor(Math.random() * winners.length)];
log(winner);
yield collection.update(
{ "_id": winner._id },
{ "$set": { "isWinner": true } }
);
continue;
}
break;
}
})();
} catch(e) {
console.error(e)
} finally {
db.close()
}
})()
当然,无论采用哪种方法,结果都是随机的,并且之前的“获胜者”在实际查询本身中被排除在选择之外。这里的“循环中断”仅用于保持输出结果,直到不再有可能的赢家为止。
现代node.js环境中的一般建议是内置async/await/yield
功能,现在包含在v8.x.x版本中默认打开。这些版本将于今年10月(写作时)达到长期支持(LTS)并按照我自己的个人“三个月规则”进行,然后任何新作品都应基于当时最新的作品。
此处的备用案例通过async.await
作为单独的库依赖项呈现。或者使用“Bluebird”Promise.coroutine
作为单独的库依赖项,后一种情况是您可以交替使用Promise.try
,但如果您要包含一个库来获取该函数,那么您可能会以及使用实现更现代语法方法的其他函数。
所以“虽然”(不打算)演示“打破一个承诺/回调”循环,真正应该从这里拿走的主要事情是不同的查询过程,它实际上是在选择随机获胜者之前,试图在“循环”中实施的“排除”。
实际情况是数据确定最佳。但是整个例子至少表明了可以应用“选择”和“循环中断”的方式。