Firestore中serverTimestamp存储不一致及排序异常问题排查
问题
在理解Firestore的serverTimestamp时遇到困惑:在Firebase云函数中使用FieldValue.serverTimestamp(),或在JavaScript客户端代码中使用serverTimestamp()保存文档时,数据库中有时存储的是{nanoseconds: xxx, seconds: xxx}对象,有时是时间戳格式的日期。这导致使用query(collectionRefOrders, orderBy('createdAt', 'desc'), limit(10))查询订单时,即使对象形式的文档创建时间更晚,却排在其他文档之前。以下是云函数及客户端的代码,请问出现该问题的原因是什么?哪里操作有误?
云函数代码
const { getFirestore, FieldValue } = require('firebase-admin/firestore'); const firebaseDB = getFirestore(); exports.createOrderFromTunnel = functions.region('europe-west3') .runWith({ timeoutSeconds: 10, memory: "4GB", }) .https .onRequest(async (req, res) => { cors(req, res, async () => { try { const { apiKey } = req.body; const project = await getProjectFromApiKey(apiKey); if (!project) { return res.json({ success: false, error: 'Unauthorized: invalid or missing api key' }); } const contactData = { address: {}, createdAt: FieldValue.serverTimestamp() }; const orderData = { accounting: { totalHT: 0, totalTTC: 0, totalTVA: 0, }, createdAt: FieldValue.serverTimestamp(), status: 'NEW', }; const refProject = firebaseDB .collection('projects') .doc(project.id); const colOrder = firebaseDB.collection(`projects/${project.id}/orders`) const refOrder = colOrder.doc(); const colContact = firebaseDB.collection(`projects/${project.id}/contacts`) const refContact = colContact.doc(); await firebaseDB.runTransaction(async transaction => { const snapProject = await transaction.get(refProject); const dataProject = snapProject.data(); const sequenceContact = dataProject.sequenceContact; const sequenceOrder = dataProject.sequenceOrder; contactData.sequence = sequenceContact; orderData.sequenceNumber = sequenceOrder; await transaction.set(refContact, contactData); orderData.customer.id = refContact.id; orderData.customer.sequence = sequenceContact; await transaction.set(refOrder, orderData); await transaction.update(refProject, { sequenceContact: sequenceContact + 1, sequenceOrder: sequenceOrder + 1, totalContacts: dataProject.totalContacts + 1, totalOrders: dataProject.totalOrders + 1, }); return refOrder.id; }); return res.json({ success: true }); } catch (err) { functions.logger.error(err); return res.json({ success: false, err }); } }); });
客户端代码
const createOrder = async (projectId) => { try { const orderData = { accounting: { totalHT: 0, totalTTC: 0, totalTVA: 0, }, createdAt: serverTimestamp(), status: 'NEW', surface: 0, }; const refProject = doc(firebaseDB, 'projects', projectId); const colOrder = collection(firebaseDB, `projects/${project.id}/orders`) const refOrder = doc(colOrder); return await runTransaction(firebaseDB, async (transaction) => { const snapProject = await transaction.get(refProject); if (!snapProject.exists()) { throw "Document does not exist!"; } const dataProject = snapProject.data(); const sequence = dataProject.sequenceOrder; orderData.sequenceNumber = sequence; transaction.set(refOrder, orderData); transaction.update(refProject, { sequenceOrder: sequence + 1, totalOrders: dataProject.totalOrders + 1 }); return refOrder.id; }); } catch (e) { console.error(e); return null; } };
原因与解决方案
问题原因
- 客户端变量错误:客户端代码中创建订单集合时,使用了未定义的
project变量,正确参数应为传入的projectId,这会导致路径错误,可能在错误位置创建文档,甚至触发意外的写入逻辑,混入非Timestamp类型的createdAt值。 - 事务中对象的可变操作:云函数事务内直接修改外部定义的
orderData对象,事务的原子性要求数据对象在提交前保持稳定,这种外部对象的修改可能导致serverTimestamp()的引用异常,最终写入的数据类型不符合预期。 - 数据类型不统一:如果存在其他未展示的代码中,混合使用了
serverTimestamp()、普通JS Date对象或数字时间戳写入createdAt字段,会导致存储类型混乱,Firestore在排序时会优先将对象结构的Timestamp排在Date/数字时间戳之前,引发排序异常。
解决方案
- 修复客户端路径错误:将客户端代码中的
colOrder = collection(firebaseDB,projects/${project.id}/orders)改为colOrder = collection(firebaseDB,projects/${projectId}/orders),确保文档写入到正确的集合路径。 - 事务内使用不可变数据:在云函数事务内部生成新的订单数据对象,避免修改外部变量,替换原有逻辑:
// 事务内生成新的订单数据 const orderToSave = { ...orderData, sequenceNumber: sequenceOrder, customer: { id: refContact.id, sequence: sequenceContact } }; await transaction.set(refOrder, orderToSave); - 统一写入类型:检查所有写入
createdAt字段的代码,确保全部使用FieldValue.serverTimestamp()(云函数端)或serverTimestamp()(客户端),禁止混合写入Date对象或数字时间戳。 - 修正已有错误数据:对于已存储的非Timestamp类型数据,通过云函数批量更新:
// 示例:批量转换Date对象为Timestamp const ordersRef = firebaseDB.collection('projects/{projectId}/orders'); const snapshot = await ordersRef.where('createdAt', 'instanceof', 'Date').get(); const batch = firebaseDB.batch(); snapshot.docs.forEach(doc => { batch.update(doc.ref, { createdAt: admin.firestore.Timestamp.fromDate(doc.data().createdAt) }); }); await batch.commit();
内容的提问来源于stack exchange,提问作者Alexis Santini
相关产品推荐
相关产品推荐

