You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,
                });
              }
            }));
  });
});

问题分析与修复方案

核心问题

  1. 集合全量查询导致逻辑错误:通过get()查询整个集合所有文档后遍历判断,只要有一个文档的日期/月份不匹配,就会执行set()操作覆盖目标文档,这是金额被覆盖的直接原因。
  2. 非原子操作存在并发风险:先读取再更新的操作不是原子性的,高并发场景下会加剧数据不一致问题。
  3. 文档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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:35:54