需要 Firebase 数据结构建议

Firebase Data Structure Advice Required

我想跟踪和报告聊天室中的用户,但我不确定如何最好地在 Firebase 中构建数据。

概况

获取数据

我们可以访问 returns json 的 API,我计划每 1 分钟轮询 API 以找到所有聊天室(room_id ) 然后请求每个房间的所有用户 (user_id)。

设置数据

数据的设置完全在我们的掌控之中

举报我希望能够得到

问题

firebase 会为我的每条记录添加时间戳吗?还是每条记录都需要写时间?

不,但是您可以使用 the docs 中提到的 Firebase.ServerValue.TIMESTAMP。 Firebase 只存储您要求它存储的内容。

最好使用 unix Epoch 还是更容易理解的日期时间?

对所有日期时间(如果可能)使用 Firebase.ServerValue.TIMESTAMP(这是一个 Unix 纪元)。与使用 new Date().getTime() 或任何其他依赖于本地机器时间的方法相比,这确保了一致性和正确性(这通常是错误的,所以你最终会得到混乱的数据)。

Unix Epochs 也是整数,与 Firebase 的查询能力配合得很好,具体来说,我们可以使用 .startAt().endAt() 从特定日期范围内获取数据(我们将在下面的答案)。

我应该如何在 firebase 中构建这些数据?

您需要问的第一个问题是 "how will I be consuming this data?" Firebase 不是一个大的 SQL 数据库,我们可以在其中获得正确的结构,然后依靠复杂的查询来弥补我们的错误.

当您在 Firebase 中构建结构时,请确保它允许您以 specifc 方式加载数据。这意味着如果您知道您将有一个 room_id 的列表,您将要从中加载数据,那么您的房间结构应该基于这些 ID。

为一个简单的聊天室考虑这样的结构(我们将使用 $ 符号来表示通配符)。

{
  "rooms": {
    $room_id: {
      "users": {
        $user_id: true
      },
      "_meta": {
        closed: Boolean
      },
      "messages": {
        $message_id: {
          "user_id": $user_id,
          "text": ""
        }
      }
    }
  },
  "users": {
    $user_id: {...}
  }
}

当 ID 为 abe 的用户加入 room_idroom_one 的房间时,我们知道他们需要将自己标记为聊天室的活跃成员通过将位置 /rooms/room_one/users/abe 设置为 true

加入房间的函数如下所示。

function joinRoom(room_id) {
  // We assume `ref` is a Firebase reference to the root of our Firebase
  var roomRef = ref.child("rooms").child(room_id);
  roomRef.child("users").child(myUserId).set(true);
  return roomRef;
}

这是具体的。我们获得了一些信息,并且因为我们的数据结构是合乎逻辑的,所以我们可以轻松地假设需要写入哪些数据,而无需从 Firebase 加载任何数据。

但这对于您的情况来说还不够好,因为您还需要报告。我们将根据您的需求逐步改进我们的结构

从 x 到 y 日期和时间我们看到了多少唯一用户

假设您是在每个房间的基础上交谈,这是一个简单的改变。

{
  "rooms": {
    $room_id: {
      "users": {
        $user_id: true
      },
      "users_history": {
        $push_id: {
          user_id: ...,
          timestamp: ...
        } 
      },
      "messages": {
        $message_id: {...}
      }
    }
  },
  "users": {
    $user_id: {...}
  }
}

我们添加 /users/$room_id/users_history 位置。这是用户每次进入该房间的列表。我们增加了一些复杂性,所以我们的加入房间功能看起来像这样。

function joinRoom(room_id) {
  var roomRef = ref.child("rooms").child(room_id);
  roomRef.child("users_history").push({
    user_id: myUserId,
    timestamp: Firebase.ServerValue.TIMESTAMP
  });
  roomRef.child("users").child(myUserId).set(true);
  return roomRef;
}

现在我们可以使用 Firebase Query 轻松地报告给定时间内有多少用户进入了房间。

function roomVisitors(room_id, start_datetime, end_datetime) {
  var roomRef = ref.child("rooms").child(room_id),
      queriedRoomRef = roomRef
        .orderByChild('timestamp')
        .startAt(start_datetime.getTime())
        .endAt(end_datetime.getTime());

  // Assuming we use some ES6 promise library
  return new Promise(function (resolve, reject) {
    queriedRoomRef.once("value", function (users) {
      /* Users will be a snapshot of all people who 
         came into the room for the given range of time. */
      resolve(users.val());
    }, function (err) {
      reject(err);;
    });
  });
}

我们稍后会讨论这样做是否真的 "specific",但这是一般的想法。

1 位用户从 x 到 y 日期和时间的在线时间

我们还没有充实我们的 /users/$user_id 结构,但我们必须在这里完成。在这种情况下,我们唯一需要查找用户在线时间的信息就是他们的 user_id。所以我们必须将此信息存储在 /user/$user_id 下,因为如果我们将其存储在 /rooms/ 下,我们将不得不加载 所有 个房间的数据并循环遍历它找到相关的用户信息,这不是很具体。

{
  "rooms": {
    $room_id: {
      "users": {
        $user_id: true
      },
      "users_history": {
        $push_id: {
          user_id: ...,
          timestamp: ...
        } 
      },
      "messages": {
        $message_id: {...}
      }
    }
  },
  "users": {
    $user_id: {
      "online_history": {
        $push_id: {
          "action": "", // "online" or "offline" 
          "timestamp": ... 
        }
      }
    }
  }
}

现在我们可以构建一个 ref.onAuth(func) 来跟踪我们的在线时间。

var userRef;
ref.onAuth(function (auth) {
  if (!auth && userRef) {
    // If we haven no auth, i.e. we log out, cancel any onDisconnect's
    userRef.onDisconnect().cancel();
    // and push a record saying the user went offline
    userRef.child("online_history").push({
      action: "offline",
      timestamp: Firebase.ServerValue.TIMESTAMP
    });
  } else if (auth) {
    userRef = ref.child('users').child(auth.uid);
    // add a record that we went offline
    userRef.child('online_history').push({
      action: "online",
      timestamp: Firebase.ServerValue.TIMESTAMP
    });
    // and if the user disconnects, add a record of going offline
    userRef.child('online_history').push().onDisconnect().set({
      action: "offline",
      timestamp: Firebase.ServerValue.TIMESTAMP
    });
  }
});

使用这种方法,我们现在可以编写一个函数来遍历 online/offline 日志,并使用上面使用的相同查询方法为给定范围添加时间,但我将把它留作练习对于 reader.

关于特异性和性能的说明

报告功能都不是特定的。当我们在第一个查询中获得访问过房间的用户列表时,我们正在抓取一个充满用户名的大对象并将所有数据拉下来然后在客户端解析它,而我们真正想要的只是一个整数唯一身份访问者数量的价值。

在这种情况下,您确实希望使用服务器端 SDK 雇用 NodeJS 工作人员。这个工作人员可以坐下来观察数据结构的变化,并在数据变化时自动汇总数据,这样您的客户就可以查看 /rooms/$room_id/_meta/analytics/uniqueVisitorsThisWeek 之类的位置,并简单地获取 10 之类的数字。

关键是,存储很便宜,像这样汇总和缓存数据很便宜,但前提是它在服务器端完成。如果您不具体并且加载太多并尝试执行总结客户端,您将浪费 CPU 周期和带宽。

如果您曾经将数据从 Firebase 加载到客户端而不显示该数据,则您应该重新设计数据结构以使其更具体。