保存数据

保存数据的方法

PUT 将数据写入到指定路径(例如 fireblog/users/user1/<data>)或替换指定路径下的数据
PATCH 更新指定路径中的部分键,而不替换所有数据。
POST 向 Firebase 数据库中的数据列表添加数据。每次发送 POST 请求时,Firebase 客户端会生成一个独一无二的键,例如 fireblog/users/<unique-id>/<data>
删除 从指定 Firebase 数据库引用中移除数据。

使用 PUT 写入数据

通过 REST API 执行的基本写入操作为 PUT。为了演示如何保存数据,我们将构建一个包含博文和用户的博客应用。此应用的所有数据都将存储在 `fireblog` 路径下,位于 Firebase 数据库网址 `https://docs-examples.firebaseio.com/fireblog`。

首先,将一些用户数据保存到我们的 Firebase 数据库。我们将按唯一的用户名存储每个用户,同时还将存储其全名和出生日期。由于每位用户均具有唯一的用户名,因此在这里可以使用 PUT 而非 POST,这是因为我们已拥有相应键,无需另行创建。

通过使用 PUT,我们可以将一个字符串、数字、布尔值、数组或任何 JSON 对象写入到我们的 Firebase 数据库中。在此示例中,我们将向它传递一个对象:

curl -X PUT -d '{
  "alanisawesome": {
    "name": "Alan Turing",
    "birthday": "June 23, 1912"
  }
}' 'https://docs-examples.firebaseio.com/fireblog/users.json'

将 JSON 对象保存到数据库后,系统会自动以嵌套方式将对象属性映射到数据库子位置。如果导航至新建的节点,将会显示值“Alan Turing”。您也可以直接将数据保存到子位置:

curl -X PUT -d '"Alan Turing"' \
  'https://docs-examples.firebaseio.com/fireblog/users/alanisawesome/name.json'
curl -X PUT -d '"June 23, 1912"' \
  'https://docs-examples.firebaseio.com/fireblog/users/alanisawesome/birthday.json'

上述两个示例(即将两个值作为一个对象同时写入和将两个值分别写入子位置)所保存到 Firebase 数据库的数据相同:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    }
  }
}

成功的请求将返回 200 OK HTTP 状态代码,并且响应中将包含我们写入到数据库中的数据。第一个示例仅会在当前观察数据的客户端上触发一个事件,而第二个示例则会触发两个事件。务必注意,如果用户路径上已存在数据,则第一种方法会将其覆盖,但是第二种方法只会修改每个单独的子节点的值,其他子节点则保持不变。PUT 相当于 JavaScript SDK 中的 set()

使用 PATCH 更新数据

使用 PATCH 请求,我们可以更新某个位置的特定子节点,而不会覆盖现有数据。让我们通过 PATCH 请求将 Turing 的昵称添加到他的用户数据中:

curl -X PATCH -d '{
  "nickname": "Alan The Machine"
}' \
  'https://docs-examples.firebaseio.com/fireblog/users/alanisawesome.json'

上述请求将 nickname 写入 alanisawesome 对象中,而不会删除 namebirthday 子对象。请注意,如果我们将此处改为发出 PUT 请求,则 namebirthday 会被删除,因为该请求中不包括这些子节点。我们的 Firebase 数据库中的数据现在如下所示:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing",
      "nickname": "Alan The Machine"
    }
  }
}

成功的请求将返回 200 OK HTTP 状态代码,并且响应中将包含写入到数据库中的更新数据。

Firebase 还支持多路径更新。这意味着,PATCH 现在可以同时更新 Firebase 数据库中多个位置的值。该功能十分强大,可以帮助您对数据进行反规范化处理。使用多路径更新功能,我们可以同时为 Alan 和 Grace 添加昵称:

curl -X PATCH -d '{
  "alanisawesome/nickname": "Alan The Machine",
  "gracehopper/nickname": "Amazing Grace"
}' \
  'https://docs-examples.firebaseio.com/fireblog/users.json'

执行此更新之后,Alan 和 Grace 均已添加各自的昵称:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing",
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper",
      "nickname": "Amazing Grace"
    }
  }
}

请注意,试图通过写入包含路径的对象来更新对象将导致不同的行为。如果我们尝试用这种方法更新 Grace 和 Alan,让我们看看会发生什么情况:

