Firebase日/月交易金额更新异常问题排查求助
交易类App Firebase交易金额更新异常问题
问题概述
开发交易类App时,Firebase读写操作整体正常,但交易完成环节的部分功能存在问题。代码包含两种支付类型:
- 交易类型1:全额支付
- 交易类型2:部分支付
两种支付完成后,需在Firebase三个独立集合更新数据:
- 每日交易(DAILYTRANSACTIONS)
- 每月交易(MONTHLYTRANSACTIONS)
- 每年交易(YEARLYTRANSACTIONS)
预期逻辑:获取各集合初始金额,累加本次支付金额。
异常现象:
- 交易类型1(全额支付):每日和每月交易更新有时正常,有时直接用本次金额覆盖原有金额(如原有45,支付20后变为20而非65)。
- 交易类型2(部分支付):不会累加金额,直接重置为本次支付金额。
- 每年交易更新在两种类型下均正常。
备注:交易完成后的其他更新均正常。
初始化代码
DateTime now = DateTime.now(); DateTime mon = DateTime.now(); DateTime yr = DateTime.now(); String today = DateFormat("dd-MMM-yyyy").format(now); String thisMonth = DateFormat("MMM-yyyy").format(mon); String thisYear = DateFormat("yyyy").format(yr); final serviceFee = widget.selectedRide["ServiceFee"]; final service = double.tryParse("$serviceFee") ?? 0.0; final amountToPay = service - balance;
交易类型1(全额支付)代码
FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .get() .then((value) => value.docs.forEach((element) { String day = element.get("Day"); if (day == today) { final bala = element.get("Amount"); final amountss = bala + service; FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today) .set({ "Amount": service, "Day": today, }); } })) .whenComplete(() { FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .get() .then((value) => value.docs.forEach((element) { String month = element.get("Month"); if (month == thisMonth) { final bala = element.get("Amount"); final amountss = bala + service; FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth) .set({ "Amount": service, "Month": thisMonth, }); } })) .whenComplete(() { FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .get() .then((value) => value.docs.forEach((element) { String year = element.get("Year"); if (year == thisYear) { final bala = element.get("Amount"); final amountss = bala + service; FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear) .set({ "Amount": service, "Year": thisYear, }); } })); }); });
交易类型2(部分支付)代码
FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .get() .then((value) => value.docs.forEach((element) { String day = element.get("Day"); if (day == today) { final bala = element.get("Amount"); final amountss = bala + amountToPay; FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today) .set({ "Amount": amountToPay, "Day": today, }); } })) .whenComplete(() { FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .get() .then((value) => value.docs.forEach((element) { String month = element.get("Month"); if (month == thisMonth) { final bala = element.get("Amount"); final amountss = bala + amountToPay; FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth) .set({ "Amount": amountToPay, "Month": thisMonth, }); } })) .whenComplete(() { FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .get() .then((value) => value.docs.forEach((element) { String year = element.get("Year"); if (year == thisYear) { final bala = element.get("Amount"); final amountss = bala + amountToPay; FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear) .update({ "Amount": amountss, }); } else { FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear) .set({ "Amount": amountToPay, "Year": thisYear, }); } })); }); });
问题分析与修复方案
核心问题
- 集合全量查询导致逻辑错误:通过
get()查询整个集合所有文档后遍历判断,只要有一个文档的日期/月份不匹配,就会执行set()操作覆盖目标文档,这是金额被覆盖的直接原因。 - 非原子操作存在并发风险:先读取再更新的操作不是原子性的,高并发场景下会加剧数据不一致问题。
- 文档ID设计未充分利用:目标文档ID已经是
today/thisMonth/thisYear,完全可以直接通过ID定位文档,无需遍历整个集合。
修复步骤
1. 替换全量查询为单文档查询
针对每日、每月交易集合,直接通过文档ID获取目标文档,避免遍历所有文档导致的错误逻辑。
2. 使用Firebase原子增量操作
用FieldValue.increment()替代手动读取累加,保证操作原子性,同时简化代码。
修复后的代码示例(交易类型1)
// 处理每日交易 final dailyDocRef = FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today); dailyDocRef.get().then((docSnapshot) { if (docSnapshot.exists) { // 原子累加金额 dailyDocRef.update({ "Amount": FieldValue.increment(service), }); } else { dailyDocRef.set({ "Amount": service, "Day": today, }); } }).then((_) { // 处理每月交易 final monthlyDocRef = FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth); return monthlyDocRef.get().then((docSnapshot) { if (docSnapshot.exists) { monthlyDocRef.update({ "Amount": FieldValue.increment(service), }); } else { monthlyDocRef.set({ "Amount": service, "Month": thisMonth, }); } }); }).then((_) { // 处理每年交易(统一改为原子操作) final yearlyDocRef = FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear); return yearlyDocRef.get().then((docSnapshot) { if (docSnapshot.exists) { yearlyDocRef.update({ "Amount": FieldValue.increment(service), }); } else { yearlyDocRef.set({ "Amount": service, "Year": thisYear, }); } }); }).catchError((error) { // 处理错误 print("交易更新失败: $error"); });
3. 交易类型2的修改
只需将上述代码中的service替换为amountToPay即可,逻辑完全一致。
额外优化建议
使用async/await替代嵌套then(),大幅提升代码可读性:
Future<void> updateTransactions(double amount) async { try { // 每日交易 final dailyDocRef = FirebaseFirestore.instance .collection("DAILYTRANSACTIONS") .doc("GH") .collection("GHANADAILYTRANSACTIONS") .doc(today); final dailySnapshot = await dailyDocRef.get(); if (dailySnapshot.exists) { await dailyDocRef.update({"Amount": FieldValue.increment(amount)}); } else { await dailyDocRef.set({"Amount": amount, "Day": today}); } // 每月交易 final monthlyDocRef = FirebaseFirestore.instance .collection("MONTHLYTRANSACTIONS") .doc("GHA") .collection("GHANAMONTHLYTRANSACT") .doc(thisMonth); final monthlySnapshot = await monthlyDocRef.get(); if (monthlySnapshot.exists) { await monthlyDocRef.update({"Amount": FieldValue.increment(amount)}); } else { await monthlyDocRef.set({"Amount": amount, "Month": thisMonth}); } // 每年交易 final yearlyDocRef = FirebaseFirestore.instance .collection("YEARLYTRANSACTIONS") .doc("GHANA") .collection("GHANAYEARLYTRANSACTIONSSS") .doc(thisYear); final yearlySnapshot = await yearlyDocRef.get(); if (yearlySnapshot.exists) { await yearlyDocRef.update({"Amount": FieldValue.increment(amount)}); } else { await yearlyDocRef.set({"Amount": amount, "Year": thisYear}); } } catch (e) { print("交易更新错误: $e"); } } // 调用方式 updateTransactions(service); // 交易类型1 updateTransactions(amountToPay); // 交易类型2
原有每年交易逻辑正常的原因
每年的文档数量极少,遍历的时候大概率第一个就匹配到当前年份的文档,不会触发else分支的set()操作。但如果存在多年文档,同样会出现覆盖问题,建议一并改为上述原子操作逻辑。
内容的提问来源于stack exchange,提问作者EBENEZER OCANSEY
相关产品推荐
相关产品推荐

