Flutter读取Firebase实时数据库含push自动ID的订单号并自增
问题场景
使用Flutter(Dart语言)向Firebase Realtime Database写入订单数据时,基础写入功能可正常运行,写入代码如下:
DatabaseReference _ref = FirebaseDatabase.instance.ref(); final order = _ref.child('Orders'); order.push().set({ 'orderNum': orderNum, 'totalAmount': totalAmount, 'dateTime': timeStamp.toString(), 'cartProduct': cartProducts .map((cp) => { 'id': cp.id, 'title': cp.name, 'quantity': cp.quantity, 'price': cp.price }) .toList() });
后续尝试读取历史最新订单号做加1操作生成新订单号,最初实现的读取逻辑如下:
String lastOrderNum = ""; int intOrderNum = 0; String newOrderNum = "2"; final DateTime dateee = DateTime.now(); DatabaseReference _readRef = FirebaseDatabase.instance.ref(); @override void initState() { super.initState(); _activateListeners(); } void _activateListeners() { final readOrder = _readRef.child('Orders/orderNum').onValue.listen((event) { final orderNum = event.snapshot.value; setState(() { lastOrderNum = orderNum.toString(); intOrderNum = int.parse(lastOrderNum) + 1; newOrderNum = intOrderNum.toString(); }); }); }
排查后确认问题出在读取路径:调用push()写入时Firebase会自动生成唯一ID作为单条订单的父节点key,原有读取路径Orders/orderNum缺少这层自动生成的ID节点,无法读取到对应值。如果移除push()使用固定路径写入,虽然能实现订单号自增,但会覆盖已存储的历史订单数据。
当前数据库实际存储结构如下(现有订单号为硬编码写入):
{ "Orders": { "-N3ZdY6LOL_9Z-6KXHnK": { "cartProduct": {}, "dateTime": "2022-06-02 15:41:20.470139", "orderNum": "6", "totalAmount": 45 }, "-N3ZdgEQIzsjLA5NCu3U": { "cartProduct": {}, "dateTime": "2022-06-02 15:43:12.123456", "orderNum": "7", "totalAmount": 45 } } }
核心需求:将push()生成的自动ID(示例值:-N3ZdgEQIzsjLA5NCu3U)正确拼入读取路径,构造Orders/[自动ID]/orderNum格式的合法路径,读取最新订单号完成自增逻辑。
实现方案
不需要手动拼接未知的push ID,两种成熟实现方式可直接使用:
方案1:直接查询最新订单(适合单用户/低并发场景)
Firebase通过push()生成的ID是基于时间戳编码的,生成时间越晚的ID字典序越靠后,直接对Orders节点做倒序查询、取最后1条结果,就是最新写入的订单,完全不需要提前知道push ID的值。
实现代码如下:
// 不需要在initState里加持续监听,生成新订单前调用一次即可 Future<String> getLatestOrderNum() async { final orderRef = FirebaseDatabase.instance.ref().child('Orders'); // 按push key倒序,只取1条结果,就是最新订单 final latestSnapshot = await orderRef.orderByKey().limitToLast(1).get(); if (!latestSnapshot.exists) { // 数据库无历史订单时,初始订单号从1开始 return "1"; } final ordersData = Map<String, dynamic>.from(latestSnapshot.value as Map); // 取到的唯一key就是最新订单的push ID final latestOrderKey = ordersData.keys.first; final latestOrder = Map<String, dynamic>.from(ordersData[latestOrderKey]); final lastNum = int.parse(latestOrder['orderNum'].toString()); // 返回自增后的新订单号 return (lastNum + 1).toString(); }
注意:不要遍历全量Orders节点比对orderNum找最大值,数据量增大后查询性能会严重下降。
方案2:独立计数器+事务(推荐,支持多用户并发场景)
方案1在多用户同时下单时存在并发风险:两个用户可能同时读到同一个最新订单号,生成重复的订单号。更稳妥的实现是单独维护一个全局自增计数节点,通过Firebase事务保证自增操作的原子性,完全不需要遍历订单列表、也不需要处理push ID路径问题。
实现步骤:
- 在数据库中新增独立节点
orderConfig/lastOrderNum,专门存储当前最新的订单序号 - 生成新订单号时,通过事务操作对该节点做原子自增
实现代码如下:
Future<String> generateUniqueOrderNum() async { final counterRef = FirebaseDatabase.instance.ref().child('orderConfig/lastOrderNum'); // 事务操作保证并发下不会重复计数 final transactionRes = await counterRef.runTransaction((currentVal) { if (currentVal == null) { // 首次使用初始值为1 return Transaction.success(1); } final currentNum = int.parse(currentVal.toString()); return Transaction.success(currentNum + 1); }); if (!transactionRes.committed) { throw Exception("订单号生成失败,请重试"); } return transactionRes.snapshot.value.toString(); }
调用该方法拿到全局唯一的新订单号后,直接走原有的push().set()逻辑写入订单数据即可。
内容的提问来源于stack exchange,提问作者Stamp777

