在Firebase中为聊天应用程序构建数据结构

14

我正在按照Firebase的指南为聊天应用程序构建数据结构。他们建议采用以下所示的结构。

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // converation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

我该如何组织用户数据,以便能轻松地显示他们参与的所有聊天的列表,并为每个聊天显示最后一条消息和时间戳。如果我采用以下结构:

   "users": {
    "ghopper": {
      "name": "Gary Hopper",
      "chats": {
          "one: true",
          "two": true
      }
    },
    "alovelace" { ... }
  },

我可以轻松地获取特定用户(例如ghopper)的每个聊天组列表,方法是在Swift中执行以下操作:

ref.child("users").child("ghopper").child("chats").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
  //do something with data
}

但是在这个快照中,我没有最后一条消息和时间戳。我需要做什么才能访问这些数据?

  • 为每个用户复制所有这些数据?例如添加users / ghopper / chats / one / {"lastMessage": "ghopper:Relay malfunction found. Cause: moth。", "timestamp":1459361875666}
  • 为用户所在的每个聊天创建查询"chats/specificGroupId"(添加多个监听器)?
  • 其他办法?
2个回答

17

如何构建用户数据结构,以便轻松显示他们参与的所有聊天列表,并为每个聊天显示最后一条消息和时间戳。

通过向聊天节点添加参与用户,稍微调整聊天结构。

"chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
      users
       uid_1: true
       uid_3: true
    },
    "two": { ... },

然后,您可以对一个特定用户参与的所有聊天进行深度查询 - 这将返回uid_3参与的聊天

chatsRef.queryOrderedByChild("users/uid_3").queryEqualToValue(true)
     .observeSingleEventOfType(.Value, withBlock: { snapshot in

     //.Value can return multiple nodes within the snapshot so iterate over them
     for child in snapshot.children {
          let lastmsg = child.value["lastMessage"] as! String
          let timestamp = child.value["timestamp"] as! String
          print(lastmsg)
          print(timestamp)
     }     
})
注意,每个Firebase用户在通过auth.uid创建时都会获得一个独立的用户ID。这应该(通常)作为每个用户的键使用。

1
嗨Jay,谢谢你的回答。我尝试了你的解决方案,似乎可以解决问题。但是,如果我有很多用户,考虑到我需要查找所有“chats/”以找到用户所在的所有聊天记录,这个操作不会变得非常昂贵吗? - Nilsymbol
1
@Nilsymbol 很高兴它起作用了!看起来你并没有拉下几兆字节的数据,只是一些带有子节点的几百个节点。Firebase非常快,所以在成千上万的节点上执行这样的查询非常快;它甚至不会让Firebase喘不过气来。我们已经测试过了! - Jay
@Jay,安全性如何融入其中?如果我的用户不是聊天的一部分,而我将自己添加到该聊天中-这意味着我可以查询它-这意味着所有聊天都可以被所有用户读取。如果现有的聊天成员添加了我-那么该聊天成员必须知道我的用户ID-这意味着所有用户都可以被所有用户读取。有没有办法解决这个问题?如何更新索引? - Itai Hanski
@ItaiHanski 这是一个很好的问题,可能应该单独提出来问。Firebase中的规则可以添加安全性,并允许或禁止用户访问特定节点。很大程度上取决于您如何保护数据;例如,您可以通过仅允许用户访问他们所在的聊天节点来保护它。在这种情况下,没有人可以查询任何节点-这是有道理的,因为用户只能访问他们所在的节点。发表另一个问题,让我们看看社区提供了什么解决方案。 - Jay
@Jay 好注意。这是它的链接:https://dev59.com/Y5fga4cB1Zd3GeqPALtR - Itai Hanski
显示剩余4条评论

3
在您拥有用户所在所有聊天列表的区块中,您能否执行以下操作:
var dictionary: [String: Long]   
var lastMessage: String 
for chat in listOfChatsUserIsIn
    ref.child("chats").child("\(chat)").child("lastMessage").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
        lastMessage = snapshot
        ref.child("chats").child("\(chat)").child("timestamp").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
            //add timestamp and last message to dictionary
        }
    }

我不知道我的语法有多正确。我还在学习firebase。但是,我认为这基本上是你的第二个建议。不知道你还能怎么获取数据。这将是O(2n),但并不糟糕。
【更新1】
我在我的代码中比较懒。我把lastMessage = snapshot放在那里,这样你就可以在下一个块中将其添加到字典中。
至于Firebase是否异步执行。我认为只要你使用时间戳或消息作为键,另一个作为字典的值,这仍然可以工作。它可能不按顺序填充,但你可以稍后根据时间戳对其进行排序。虽然,是的,这可能不是最佳实践。
Jay,我喜欢你的解决方案。你不也要列出uid_2:false吗?
不幸的是,似乎这两种数据库结构都随着用户 -> inf和聊天 -> inf而呈n ^ 2增长。

lastMessage = snapshot的作用是什么,因为你没有使用那个变量?此外,在块(闭包)中使用self.variable name是一个好习惯。另外,第一个ref.child内部的ref.child是重复的,因此它将读取相同的数据。最大的问题是Firebase是异步的,而那个紧密的循环要比数据在observe块中返回的速度快得多,所以我认为我们会遇到一些数据完整性问题 - 可能应该让Firebase异步地完成它的“工作”。 - Jay
回答你的问题......不需要 uid_2:false,因为如果它不存在,它可能默认是 false(这取决于它的编码方式)。而且,数据库不会呈指数增长。如果你有10个用户和1个聊天,那么所有10个用户都在那个聊天中,所以唯一增长的就是聊天中的用户节点(在我的回答中),同样地,如果你有1000个用户和100个聊天,每个聊天有两个用户,那么仍然只有100个聊天,每个聊天都有一个用户节点,其中包含两个用户。即使有1000个用户和1000个聊天,也不是很大量的数据。 - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接