curl -X PATCH -d '{
  "alanisawesome": {"nickname": "Alan The Machine"},
  "gracehopper": {"nickname": "Amazing Grace"}
}' \
  'https://docs-examples.firebaseio.com/fireblog/users.json'

这会导致不同的行为,即重写整个 /fireblog/users 节点:

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

使用条件请求更新数据

您可以使用条件请求(在 REST 中相当于事务)根据数据的现有状态对其进行更新。例如,如果要增加点赞计数器的值,并且想确保该计数准确地反映同时进行的多次点赞,可使用条件请求向计数器写入新值。如果有两个同时发生的写入操作,系统不会根据这两个写入操作将计数器值更改为同一数值,而是会令其中一个写入请求失败,然后您可以使用新值重试该请求。
  1. 如需在某个位置执行条件请求,请获取该位置的当前数据的唯一标识符(即 ETag)。如果该位置的数据发生更改,ETag 也会随之更改。您可以使用除 PATCH 之外的任何方法请求 ETag。下面的示例使用了 GET 请求。
    curl -i 'https://test.example.com/posts/12345/upvotes.json' -H 'X-Firebase-ETag: true'
    
    具体而言,在标头中调用 ETag 会在 HTTP 响应中返回指定位置的 ETag。
    HTTP/1.1 200 OK
    Content-Length: 6
    Content-Type: application/json; charset=utf-8
    Access-Control-Allow-Origin: *
    ETag: [ETAG_VALUE]
    Cache-Control: no-cache
    
    10 // Current value of the data at the specified location
    
  2. 在您的下一个 PUTDELETE 请求中包含返回的 ETag,以更新与该 ETag 值匹配的数据。继续看我们的示例,如需将计数器值更新为 11(即比初始提取值 10 大 1),并在该值不再匹配时使请求失败,请使用以下代码:
    curl -iX PUT -d '11' 'https://[PROJECT_ID].firebaseio.com/posts/12345/upvotes.json' -H 'if-match:[ETAG_VALUE]'
    
    如果指定位置的数据值仍为 10,即与 PUT 请求中的 ETag 匹配,则请求会成功,并向数据库写入 11。
    HTTP/1.1 200 OK
    Content-Length: 6
    Content-Type: application/json; charset=utf-8
    Access-Control-Allow-Origin: *
    Cache-Control: no-cache
    
    11 // New value of the data at the specified location, written by the conditional request
    
    如果该位置不再与 ETag 匹配(在另一用户向数据库写入了新值时可能会发生这种情况),则请求会失败,不向该位置写入数据。返回的响应包含新的值和 ETag。
    HTTP/1.1 412 Precondition Failed
    Content-Length: 6
    Content-Type: application/json; charset=utf-8
    Access-Control-Allow-Origin: *
    ETag: [ETAG_VALUE]
    Cache-Control: no-cache
    
    12 // New value of the data at the specified location
    
  3. 如果您决定重试请求,请使用此新信息。Realtime Database 不会自动重试已失败的条件请求。但是,您可以使用新值和 ETag 根据失败响应返回的信息构建新的条件请求。

基于 REST 的条件请求实现了 HTTP if-match 标准。但是,它们与此标准在以下方面存在差异:

  • 您只能为每个 if-match 请求提供一个(而不是多个)ETag 值。
  • 此标准建议所有请求都返回 ETag,但 Realtime Database 仅为包含 X-Firebase-ETag 标头的请求返回 ETag。这样可以降低标准请求的账单费用。

此外,条件请求的速度可能比典型的 REST 请求慢。

保存数据列表

如需为添加到 Firebase 数据库引用的每个子节点生成一个唯一的基于时间戳的键,我们可以发送一个 POST 请求。对于我们的 users 路径,因为每个用户都有一个唯一的用户名,所以定义我们自己的键会十分有用。但是,当用户将博文添加到应用中时,我们将使用 POST 请求为每篇博文自动生成一个键:

curl -X POST -d '{
  "author": "alanisawesome",
  "title": "The Turing Machine"
}' 'https://docs-examples.firebaseio.com/fireblog/posts.json'

我们的 posts 路径现在具有以下数据:

