Firebase 数据库:引用动态值

Firebase database: Referencing a dynamic value

假设我正在尝试构建一个群发消息应用程序,所以我设计的数据库结构如下所示:

users: {
    uid1: { //A user id using push()
        username: "user1"
        email: "aaa@bbb.ccc"
        timestampJoined: 18594659346
        groups: {
            gid1: true,
            gid3: true
        }
    }
    uid2: {
        username: "user2"
        email: "ddd@eee.fff"
        timestampJoined: 34598263402
        groups: {
            gid1: true,
            gid5: true
        }
    }
    ....
}

groups: {
    gid1: { //A group id using push()
        name: "group1"
        users: {
            uid1: true,
            uid2: true
        }
    }
    gid2: {
        name: "group2"
        users: {
            uid5: true,
            uid7: true,
            uid80: true
        }
    }
    ...
}

messages: {
    gid1: {
        mid1: {  //A message id using push()
            sender: uid1
            message: "hello"
            timestamp: 12839617675
        }
        mid2: {
            sender: uid2
            message: "welcome"
            timestamp: 39653027465
        }
        ...
    }
    ...
}

根据 Firebase 的文档,这会扩展得很好。

现在假设在我的应用程序中,我想在每条消息上显示发件人的用户名。

查询每条消息的用户名显然是不好的,所以我找到的解决方案之一是在每条消息中复制用户名。

消息节点现在如下所示:

messages: {
    gid1: {
        mid1: {  //A message id using push()
            sender: uid1
            username: "user1"
            message: "hello"
            timestamp: 12839617675
        }
        mid2: {
            sender: uid2
            username: "user2"
            message: "welcome"
            timestamp: 39653027465
        }
        ...
    }
    ...
}

现在我想为用户添加更改用户名的选项。

因此,如果用户决定更改其用户名,则必须在用户节点以及他发送的每条消息中进行更新。

如果我采用 "listener for every message" 方法,那么更改用户名会很容易,因为我需要在一个位置更改名称。

现在,我还必须更新他发送的每个组的每条消息中的名称。

我假设为用户 ID 查询整个消息节点是一个糟糕的设计,因此我考虑创建另一个节点来存储用户已发送的所有消息的位置。

它将看起来像这样:

userMessages: {
    uid1: {
        gid1: {
            mid1: true
        }
        gid3: {
            mid6: true,
            mid12: true
        }
        ...
    }
    uid2: {
        gid1: {
            mid2: true
        }
        gid5: {
            mid13: true,
            mid25: true
        }
        ...
    }
    ...
}

现在我可以快速获取特定用户的所有消息的位置,并通过一次 updateChildren() 调用更新用户名。

这真的是最好的方法吗?我真的需要复制这么多数据(数百万条消息)只是因为我引用了一个动态值(用户名)吗?

或者在处理动态数据时有更好的方法吗?

要遵循的步骤:

  • 每次重新启动应用程序时获取用户名

  • 在本地缓存它们

  • 根据 uid 从缓存中显示用户名

  • 完成

注意:如何获取用户名取决于您的实现方式

这是一个完美的例子,说明了为什么通常父节点名称(键)应该与其包含或表示的值分离。

所以一些大局观可能会有所帮助,考虑用户体验可能会提供答案。

Now lets assume that inside my application, I want to display the sender's username on every message.

但你真的想这么做吗?您的用户真的想滚动浏览包含 10,000 条消息的列表吗?可能不会。最有可能的是,该应用程序将显示这些消息的一个子集,甚至可能一次显示 10 或 12 条。

这里有一些想法:

假设用户 table:

users
  uid_0
    name: Charles
  uid_1
    name: Larry
  uid_2:
    name: Debbie

和一条消息 table

messages
  msg_1
     sender: uid_1
     message: "hello"
     timestamp: 12839617675
     observers:
        uid_0: true
        uid_1: true
        uid_2: true

每个用户登录并且应用程序执行查询以观察他们所属的消息节点 - 应用程序显示显示消息的消息文本以及也在观察该消息的每个用户名('group').

这也可以用来只显示发布它的用户的用户名。

解决方案1:应用启动时,加载用户节点中的所有用户,以uid_为键存储在字典中。

当观察消息节点时,每条消息都会被加载,您将通过密钥将其他用户(或发布者)的 uid 存储在 users_dict 中,因此只需选择他们的名字:

let name = users_dict["uid_2"]

解决方案 2:

假设您的用户节点(这是典型的)和一千个用户中存储了大量数据。当您只对他们的名字感兴趣时,加载所有这些数据是没有意义的,所以您可以

a) 使用解决方案 #1 并忽略除 uid 和名称之外的所有其他数据或

b) 在 firebase 中创建一个单独的 'names' 节点,它只保留用户名,因此您不需要将其存储在用户节点中。

names:
  uid_0: Charles
  uid_1: Larry
  uid_2: Debbie

如您所见,即使有几千个用户,加载的数据也很少。而且...这里很酷的是,如果您向名称节点添加一个侦听器,如果一个用户更改他们的名称应用程序将收到通知并可以相应地更新您的UI。

解决方案 3:

根据需要加载您的姓名。虽然从技术上讲你可以这样做,但我不推荐这样做:

观察用户所属的所有消息节点。这些节点将被读入,并在读入时构建一个您需要其名称的 uid 字典。然后根据uid对每个用户名进行查询。这可以工作,但您必须考虑到 Firebase 的异步性质,并留出时间加载名称。同样,您可以加载一条消息,然后使用路径加载该消息的用户名:users/uid_x/user_name。再次,虽然这会进入异步计时问题,您在异步调用或循环中嵌套异步调用,但应该避免这种情况。

用户体验和保持 Firebase 结构尽可能平坦的任何解决方案的重点。

例如,如果您确实想要加载 10,000 条消息,请考虑将消息文本或主题拆分到另一个节点中,并且只为您的初始 UI 列表加载这些节点。当用户深入了解消息时,然后加载其余数据。

你只需要这个结构

  mid1: {  //A message id using push()
     sender: uid1
     message: "hello"
     timestamp: 12839617675
  }

在读取每个子项后,可以使用单值事件侦听器直接从用户 "users/uid1/username" 读取用户名。 Firebase 应该与顺序调用一起使用,因为您不能创建像 SQL、

中那样的复杂查询

为了保持高效,您可以:

1) 创建一个参考字典以将其用作缓存处理程序,在您读取每条消息后验证您是否具有每个键的值:

   [uid1:"John",uid2:"Peter",....etc...]

如果密钥不存在,则添加指向 /users/$uid/username 的单值侦听器,该侦听器在其回调

中处理 "add to cache"

2) 使用 limitTo startAt 和 endAt 查询对侦听器进行分页,避免带来用户看不到的数据

*没有必要在每次用户更改时实际更新所有消息和所有节点,想象一个有 100 个用户的聊天组,其中每个用户有 20 条消息 ...2000 条更新与你的单个 updateChildren( ) 调用效率极低,因为它不可扩展,而且您更新的数据在现实生活场景中肯定没有用户会再次看到(比如 2000 条聊天消息中的第一条消息)