使用 Firebase 实时数据库保存数据 (C++)

入门

如果您尚未设置应用以及对数据库的访问权限,请先参阅Get Started指南。

获取 DatabaseReference

要将数据写入到数据库中,您需要一个 DatabaseReference 实例:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

保存数据

将数据写入 Firebase 实时数据库有四种方法:

方法 常见用法
SetValue() 将数据写入或替换到定义的路径,例如 users/<user-id>/<username>
PushChild() 添加到数据列表。每次您调用 Push() 时,Firebase 都会生成一个唯一的键(也可以用作唯一标识符),例如 user-scores/<user-id>/<unique-score-id>
UpdateChildren() 更新指定路径中的部分键,而不替换所有数据。
RunTransaction() 更新可能被并发更新损坏的复杂数据。

写入、更新或删除引用中的数据

基本写入操作

对于基本写入操作,您可以使用 SetValue() 将数据保存至指定引用,替换该路径上的任何现有数据。您可以使用此方法通过 Variant 类型传递 JSON 接受的类型,该 Variant 类型支持以下值:

  • 空(这会删除数据)
  • 整数(64 位)
  • 双精度浮点数
  • 布尔值
  • 字符串
  • 变体矢量
  • 将字符串映射到变体

以这种方式使用 SetValue() 将覆盖指定位置的数据,包括所有子节点。但是,您仍可在不重写整个对象的情况下更新子节点。如果要允许用户更新其个人资料,您可按照如下所示更新用户名:

dbref.Child("users").Child(userId).Child("username").SetValue(name);

向数据列表附加数据

使用 PushChild() 方法可将数据附加到多用户应用中的某个列表中。每次将新子项目添加到指定的 Firebase 引用时,PushChild() 方法均会生成一个唯一键。通过为列表中的每个新元素使用这些自动生成的键,多个客户端可以同时向同一位置添加子项目,而不会引起写入冲突。PushChild() 生成的唯一键是基于某个时间戳的,因此列表项目会自动按时间顺序排列。

您可以使用对 PushChild() 方法所返回的新数据的引用,获取子项目的自动生成的键的值,或为子项目设置数据。对 GetKey() 引用调用 PushChild() 将返回自动生成的键的值。

更新特定字段

要同时向一个节点的多个特定子节点写入数据,而不重写其他子节点,请使用 UpdateChildren() 方法。

调用 UpdateChildren() 时,可以通过为键指定路径来更新较低层级的子节点值。如果为了更好地进行调节而将数据存储在多个位置,则可使用数据扇出更新这些数据的所有副本。例如,游戏可能具有 LeaderboardEntry 类,如下所示:

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map&ltstd::string, Object&gt ToMap() {
    std::map&ltstring, Variant&gt result = new std::map&ltstring, Variant&gt();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

要创建 LeaderboardEntry 并同时将其更新到最近的分数 Feed 和用户自己的分数列表中,游戏会使用以下代码:

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map&ltstd::string, Variant&gt entryValues = entry.ToMap();

  std::map&ltstring, Variant&gt childUpdates = new std::map&ltstring, Variant&gt();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

此示例使用 PushChild() 在包含所有用户条目的节点 (/scores/$key) 创建了一个条目,同时使用 key() 检索相应键。接着,该键被用于在用户的分数位置 (/user-scores/$userid/$key) 创建第二个条目。

通过使用这些路径,只需调用 UpdateChildren() 一次即可同时更新 JSON 树中的多个位置,例如,上述示例如何在两个位置同时创建新条目。通过这种方式进行同时更新属于原子操作:所有更新要么全部成功,要么全部失败。

删除数据

删除数据最简单的方法是对于对数据位置的引用调用 RemoveValue()

您也可以通过指定 null Variant 作为另一个写操作(如 SetValue()UpdateChildren())的值进行删除。您可以将此方法与 UpdateChildren() 结合使用,在一次 API 调用中删除多个子节点。

了解您的数据的提交时间。

要了解您的数据何时被提交到 Firebase 实时数据库服务器,请检查 Future 结果是否成功。

将数据另存为事务

处理可能因并发修改而损坏的数据(例如,增量计数器)时,您可以使用事务操作。为此操作提供 DoTransaction 函数。此更新函数将数据的当前状态视为参数,并返回您要写入的新目标状态。如果另一个客户端在您成功写入新值前向该位置写入数据,则系统会使用新的当前值再次调用更新函数,然后重新尝试执行写入操作。

例如,在一个游戏中,您可以允许用户更新排行榜中前五个最高得分:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() &gt= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector&ltMutableData&gt children = mutableData.children();
      std::vector&ltMutableData&gt::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore &lt minScore) {
          minScore = childScore;
          minVal = &amp*it;
        }
      }
      if (minScore &gt score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map&ltstd::string, Variant&gt newScoreMap =
      new std::map&ltstd::string, Variant&gt();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

如果多个用户同时记录得分或客户端存在过时数据,使用事务可防止排行榜出错。如果事务遭拒绝,则服务器会将当前值返回到客户端,然后客户端会使用更新后的值再次运行事务。此过程将反复进行,直到事务被接受或尝试次数达到限制为止。

离线写入数据

如果客户端的网络连接中断,您的应用将继续正常运行。

对于所有有效数据,连接到 Firebase 数据库的每个客户端均维护着各自的内部版本。数据在写入时,首先会写入这类本地版本。然后,Firebase 客户端会尽可能将这些数据与远程数据库服务器以及其他客户端同步。

因此,对数据库执行的所有写入操作会立即触发本地事件,然后数据才会写入服务器。这意味着应用仍将保持随时响应的状态,无论网络延迟或连接状况如何。

连接重新建立之后,您的应用将收到一系列相应的事件,以便客户端与当前服务器状态进行同步,而不必编写任何自定义代码。

后续步骤

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面