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

如何用Firebase防止多人同时将同一商品加入Flutter购物车?

解决Flutter+Firebase商品并发加入购物车的问题

你的核心问题是事务逻辑错误,直接绕开事务操作文档,加上错误的删除+更新操作,导致并发时逻辑混乱。下面是具体问题和修复方案:

你的代码存在的问题

  1. 事务内直接操作文档,破坏原子性:你在事务里用了await product.doc(data['id']).delete(),这是绕开Transaction的独立操作,Firebase事务无法管控这个动作,并发时多个请求会同时执行删除,导致后续逻辑混乱。
  2. 删除后更新不存在的文档:删除商品文档后又执行tx.update(ref, data),此时文档已经被删除,update会直接报错,这就是你看到分支同时执行报错的原因。
  3. UI操作放在事务内部:Get.back()、inArea.removeAt(index)这类UI操作放在事务里,而Firebase事务可能因为冲突自动重试,会导致这些操作重复执行,出现多次弹窗或页面回退的问题。

修复后的实现方案

我们改用商品锁定字段的方式(比删除文档更灵活,后续取消购物车还能恢复商品),严格通过事务控制并发:

1. 商品文档结构调整

给product集合的文档新增一个字段:

  • lockedBy: String类型,默认null,表示该商品未被占用;如果被用户加入购物车,就存入该用户的uid。

2. 修正后的代码

void addBasket(Map<String, dynamic> data, int index) async {
  final _storage = await GetStorage();
  final currentUid = _storage.read('uid');
  if (currentUid == null) {
    // 先判断用户是否登录,避免空值
    Get.defaultDialog(title: "错误", middleText: "请先登录");
    return;
  }

  final CollectionReference userBasket = FirebaseFirestore.instance.collection('user basket');
  final DocumentReference productRef = FirebaseFirestore.instance.collection('product').doc(data['id']);

  try {
    await FirebaseFirestore.instance.runTransaction((Transaction tx) async {
      DocumentSnapshot productSnap = await tx.get(productRef);
      if (!productSnap.exists) {
        // 商品已被其他用户锁定并移除(如果是永久移除的场景)
        throw Exception("商品已被他人占用");
      }

      String? lockedUid = productSnap.get('lockedBy');
      if (lockedUid == null || lockedUid == currentUid) {
        // 商品未被占用,或者当前用户已经锁定该商品,执行加入购物车逻辑
        await tx.update(productRef, {'lockedBy': currentUid});
        
        // 将商品加入用户购物车
        await userBasket
            .doc(currentUid)
            .collection('basket')
            .doc(data['id'])
            .set({...data, 'lockedBy': currentUid});
      } else {
        // 商品已被其他用户锁定
        throw Exception("该商品正在被他人操作");
      }
    });

    // 事务执行成功后再执行UI操作
    inArea.removeAt(index);
    Get.back();
    Get.back();
  } catch (e) {
    // 捕获事务中的异常,弹出提示
    Get.back();
    Get.defaultDialog(
        title: "错误",
        middleText: e.toString().replaceAll("Exception: ", ""));
  }
}

关键逻辑说明

  • 严格通过Transaction操作文档:所有对商品文档的读写都用tx对象,确保Firebase能正确处理并发冲突,只有第一个成功锁定商品的请求能进入分支。
  • 用lockedBy字段代替删除:避免删除文档带来的后续恢复问题,同时能明确标记商品被哪个用户占用。
  • UI操作移到事务外:事务成功后再执行页面回退、列表移除操作,避免事务重试导致的重复UI操作。
  • 异常捕获:把事务逻辑包在try-catch里,统一处理错误提示,避免崩溃。

内容的提问来源于stack exchange,提问作者Alakba

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 16:25:25