如何用Firebase防止多人同时将同一商品加入Flutter购物车?
解决Flutter+Firebase商品并发加入购物车的问题
你的核心问题是事务逻辑错误,直接绕开事务操作文档,加上错误的删除+更新操作,导致并发时逻辑混乱。下面是具体问题和修复方案:
你的代码存在的问题
- 事务内直接操作文档,破坏原子性:你在事务里用了
await product.doc(data['id']).delete(),这是绕开Transaction的独立操作,Firebase事务无法管控这个动作,并发时多个请求会同时执行删除,导致后续逻辑混乱。 - 删除后更新不存在的文档:删除商品文档后又执行
tx.update(ref, data),此时文档已经被删除,update会直接报错,这就是你看到分支同时执行报错的原因。 - 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
相关产品推荐
相关产品推荐

