【发布时间】:2021-02-18 11:30:21
【问题描述】:
我有一个用户想要注册课程的案例。
如果课程已满,用户可以在等候名单上注册。如果此课程有空位,该用户将自动注册。为了实现等候名单自动化,我使用了带有.onUpdate 触发器的云函数 (CF),它可以工作。
CF 示例: 课程已满。参与者取消注册。 CF 触发并检查课程中是否有空闲位置以及等待列表中的用户是否尚未注册。如果是这样,那么 CF 会从课程的等待列表中注册用户。
同时,在 UI 中,当有空位可用时,我允许已在等候名单上的同一用户注册课程。通过注册课程,我将他从等候名单中删除。
对于这个实现,我使用了transaction,它仅在用户尚未在课程中注册时完成。
由于某种原因,有时用户会注册两次。
我的问题是:onUpdate 函数会锁定文档吗?我是否需要onUpdate 函数中的transaction 来监听同一文档的更改? => 对我来说这是没有意义的。
附:处理此错误的最简单方法是让用户先从等候名单中取消注册,然后再注册课程。不过,我想了解如何实现上述逻辑。
Flutter 中的事务:
@override
Future<void> selfSetCourseParticipantWithStashedCreditPoint(
String courseEventId, Participant participantToAdd) async {
try {
UsedCreditPointInfo participantsCreditPoint =
participantToAdd.usedCreditPointInfo;
await _firestore.runTransaction((transaction) async {
// 1) Check if the customer does still have an available credit point left
final userRef = _usersCollection.doc(participantToAdd.uid);
var userDocumentSnapshot = await transaction.get(userRef);
var stashedCreditPoints =
userDocumentSnapshot.data()['${participantToAdd.creditPointPath}'];
var creditPointExists = false;
for (var usedCreditPointInfoMap in stashedCreditPoints) {
var usedCreditPoint =
UsedCreditPointInfo.fromMap(usedCreditPointInfoMap);
if (usedCreditPoint == participantsCreditPoint) {
creditPointExists = true;
break;
}
}
// if the credit point does not exist, then the transaction must fail
if (!creditPointExists) return null;
final courseRef = _courseEventsCollection.doc(courseEventId);
final courseEventDocumentSnapshot = await transaction.get(courseRef);
// 2) if the courseEvent document exists I have to check first if there are slots left
var currentCourseEvent = CourseEvent.fromEntity(
CourseEventEntity.fromSnapshot(courseEventDocumentSnapshot));
//todo: add the remaining requirements, e.g., registration date limit
if (currentCourseEvent.participants.length >=
currentCourseEvent.maxParticipants) {
return null;
}
if (currentCourseEvent.waitingList != null) {
var waitingList = currentCourseEvent.waitingList;
for (var i = 0; i < waitingList.length; i++) {
final waitingListParticipant = waitingList[i];
if (waitingListParticipant.uid == participantToAdd.uid) {
waitingList.removeAt(i);
break;
}
}
}
// add only if the participant is not already included to the list
if (!currentCourseEvent.participants.contains(participantToAdd)) {
currentCourseEvent = currentCourseEvent.copyWith(
participants: List.from(currentCourseEvent.participants)
..add(participantToAdd),
);
// the user document exists at this point, thus update is sufficient
transaction.update(
userRef,
{
'${participantToAdd.creditPointPath}': FieldValue.arrayRemove(
[participantToAdd.usedCreditPointInfo?.toMap()])
},
);
return transaction.set(
courseRef, currentCourseEvent.toEntity().toMap());
}
});
} catch (e, s) {
return handleFirestoreError(e, s);
}
}
我的 CF 的 sn-p:
export const notifyOnCourseEventUpdateAndWaitingListRegistration = functions.firestore
.document("CourseEvents/{courseEvent}")
.onUpdate(async (change, context) => {
return firestore
.runTransaction(async (transaction) => {
const courseEventRef = firestore
.collection("CourseEvents")
.doc(change.after.data().firebaseId);
const courseEventSnapshot = await transaction.get(courseEventRef);
const courseEvent = courseEventSnapshot.data() as CourseEvent;
const currentCourseEvent = change.after.data() as CourseEvent;
const currentParticipants = currentCourseEvent.participants;
const previousCourseEvent = change.before.data() as CourseEvent;
const previousParticipants = previousCourseEvent.participants;
const currentMaxParticipants = currentCourseEvent.maxParticipants;
const currentWaitingList = currentCourseEvent.waitingList;
...
})
});
【问题讨论】:
标签: firebase flutter google-cloud-firestore google-cloud-functions