假定有一个用户集合,每个用户都与帐户关联,这些帐户保存在一个单独的集合中。每个帐户都有一个余额,该余额会通过某种外部方式(例如下面的http触发器)定期更新。我需要能够查询用户在她所有帐户中的总余额。
我添加了onUpdate触发器,每次帐户更改时都会调用该触发器,并相应地更新总数。但是,似乎有些比赛条件,例如当两个帐户几乎同时更新时:第一个帐户调用onUpdate并更新总余额后,第二个帐户调用onUpdate时,余额仍不更新。我猜测我需要以某种方式使用“交易”进行簿记,但不确定如何使用。
const data = {
'users/XXX': {
email: "a@b.com",
balance: 0
},
"accounts/YYY": {
title: "Acc1",
userID: "XXX"
balance: 0
},
"accounts/ZZZ": {
title: "Acc2",
userID: "XXX"
balance: 0
}
};
exports.updateAccounts = functions.https.onRequest((request, response) => {
admin.firestore().collection('accounts').get().then((accounts) => {
accounts.forEach((account) => {
return admin.firestore().collection('accounts').doc(account.id).update({balance:
WHATEVER});
})
response.send("Done");
});
exports.updateAccount = functions.firestore
.document('accounts/{accountID}')
.onUpdate((change, context) => {
const userID = change.after.data().userID;
admin.firestore().doc("users/"+userID).get().then((user) => {
const new_balance = change.after.data().balance;
const old_balance = change.before.data().balance;
var user_balance = user.data().balance + new_balance - old_balance;
admin.firestore().doc("users/"+userID).update({balance: user_balance});
});
});
答案 0 :(得分:2)
通过查看您的代码,我们可以看到其中的几部分可能会导致错误的结果。没有彻底测试并重现您的问题,就不可能100%确保纠正它们可以完全解决您的问题,但这很可能是造成问题的原因。
通过forEach()
循环,您正在调用几个异步操作(update()
方法),但是您不必等待所有这些异步操作都已完成,然后再发送回响应。您应该执行以下操作,使用Promise.all()
等待所有异步方法完成,然后再发送响应:
exports.updateAccounts = functions.https.onRequest((request, response) => {
const promises = [];
admin.firestore().collection('accounts').get()
.then(accounts => {
accounts.forEach((account) => {
promises.push(admin.firestore().collection('accounts').doc(account.id).update({balance: WHATEVER}));
return Promise.all(promises);
})
.then(() => {
response.send("Done");
})
.catch(error => {....});
});
onUpdate
背景触发云功能您需要正确返回Promises链,以便在云功能完成时向平台指示。以下应该可以解决问题:
exports.updateAccount = functions.firestore
.document('accounts/{accountID}')
.onUpdate((change, context) => {
const userID = change.after.data().userID;
return admin.firestore().doc("users/"+userID).get() //Note the return here. (Note that in the HTTP Cloud Function we don't need it! see the link to the video series below)
.then(user => {
const new_balance = change.after.data().balance;
const old_balance = change.before.data().balance;
var user_balance = user.data().balance + new_balance - old_balance;
return admin.firestore().doc("users/"+userID).update({balance: user_balance}); //Note the return here.
});
});
我建议您观看Firebase视频系列中有关“ JavaScript Promises”的3个视频:https://firebase.google.com/docs/functions/video-series/。他们解释了上面已更正的所有关键点。
乍一看,如果您在updateAccounts
Cloud Function中修改了几个共享相同account
的{{1}}个文档,则确实需要实现用户余额更新在事务中,user
云功能的多个实例可能会并行触发。交易文档为here。
更新:
您可以在updateAccount
Cloud Function(未经测试)中实现以下事务:
updateAccounts
答案 1 :(得分:2)
除了@Renaud Tarnec涵盖的their answer之外,您可能还需要考虑以下方法:
在updateAccounts
函数中,您一次要写入许多数据,如果其中任何一个失败,则最终可能得到一个数据库,其中包含正确更新的数据和未能成功更新的数据的混合。进行更新。
要解决此问题,您可以使用批处理写入来原子地写入数据,其中所有新数据都将成功更新,或者不写入任何数据而使数据库处于已知状态。
exports.updateAccounts = functions.https.onRequest((request, response) => {
const db = admin.firestore();
db.collection('accounts')
.get()
.then((qsAccounts) => { // qs -> QuerySnapshot
const batch = db.batch();
qsAccounts.forEach((accountSnap) => {
batch.update(accountSnap.ref, {balance: WHATEVER});
})
return batch.commit();
})
.then(() => response.send("Done"))
.catch((err) => {
console.log("Error whilst updating balances via HTTP Request:", err);
response.status(500).send("Error: " + err.message)
});
});
与其在文档中存储单个“余额”,不如(根据您要执行的操作)将每个帐户的余额存储在用户的文档中。
"users/someUser": {
...,
"balances": {
"accountId1": 10,
"accountId4": -20,
"accountId23": 5
}
}
如果需要累积余额,只需将它们添加到客户端即可。如果您需要删除余额,只需在用户文档中删除它的条目即可。
exports.updateAccount = functions.firestore
.document('accounts/{accountID}')
.onUpdate((change, context) => {
const db = admin.firestore();
const accountID = context.params.accountID;
const newData = change.after.data();
const accountBalance = newData.balance;
const userID = newData.userID;
return db.doc("users/"+userID)
.get()
.then((userSnap) => {
return db.doc("users/"+userID).update({["balances." + accountID]: accountBalance});
})
.then(() => console.log(`Successfully updated account #${accountID} balance for user #${userID}`))
.catch((err) => {
console.log(`Error whilst updating account #${accountID} balance for user #${userID}`, err);
throw err;
});
});