如何通过Firebase实现服务器端强制15分钟结账倒计时及库存归还?
基于Firebase实现临时库存锁定+超时自动释放方案
核心思路
不用持续运行的计时器,而是利用Firestore的TTL自动过期文档+Cloud Function触发事件实现库存临时锁定与超时释放,兼顾成本与安全性。
1. 数据结构设计
- 主库存集合(如
products):每个文档存储商品的total_stock(总库存)。 - 临时预订集合(如
reservations):每个文档包含:product_id:关联商品IDreserved_quantity:锁定库存数量user_id:创建预订的用户IDexpire_at:过期时间(Timestamp类型,当前时间+15分钟)status:状态(active/completed/cancelled,默认active)
2. 创建预订与锁定库存(原子操作)
用户点击结账时,通过Firestore事务完成两个原子操作,避免并发超卖:
- 校验商品可用库存(总库存减去所有未过期
active预订的总量)是否充足 - 库存充足则减少主库存,同时创建
reservations文档并设置过期时间
示例代码(Node.js后端/Cloud Function):
const createReservation = async (productId, userId, quantity) => { const db = admin.firestore(); const productRef = db.collection('products').doc(productId); const reservationRef = db.collection('reservations').doc(); await db.runTransaction(async (transaction) => { const productDoc = await transaction.get(productRef); if (!productDoc.exists) throw new Error('商品不存在'); const { total_stock } = productDoc.data(); // 查询当前未过期的active预订总数量 const activeReservations = await db.collection('reservations') .where('product_id', '==', productId) .where('status', '==', 'active') .where('expire_at', '>', admin.firestore.Timestamp.now()) .get(); const reservedTotal = activeReservations.docs.reduce((sum, doc) => sum + doc.data().reserved_quantity, 0); const available = total_stock - reservedTotal; if (available < quantity) throw new Error('库存不足'); // 原子更新主库存 transaction.update(productRef, { total_stock: total_stock - quantity }); // 创建预订记录 transaction.set(reservationRef, { product_id: productId, user_id: userId, reserved_quantity: quantity, expire_at: admin.firestore.Timestamp.now().toDate().getTime() + 15 * 60 * 1000, status: 'active', created_at: admin.firestore.Timestamp.now() }); }); return reservationRef.id; };
3. 超时自动释放库存
- 给
reservations集合设置TTL索引:在Firestore控制台为expire_at字段配置TTL策略,文档过期后自动删除。 - 编写Cloud Function监听
reservations文档的onDelete事件,若删除的是active状态的记录,则将对应库存加回:
exports.onReservationDeleted = functions.firestore .document('reservations/{reservationId}') .onDelete(async (snap, context) => { const reservationData = snap.data(); if (!reservationData || reservationData.status !== 'active') return; const db = admin.firestore(); const productRef = db.collection('products').doc(reservationData.product_id); await db.runTransaction(async (transaction) => { const productDoc = await transaction.get(productRef); if (!productDoc.exists) return; const { total_stock } = productDoc.data(); transaction.update(productRef, { total_stock: total_stock + reservationData.reserved_quantity }); }); });
4. 完成结账后的处理
用户成功支付后,更新对应reservations文档的status为completed,这样即使文档过期被删除,Cloud Function也不会触发库存恢复。
5. 客户端显示可用库存
客户端实时计算可用库存:主库存减去所有未过期的active预订总量:
// 监听商品库存和相关预订 const productRef = db.collection('products').doc(productId); const reservationsQuery = db.collection('reservations') .where('product_id', '==', productId) .where('status', '==', 'active') .where('expire_at', '>', admin.firestore.Timestamp.now()); // 合并数据计算可用库存 productRef.onSnapshot(async (productSnap) => { const { total_stock } = productSnap.data(); const reservationsSnap = await reservationsQuery.get(); const reservedTotal = reservationsSnap.docs.reduce((sum, doc) => sum + doc.data().reserved_quantity, 0); const availableStock = total_stock - reservedTotal; // 更新UI显示可用库存 });
6. 安全规则配置
通过Firestore安全规则限制操作权限,防止恶意篡改:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 商品库存仅允许后端/Cloud Function修改 match /products/{productId} { allow read: if true; allow write: if false; // 或仅允许特定服务账号操作 } // 预订记录仅允许创建者读写 match /reservations/{reservationId} { allow read, write: if request.auth.uid == resource.data.user_id; allow create: if request.auth != null; } } }
为什么不推荐自定义认证令牌方案
- 认证令牌核心作用是身份验证,用来关联预订属于场景滥用,管理成本高。
- 令牌列表若公开会暴露用户隐私与预订信息;若限制读取,客户端无法计算可用库存,逻辑矛盾。
- 令牌过期后无法直接关联库存恢复逻辑,远不如Firestore TTL文档+触发器方案直接高效。
内容的提问来源于stack exchange,提问作者Pieter Pienaar
相关产品推荐
相关产品推荐