{
  "posts": {
    "-JSOpn9ZC54A4P4RoqVa": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

请注意,由于我们使用了 POST 请求,因此系统自动为我们生成了 -JSOpn9ZC54A4P4RoqVa 键。成功的请求将返回 200 OK HTTP 状态代码,并且响应中将包含添加的新数据的键:

{"name":"-JSOpn9ZC54A4P4RoqVa"}

移除数据

如需从数据库中移除数据,我们可以发送 DELETE 请求,其中包含我们想要从中删除数据的路径网址。下面的示例将从我们的 users 路径中删除 Alan:

curl -X DELETE \
  'https://docs-examples.firebaseio.com/fireblog/users/alanisawesome.json'

成功的 DELETE 请求将返回 200 OK HTTP 状态代码,并且响应中包含的 JSON 为 null

URI 参数

在将数据写入数据库时,REST API 可接受以下 URI 参数:

auth

利用 auth 请求参数,您可以访问受 Firebase Realtime Database Security Rules 保护的数据。所有请求类型都支持该参数。该参数可以是我们的 Firebase 应用的 Secret 或是身份验证令牌,我们将在用户授权部分介绍这些内容。在下面的示例中,我们将发送带有 auth 参数的 POST 请求,其中 CREDENTIAL 是我们的 Firebase 应用的 Secret 或某个身份验证令牌:

curl -X POST -d '{"Authenticated POST request"}' \
  'https://docs-examples.firebaseio.com/auth-example.json?auth=CREDENTIAL'

平面

我们可以使用 print 参数指定来自数据库的响应的格式。在我们的请求中添加 print=pretty 将以便于阅读的格式返回数据。GETPUTPOSTPATCHDELETE 请求支持 print=pretty

在写入数据时,若要禁止服务器输出任何内容,我们可以在自己的请求中添加 print=silent。由此产生的响应将是空的,如果请求成功,将返回 204 No Content HTTP 状态代码。GETPUTPOSTPATCH 请求支持 print=silent

写入服务器值

利用占位符值,您可以在某个位置写入服务器值。占位符值是只包含一个 ".sv" 键的对象。该键的值就是我们希望设置的服务器值的类型。例如,在创建用户后,如需设置时间戳,我们可以执行以下操作:

curl -X PUT -d '{".sv": "timestamp"}' \
  'https://docs-examples.firebaseio.com/alanisawesome/createdAt.json'

"timestamp" 是唯一支持的服务器值,它是从 UNIX 纪年开始算起的时间,以毫秒为单位。

提高写入性能

如果我们要将大量数据写入到数据库,则可以使用 print=silent 参数来提高我们的写入性能,并降低带宽使用量。在正常的写入操作中,服务器会使用已写入的 JSON 数据作为响应。如果指定了 print=silent,服务器在接收完数据后就会立即关闭连接,从而减少带宽占用。

在需要向数据库发出大量请求的情况下,我们可以通过在 HTTP 标头中发送 Keep-Alive 请求来重复使用该 HTTPS 连接。

错误条件

在下面这些情况下,REST API 将返回错误代码:

HTTP 状态代码
400 请求错误

以下错误情况之一:

  • 无法解析通过 PUTPOST 写入的数据。
  • 缺少通过 PUTPOST 写入的数据。
  • 请求尝试通过 PUTPOST 写入的数据太大。
  • REST API 调用的路径中包含无效的子节点名称。
  • REST API 调用路径太长。
  • 请求包含无法识别的服务器值。
  • 您的 Firebase Realtime Database Security Rules 中未定义该查询的索引。
  • 请求不支持某一个指定的查询参数。
  • 请求混用查询参数与浅层 GET 请求。
401 未经授权

以下错误情况之一:

404 找不到 找不到指定的 Firebase 数据库。
500 内部服务器错误 服务器返回一个错误。如需了解详情,请参阅错误消息。
503 服务不可用 指定的 Firebase Realtime Database 暂不可用,意味着未尝试过发出请求。

保护数据

Firebase 采用了一种安全语言,我们可以通过这种语言定义哪些用户拥有对于我们数据中不同节点的读写访问权限。如需了解详情,请参阅 Realtime Database Security Rules

上面我们介绍了如何保存数据,在下一部分中,我们将学习如何通过 REST API 从 Firebase 数据库中检索数据。