You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Firestore事务中实现getOrCreate逻辑以构建引用收藏服务?

Firestore事务中实现作者/来源的Get-Or-Create并创建引用

我正在开发一个引用收藏服务,数据结构如下:

  • quote集合包含quote、userId、authorId、sourceId四个字段;
  • author集合包含name、userId两个字段;
  • source集合包含name(例如“Star Wars”)、type(如book、film)、userId三个字段。

我的需求是:当用户保存引用时,通过Firestore事务完成以下操作:

  1. 检查作者是否存在(按name+userId查询,存在则返回authorId,不存在则创建作者);
  2. 对来源(name+type+userId)执行同样逻辑,获取对应的sourceId;
  3. 最后创建包含authorId和sourceId的quote对象。

但我发现Firestore的事务仅支持getDocument方法,无法使用whereField()进行查询。我了解到可以通过规则强制创建,并用try/catch捕获错误后执行getDocument,但不确定是否可行。如果只能依赖getDocument,是否需要将author/source集合的ID设为类似“userId + 哈希(作者/来源名称)”的复合键?请问该如何实现这一需求,Firestore是否支持此类场景?

附上我的伪代码:

Firestore.transaction { 
  // Get or create author 
  let author = Author.getOrCreate("Yoda", userId) 
  // Get or create source 
  let source = Source.getOrCreate("Star Wars", "film", userId) 
  // Save quote 
  let quote = Quote.create({ 
    quote: "Do or do not. There is no try", 
    authorId: author.id, 
    sourceId: source.id, 
    userId: userId 
  }) 
}

解决方案:基于复合文档ID的Get-Or-Create模式

Firestore确实不支持在事务中执行查询(比如whereField)——事务只能针对已知的文档ID进行读写操作,这是为了保证事务的原子性和性能。所以你的思路是对的:要实现事务内的get-or-create,必须提前确定文档的唯一ID,也就是把“用户唯一标识+内容标识”组合成文档ID,避免重复。

1. 设计复合文档ID

对于author集合,每个用户的作者名称是唯一的(同一个用户不会重复创建同名作者),所以可以把文档ID设为:

`${userId}_${authorName.toLowerCase().replace(/\s+/g, '-')}`

或者更稳妥的方式是对作者名称做哈希(比如SHA-1),避免特殊字符或过长的问题:

`${userId}_${hash(authorName)}`

同理,source集合的文档ID需要包含userId、sourceName和type,因为同一个用户可能有同名但不同类型的来源(比如叫“Dune”的书和电影),所以ID可以是:

`${userId}_${hash(sourceName)}_${type}`

这样每个“用户-作者”和“用户-来源-类型”组合都对应唯一的文档ID,事务里可以直接通过ID获取文档。

2. 事务内的Get-Or-Create实现

在事务中,你可以先尝试获取指定ID的文档:

  • 如果文档存在,直接拿到它的ID;
  • 如果文档不存在,就在事务内创建这个文档,然后使用新生成的ID。

以JavaScript为例,实现getOrCreateAuthor的逻辑大概是这样:

async function getOrCreateAuthor(transaction, userId, authorName) {
  const authorDocId = `${userId}_${crypto.createHash('sha1').update(authorName.toLowerCase()).digest('hex')}`;
  const authorRef = firestore.collection('author').doc(authorDocId);
  
  const authorDoc = await transaction.get(authorRef);
  if (authorDoc.exists) {
    return { id: authorDoc.id, ...authorDoc.data() };
  } else {
    // 创建新作者文档
    await transaction.set(authorRef, {
      name: authorName,
      userId: userId
    });
    return { id: authorDocId, name: authorName, userId };
  }
}

getOrCreateSource的逻辑类似,只是文档ID要包含type:

async function getOrCreateSource(transaction, userId, sourceName, type) {
  const sourceDocId = `${userId}_${crypto.createHash('sha1').update(sourceName.toLowerCase()).digest('hex')}_${type}`;
  const sourceRef = firestore.collection('source').doc(sourceDocId);
  
  const sourceDoc = await transaction.get(sourceRef);
  if (sourceDoc.exists) {
    return { id: sourceDoc.id, ...sourceDoc.data() };
  } else {
    await transaction.set(sourceRef, {
      name: sourceName,
      type: type,
      userId: userId
    });
    return { id: sourceDocId, name: sourceName, type, userId };
  }
}

然后把这些方法整合到事务里:

async function createQuote(userId, quoteText, authorName, sourceName, sourceType) {
  const firestore = getFirestore();
  return firestore.runTransaction(async (transaction) => {
    // 获取或创建作者
    const author = await getOrCreateAuthor(transaction, userId, authorName);
    // 获取或创建来源
    const source = await getOrCreateSource(transaction, userId, sourceName, sourceType);
    // 创建引用
    const quoteRef = firestore.collection('quote').doc();
    await transaction.set(quoteRef, {
      quote: quoteText,
      userId: userId,
      authorId: author.id,
      sourceId: source.id
    });
    return { quoteId: quoteRef.id, author, source };
  });
}

3. 关于try/catch+规则的方案可行性

你提到的“用规则强制创建,try/catch捕获错误后get”的方案其实也能工作,但不推荐在事务中使用:因为try/catch的方式本质是先尝试写入,失败(比如文档已存在)再读取,这会增加额外的网络请求,而且在高并发场景下可能出现多次重试,性能不如直接用复合ID的方式稳定。

不过如果你的业务场景中用户重复创建同一作者/来源的概率极低,这种方式也可以作为备选,但要注意Firestore的写入冲突重试机制——事务本身会自动重试,所以try/catch需要和事务的重试逻辑配合好,避免逻辑混乱。

4. Firestore对这类场景的支持

Firestore完全支持这种基于复合ID的get-or-create模式,这也是官方推荐的在事务中处理“唯一约束”场景的方案。因为事务能保证整个操作的原子性:要么作者、来源、引用都创建成功,要么全部失败,不会出现中间状态。

内容的提问来源于stack exchange,提问作者Mickey Cheong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:37:11