有时我会从一个可调用函数中获得重复的文档,该函数如下所示:
const { default: Big } = require('big.js');
const { firestore } = require('firebase-admin');
const functions = require('firebase-functions');
const { createLog } = require('./utils/createLog');
const { payCart } = require('./utils/payCart');
const { unlockCart } = require('./utils/unlockCart');
exports.completeRechargedTransaction = functions.https.onCall(
async (data, context) => {
try {
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'unauthenticated'
);
}
const requiredProperties = [
'foo',
'bar',
'etc'
];
const isDataValid = requiredProperties.every(prop => {
return Object.keys(data).includes(prop);
});
if (!isDataValid) {
throw new functions.https.HttpsError(
'failed-precondition',
'failed-precondition'
);
}
const transactionRef = firestore()
.collection('transactions')
.doc(data.transactionID);
const userRef = firestore().collection('users').doc(data.paidBy.userID);
let currentTransaction = null;
await firestore().runTransaction(async transaction => {
try {
const transactionSnap = await transaction.get(transactionRef);
if (!transactionSnap.exists) {
throw new functions.https.HttpsError(
'not-found',
'not-found'
);
}
const transactionData = transactionSnap.data();
if (transactionData.status !== 'recharged') {
throw new functions.https.HttpsError(
'invalid-argument',
'invalid-argument'
);
}
if (transactionData.type !== 'recharge') {
throw new functions.https.HttpsError(
'invalid-argument',
'invalid-argument'
);
}
if (transactionData.paidBy === null) {
throw new functions.https.HttpsError(
'invalid-argument',
'invalid-argument',
);
}
const userSnap = await transaction.get(userRef);
if (!userSnap.exists) {
throw new functions.https.HttpsError(
'not-found',
'not-found',
);
}
const userData = userSnap.data();
const newUserPoints = new Big(userData.points).plus(data.points);
if (!data.isGoldUser) {
transaction.update(userRef, {
points: parseFloat(newUserPoints.toFixed(2))
});
}
currentTransaction = {
...data,
remainingBalance: parseFloat(newUserPoints.toFixed(2)),
status: 'completed'
};
transaction.update(transactionRef, currentTransaction);
} catch (error) {
console.error(error);
throw error;
}
});
const { paymentMethod } = data.rechargeDetails;
let cashAmount = 0;
if (paymentMethod && paymentMethod.paymentMethod === 'cash') {
cashAmount = data.points;
}
let cartResponse = null;
if (
data.rechargeDetails.isProcessingCart &&
Boolean(data.paidBy.userID) &&
!data.isGoldUser
) {
cartResponse = await payCart(context, data.paidBy.userID, cashAmount);
// This is the function that does all the writes and for some reason it is getting
// called twice or thrice in some rare cases, and I'm pretty much sure that
// The Angular Client is only calling this function "completeRechargedTransaction " once.
}
await createLog({
message: 'Success',
createdAt: new Date(),
type: 'activity',
collectionName: 'transactions',
callerID: context.auth.uid || null,
docID: transactionRef.id
});
return {
code: 200,
message: 'Success',
transaction: currentTransaction,
cartResponse
};
} catch (error) {
console.error(error);
await unlockCart(data.paidBy.userID);
await createLog({
message: error.message,
createdAt: new Date(),
type: 'error',
collectionName: 'transactions',
callerID: context.auth.uid || null,
docID: data.transactionID,
errorSource:
'completeRechargedTransaction'
});
throw error;
}
}
);
我读了很多firebase文档,但我找不到在我的可调用函数上实现幂等性的解决方案,可调用函数中的上下文参数与后台函数和触发器非常不同,可调用上下文如下所示:
https://firebase.google.com/docs/reference/functions/providers_https_.callablecontext
我确实找到了一篇有用的博客文章来实现firebase触发器的幂等性:
云函数专业提示:构建幂等函数
但我不完全理解这种方法,因为我认为这是假设文档编写是在客户端(即前端应用程序(上进行的,我真的不认为这是一种好方法,因为它过于依赖客户端吗?我也担心安全问题。
所以,是的,我想知道有没有一种方法可以在可调用函数上实现Idemptency,我需要像EventID这样的东西,但对于可调用函数,可以在我的应用程序和第三方api(如stripe(上安全地实现支付。
我将感谢你能给我的任何帮助或提示。
幂等函数的使用主要适用于自动触发的云函数,这些函数会响应上传到云存储的文件或添加到Firestore的文档等事件。在这些情况下,事件会触发要执行的函数,如果函数成功,则一切正常。然而,如果该功能失败,它将自动重试,这将导致您链接的博客文章中讨论的问题。
在用户触发的云函数(HTTPS事件或可调用云函数(的情况下,不会自动重试这些函数。这些函数的调用者可以选择处理任何错误,以及客户端是否再次调用函数来重试这些错误。
由于这些用户触发的函数只由客户端代码执行,因此应该检查以确保completeRechargedTransaction()
不会被多次调用。一种测试方法是在调用函数之前为事件ID提供自己的值,如下所示:
// using a firebase push ID as a UUID
// could also use someFirestoreCollectionReference.doc().id or uuid()
const eventId = firebase.database.ref().push().key;
completeRechargedTransaction({
eventId,
/* ... other data ... */
})
.then(console.log.bind(null, "Successfully completed recharged transaction:"))
.catch(console.error.bind(null, "Failed to complete recharged transaction:"));
注意:函数被客户端调用两次的最常见方式之一是因为重新转发器,您已经更新了状态以显示";加载";消息,然后您再次调用该函数。作为React的一个例子,您需要确保您的数据库调用被封装在它自己的useEffect()
调用中。