设计数据库的结构

开始之前

您必须先创建一个 Firebase 项目,并将 Firebase Unity SDK 软件包添加到您的 Unity 项目中,然后才能使用 Firebase 实时数据库

设置:

前提条件

Android

  • Unity 5.0 或更高版本
  • Android NDK 版本 10d 或更高版本

iOS

  • Unity 5.0 或更高版本
  • Xcode 7.0 或更高版本

如果您还没有 Unity 项目,可以下载一个快速入门示例,试用一下特定的 Firebase 功能。如果使用快速入门示例,请不要忘记从项目设置中获取软件包标识符,下一步中会用到该标识符。

在 Firebase 控制台中设置您的应用

要将 Firebase 添加到您的应用,您需要有一个 Firebase 项目以及适用于您的应用的 Firebase 配置文件。

如果您还没有 Firebase 项目,请在 Firebase 控制台中创建一个。如果您已经有与自己的移动应用相关联的现有 Google 项目,请点击导入 Google 项目。如果没有,请点击添加项目

Android

  1. 点击将 Firebase 添加到您的 Android 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。
  2. 出现提示时,输入应用的软件包名称。请务必输入应用在使用的软件包名称;只有在将应用添加到 Firebase 项目时您才能进行此设置。
  3. 按照说明下载 google-services.json 文件。您随时可以再重新下载此文件
  4. 将此文件复制到项目的资源文件夹内的任意位置。

iOS

  1. 点击将 Firebase 添加到您的 iOS 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。
  2. 出现提示时,输入应用的软件包 ID。请务必输入应用在使用的软件包 ID;只有在将应用添加到 Firebase 项目时您才能进行此设置。
  3. 按照说明下载 GoogleService-Info.plist 文件。您随时可以再重新下载此文件
  4. GoogleService-Info.plist 文件添加到项目中。

    • 将从 Firebase 控制台下载的 GoogleService-Info.plist 拖动到 Unity 项目中的任意文件夹内。

将 Firebase Unity SDK 添加到您的应用

  1. 下载 Firebase Unity SDK
  2. 选择“Assets > Import Package > Custom Package”菜单项。
  3. 从之前下载的 Firebase Unity SDK 导入 FirebaseDatabase.unitypackage 软件包。
  4. 当出现 Import Unity Package 窗口时,点击 Import 按钮。

编译您的应用

Android

  1. 依次选择 File > Build Settings 菜单选项。
  2. 选择 Platform 列表中的 Android
  3. 点击 Switch Platform,选择 Android 作为目标平台。
  4. 等待 Unity 状态栏右下角的旋转进度条(正在编译)图标停止旋转。
  5. 点击 Build and Run

iOS

  1. 依次选择 File > Build Settings 菜单选项。
  2. 选择 Platform 列表中的 iOS
  3. 点击 Switch Platform,选择 iOS 作为目标平台。
  4. 等待 Unity 状态栏右下角的旋转进度条(正在编译)图标停止旋转。
  5. 点击 Build and Run

设计数据结构

本指南介绍了关于数据架构的几个主要概念,以及在您的 Firebase 实时数据库中设计 JSON 数据结构的最佳做法。

构建一个结构合理的数据库需要预先进行大量计划。最重要的是,您需要对如何保存数据及之后如何检索数据做好计划,尽可能地简化流程。

数据的结构形式:JSON 树

所有 Firebase 实时数据库数据都会存储为 JSON 对象。您可将该数据库视为托管在云端的 JSON 树。该数据库与 SQL 数据库不同,没有表格或记录的概念。当您将数据添加至 JSON 树时,它会变为现有 JSON 结构中的一个节点,带有一个关联的键。您可以自行提供键(例如用户 ID 或语义名称),也可以使用 Push() 方法由系统为您提供键。

如果您要自行创建键,必须确保您的键采用 UTF-8 编码,长度不超过 768 个字节,且不包含 .$#[]/ 或 ASCII 控制字符 0-31 或 127。

例如,假设某个聊天应用允许用户存储基本个人资料和联系人列表。通常用户个人资料位于诸如 /users/$uid 之类的路径中。用户 alovelace 的数据库条目可能如下所示:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

虽然数据库采用了 JSON 树,但数据库中存储的数据可以使用与可用 JSON 类型对应的某些原生类型来表示,便于您编写更易于维护的代码。

数据结构最佳做法

避免嵌套数据

Firebase 实时数据库允许嵌套最多 32 层数据,因此您可能会认为这应是默认结构。然而,当您提取数据库中某个位置的数据时,其所有子节点也会在检索范围内。另外,当您向某用户授予数据库中某个节点的读取或写入权限时,也会将该节点下所有数据的读取或写入权限授予该用户。因此,在实践中,最好使您的数据结构尽量扁平。

现以如下所示的多层嵌套结构为例,说明为什么嵌套数据不妥:

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

若采用这种嵌套设计,循环访问数据就会出现问题。例如,要列出聊天会话标题,就需要将整个 chats 树(包括所有成员和消息数据)都下载到客户端。

扁平化数据结构

如果数据被拆分到不同路径(又称反规范化),则可根据需要通过不同调用有效地下载。请参考以下扁平化结构:

{
  // 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
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

现在,只需针对每个会话下载几个字节并快速提取元数据(以便在用户界面中列出或显示会话),即可遍历会话列表。消息可单独提取并在到达时显示,从而确保用户界面及时快速地响应。

创建规模可扩展的数据

构建应用时,最好下载列表的子集。当列表含有成千上万条记录时,这种做法尤其常见。当这种关系属于静态和单向关系时,您只需在父对象下嵌套子对象即可。

有时,这种关系更具动态性,或者需要对此数据进行反规范化。通常,您可以通过查询来检索该数据的子集,以便对该数据进行反规范化,如检索数据中所述。

但即使这样也可能无法解决问题。以用户与群组之间的双向关系为例。用户可属于某个群组,群组可包含一系列用户。当需要确定用户属于哪些群组时,情况就会比较复杂。

我们需要的是一种巧妙的方法,不仅要列出用户所属的群组,而且只提取这些群组的数据。群组索引可在此发挥巨大作用:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

您可能注意到了,这种索引会同时在 Ada 的记录下及该群组的记录下存储相应关系数据,因此某些数据会重复。现在,alovelace 在一个群组的索引中,而 techpioneers 则列在 Ada 的个人资料中。所以,要从该群组删除 Ada,有两个地方需要更新。

对于双向关系而言,这是必要的冗余。这样,您就可以快速、高效地提取 Ada 的成员身份,而且即使用户列表或群组列表扩展到数百万条,或实时数据库安全规则阻止访问某些记录,也不会对提取造成影响。

这种方法(即,通过将 ID 列为键并将值设为 true 来反转数据)使对键的检查变得简单,只需读取 /users/$uid/groups/$group_id 然后检查它是否为 null。与查询或扫描数据相比,索引的速度更快、效率更高。

后续步骤

发送以下问题的反馈:

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