有时,我会从一个可调用的函数获得如下所示的复制文档:
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文档,但我无法找到在可调用函数上实现幂等性的解决方案,可调用函数中的上下文参数与后台函数和触发器非常不同,可调用的上下文如下所示:
我确实找到了一个有用的blogpost来使用firebase触发器实现幂等性:
但我并不完全理解这种方法,因为我认为这是假设文档是在客户端(也就是前端应用程序)编写的,我并不认为这是一个很好的方法,因为它过于依赖客户机,我也担心安全性问题。
所以,是的,我想知道有一种方法可以在可调用函数上实现不可调用性,我需要一些类似于EventID的东西,但是对于可调用函数,可以安全地在我的应用程序和第三方apis (如stripe )上实现支付。
如果你能给我任何帮助或暗示,我将不胜感激。
发布于 2021-06-02 05:29:06
幂等函数的使用主要适用于响应诸如上传到云存储的文件或添加到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:"));备注:客户端调用函数两次的最常见方式之一是重发器,您在其中更新状态以显示“加载”消息,然后再次调用该函数。作为一个反应的例子,您将确保数据库调用被包装在它自己的useEffect()调用中。
https://stackoverflow.com/questions/67764159
复制相似问题