我目前正在制作一个有反应前端和防火墙后端的web应用程序。这是一个当地健身房的申请,由两部分组成:
当地的健身房为公司提供节目。这样公司就可以订阅,公司的员工可以在当地的健身房接受培训,并使用客户端应用程序。重要的是要跟踪公司员工的个人进度以及整个进度(x公司所有员工总共损失的公斤数)。
在Firestore集合“user”中,每个用户文档都具有字段的权重。每当培训师在对特定客户进行物理评估后填写进度表时,客户的用户文档中的体重字段将被更新为新的体重。
在Firestore中,还有另一个集合“公司”,每个公司都有一个文档。我的目标是把公司员工损失的总重量放在具体的文件中。因此,每次培训师更新员工的体重时,公司文档都需要更新。我制作了一个云功能,可以监听用户文档的更新。该职能列于下:
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate((change, context) => {
const previousData = change.before.data();
const data = change.after.data();
if (previousData === data) {
return null;
}
const companyRef = admin.firestore.doc(`/companies/${data.company}`);
const newWeight = data.bodyweight;
const oldWeight = previousData.bodyweight;
const lostWeight = oldWeight > newWeight;
const difference = diff(newWeight, oldWeight);
const currentWeightLost = companyRef.data().weightLostByAllEmployees;
if (!newWeight || difference === 0 || !oldWeight) {
return null;
} else {
const newCompanyWeightLoss = calcNewCWL(
currentWeightLost,
difference,
lostWeight
);
companyRef.update({ weightLostByAllEmployees: newCompanyWeightLoss });
}
});在上面的云函数中有两个简单的函数:
const diff = (a, b) => (a > b ? a - b : b - a);
const calcNewCWL = (currentWeightLost, difference, lostWeight) => {
if (!lostWeight) {
return currentWeightLost - difference;
}
return currentWeightLost + difference;
};我已经将云功能部署到Firebase来测试它,但是我无法让它工作。只要用户文档被更新,该函数就会触发,但它不会用新的weightLostByAllEmployees值更新公司文档。这是我第一次使用Firebase云功能,所以大的改变是某种新手的错误。
发布于 2021-05-28 14:23:52
您目前的解决方案中有一些缺陷,我们可以消除。
始终检查false是否相等
您可以使用以下等式检查来确定数据是否没有更改:
if (previousData === data) {
return null;
}这将始终是false,因为change.before.data()和change.after.data()返回的对象总是不同的实例,即使它们包含相同的数据。
公司变更从不处理。
虽然这可能是一个罕见的,也许不可能的事件,如果一个用户的公司被改变,你应该删除他们的重量从原来的公司总数,并添加到新的公司。
同样,当员工离开公司或删除他们的帐户时,您应该在onDelete处理程序中从总数中删除他们的权重。
处理浮点和
如果你不知道,浮点算术有一些小的怪癖。举个例子,0.1 + 0.2,对人类来说,答案是0.3,但是对于JavaScript和许多语言来说,答案是0.30000000000000004。有关详细信息,请参阅这个问题和线索。
与其将权重作为浮点数存储在数据库中,不如考虑将其存储为整数。由于权重通常不是一个整数(例如9.81kg),所以您应该将此值乘以100 (对于2个重要数字),然后将其舍入最接近的整数。然后,当您显示它时,要么将它除以100,要么在适当的十进制符号中拼接。
const v = 1201;
console.log(v/100); // -> 12.01
const vString = String(v);
console.log(vString.slice(0,-2) + "." + vString.slice(-2) + "kg"); // -> "12.01kg"所以对于求和,0.1 + 0.2,你会把它放大到10 + 20,结果是30。
console.log(0.1 + 0.2); // -> 0.30000000000000004
console.log((0.1*100 + 0.2*100)/100); // -> 0.3但是,这种策略本身并不是防弹的,因为一些乘法仍然会导致这些错误,比如0.14*100 = 14.000000000000002和0.29*100 = 28.999999999999996。为了剔除这些值,我们将乘积的值除以。
console.log(0.01 + 0.14); // -> 0.15000000000000002
console.log((0.01*100 + 0.14*100)/100); // -> 0.15000000000000002
console.log((Math.round(0.01*100) + Math.round(0.14*100))/100) // -> 0.15您可以使用以下方法进行比较:
const arr = Array.from({length: 100}).map((_,i)=>i/100);
console.table(arr.map((a) => arr.map((b) => a + b)));
console.table(arr.map((a) => arr.map((b) => (a*100 + b*100)/100)));
console.table(arr.map((a) => arr.map((b) => (Math.round(a*100) + Math.round(b*100))/100)));因此,我们可以得到以下帮助函数:
function sumFloats(a,b) {
return (Math.round(a * 100) + Math.round(b * 100)) / 100;
}
function sumFloatsForStorage(a,b) {
return (Math.round(a * 100) + Math.round(b * 100));
}以这种方式处理权重的主要好处是,您现在可以使用FieldValue#increment()而不是完全成熟的事务来快捷地更新值。在同一公司的两个用户发生更新冲突的罕见情况下,您可以重试增量,也可以返回完整事务。
低效数据解析
在当前代码中,您可以使用.data()在前后状态中获取函数所需的数据。但是,因为要提取用户的整个文档,所以最终解析文档中的所有字段,而不是只解析所需的-- bodyweight和company字段。您可以使用DocumentSnapshot#get(fieldName)来完成这个任务。
const afterData = change.after.data(); // parses everything - username, email, etc.
const { bodyweight, company } = afterData;与以下方面相比:
const bodyweight = change.after.get("bodyweight"); // parses only "bodyweight"
const company = change.after.get("company"); // parses only "company"冗余数学
由于某些原因,您正在计算权重之间差值的绝对值,将差异符号存储为布尔值,然后将它们一起用于将更改应用到损失的总重量上。
以下几行:
const previousData = change.before.data();
const data = change.after.data();
const newWeight = data.bodyweight;
const oldWeight = previousData.bodyweight;
const lostWeight = oldWeight > newWeight;
const difference = diff(newWeight, oldWeight);
const currentWeightLost = companyRef.data().weightLostByAllEmployees;
const calcNewCWL = (currentWeightLost, difference, lostWeight) => {
if (!lostWeight) {
return currentWeightLost - difference;
}
return currentWeightLost + difference;
};
const newWeightLost = calcNewCWL(currentWeightLost, difference, lostWeight);可代之以:
const newWeight = change.after.get("bodyweight");
const oldWeight = change.before.get("bodyweight");
const deltaWeight = newWeight - oldWeight;
const currentWeightLost = companyRef.get("weightLostByAllEmployees") || 0;
const newWeightLost = currentWeightLost + deltaWeight;把一切都卷在一起
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate(async (change, context) => {
// "bodyweight" is the weight scaled up by 100
// i.e. "9.81kg" is stored as 981
const oldHundWeight = change.before.get("bodyweight") || 0;
const newHundWeight = change.after.get("bodyweight") || 0;
const oldCompany = change.before.get("company");
const newCompany = change.after.get("company");
const db = admin.firestore();
if (oldCompany === newCompany) {
// company unchanged
const deltaHundWeight = newHundWeight - oldHundWeight;
if (deltaHundWeight === 0) {
return null; // no action needed
}
const companyRef = db.doc(`/companies/${newCompany}`);
await companyRef.update({
weightLostByAllEmployees: admin.firestore.FieldValue.increment(deltaHundWeight)
});
} else {
// company was changed
const batch = db.batch();
const oldCompanyRef = db.doc(`/companies/${oldCompany}`);
const newCompanyRef = db.doc(`/companies/${newCompany}`);
// remove weight from old company
batch.update(oldCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(-oldHundWeight)
});
// add weight to new company
batch.update(newCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(newHundWeight)
});
// apply changes
await db.batch();
}
});事务回退
在很少遇到写冲突的情况下,此变体返回到传统事务以重新尝试更改。
/**
* Increments weightLostByAllEmployees in all documents atomically
* using a transaction.
*
* `arrayOfCompanyRefToDeltaWeightPairs` is an array of company-increment pairs.
*/
function transactionIncrementWeightLostByAllEmployees(db, arrayOfCompanyRefToDeltaWeightPairs) {
return db.runTransaction((transaction) => {
// get all needed documents, then add the update for each to the transaction
return Promise
.all(
arrayOfCompanyRefToDeltaWeightPairs
.map(([companyRef, deltaWeight]) => {
return transaction.get(companyRef)
.then((companyDocSnapshot) => [companyRef, deltaWeight, companyDocSnapshot])
})
)
.then((arrayOfRefWeightSnapshotGroups) => {
arrayOfRefWeightSnapshotGroups.forEach(([companyRef, deltaWeight, companyDocSnapshot]) => {
const currentValue = companyDocSnapshot.get("weightLostByAllEmployees") || 0;
transaction.update(companyRef, {
weightLostByAllEmployees: currentValue + deltaWeight
})
});
});
});
}
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate(async (change, context) => {
// "bodyweight" is the weight scaled up by 100
// i.e. "9.81kg" is stored as 981
const oldHundWeight = change.before.get("bodyweight") || 0;
const newHundWeight = change.after.get("bodyweight") || 0;
const oldCompany = change.before.get("company");
const newCompany = change.after.get("company");
const db = admin.firestore();
if (oldCompany === newCompany) {
// company unchanged
const deltaHundWeight = newHundWeight - oldHundWeight;
if (deltaHundWeight === 0) {
return null; // no action needed
}
const companyRef = db.doc(`/companies/${newCompany}`);
await companyRef
.update({
weightLostByAllEmployees: admin.firestore.FieldValue.increment(deltaHundWeight)
})
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[companyRef, deltaHundWeight]
]);
});
} else {
// company was changed
const batch = db.batch();
const oldCompanyRef = db.doc(`/companies/${oldCompany}`);
const newCompanyRef = db.doc(`/companies/${newCompany}`);
// remove weight from old company
batch.update(oldCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(-oldHundWeight)
});
// add weight to new company
batch.update(newCompanyRef, {
weightLostByAllEmployees: admin.firestore.FieldValue.increment(newHundWeight)
});
// apply changes
await db.batch()
.catch((error) => {
// if an unexpected error, just rethrow it
if (error.code !== "resource-exhausted")
throw error;
// encountered write conflict, fall back to transaction
return transactionIncrementWeightLostByAllEmployees(db, [
[oldCompanyRef, -oldHundWeight],
[newCompanyRef, newHundWeight]
]);
});
}
});发布于 2021-05-28 10:52:25
在云功能中有几点需要调整:
admin.firestore()而不是admin.firestorecompanyRef.data()获取公司文档的数据。必须调用异步get()方法。因此,下面的代码应该可以做到这一点。
注意,由于我们使用的是事务,所以实际上没有实现上面第二个要点的建议。我们使用transaction.get(companyRef)代替。
exports.updateCompanyProgress = functions.firestore
.document("users/{userID}")
.onUpdate((change, context) => {
const previousData = change.before.data();
const data = change.after.data();
if (previousData === data) {
return null;
}
// You should do admin.firestore() instead of admin.firestore
const companyRef = admin.firestore().doc(`/companies/${data.company}`);
const newWeight = data.bodyweight;
const oldWeight = previousData.bodyweight;
const lostWeight = oldWeight > newWeight;
const difference = diff(newWeight, oldWeight);
if (!newWeight || difference === 0 || !oldWeight) {
return null;
} else {
return admin.firestore().runTransaction((transaction) => {
return transaction.get(companyRef).then((compDoc) => {
if (!compDoc.exists) {
throw "Document does not exist!";
}
const currentWeightLost = compDoc.data().weightLostByAllEmployees;
const newCompanyWeightLoss = calcNewCWL(
currentWeightLost,
difference,
lostWeight
);
transaction.update(companyRef, { weightLostByAllEmployees: newCompanyWeightLoss });
});
})
}
});https://stackoverflow.com/questions/67737410
复制相似问题