Firestore新增成就文档时如何避免生成重复副本
首先你的现有代码存在逻辑偏差:achievements 是集合引用,调用 get() 返回的是集合查询快照,只有文档快照才有 exists 属性,集合快照判断是否为空用的是 empty 属性,你当前的 !snapshot.exists 永远为真,所以每次调用都会触发 add() 逻辑,这是重复创建的直接诱因。此外你的原有逻辑是判断用户的整个成就集合是否为空,而非判断待发放的单个成就是否已存在,本身不符合需求。
针对避免成就重复添加的需求,有两种成熟解决方案:
方案1:手动指定固定文档ID(更推荐)
每个成就预设唯一的固定标识(比如英文slug、数字ID),直接作为文档ID使用,从根源上避免重复。可以用Firestore的create()方法,该方法仅当目标文档不存在时才会写入,文档已存在时会直接抛出错误,完全规避重复写入风险。
const GiveAchievementTest = async () => { // 给当前成就指定唯一固定ID,示例为测试成就分配test_achievement作为唯一标识 const achievementRef = firestore .collection("users") .doc(user.uid) .collection("achievements") .doc('test_achievement'); // 手动指定文档ID try { // create方法仅文档不存在时写入,已存在会抛出already-exists错误 await achievementRef.create({ title: 'Test Achievement', reward: 200, desc: 'Testing Function' }); console.log('Gave Achievement'); } catch (error) { // 文档已存在的错误直接忽略即可 if (error.code === 'already-exists') { console.log('Achievement already granted'); return; } console.log('Error in giving achievement to user', error); } }
该方案优势:
- 仅需要1次读写操作,性能远优于先全量查询的方案
- 天然支持并发场景,就算同一时间多次调用接口,也不会生成重复文档
- 逻辑简洁,不需要额外维护查询条件和索引
方案2:先查询匹配字段判断是否存在
如果你的成就属于动态生成、没有预设唯一ID的场景,可以先查询对应字段(比如title)判断成就是否已发放,再决定是否写入。注意需要给查询的字段提前建立索引,高并发场景下建议配合事务使用,避免查询和写入的间隙有其他请求写入导致重复。
const GiveAchievementTest = async () => { const achievementsRef = firestore .collection("users") .doc(user.uid) .collection("achievements"); try { // 查询是否已有相同title的成就 const querySnapshot = await achievementsRef .where('title', '==', 'Test Achievement') .limit(1) // 仅查询1条即可,优化性能 .get(); if (querySnapshot.empty) { // 没有匹配记录,新增成就 await achievementsRef.add({ title: 'Test Achievement', reward: 200, desc: 'Testing Function' }); console.log('Gave Achievement'); } else { console.log('Achievement already granted'); } } catch (error) { console.log('Error in giving achievement to user', error); } }
内容的提问来源于stack exchange,提问作者Demonix
相关产品推荐
相关产品推荐

