透過 Cloud Functions 擴充即時資料庫


有了 Cloud Functions,您就能在 Firebase Realtime Database中處理事件,不必更新用戶端程式碼。 Cloud Functions 可讓您以完整管理員權限執行 Realtime Database 作業,並確保 Realtime Database 的每項變更都會個別處理。您可以透過資料快照或 Admin SDK 進行Firebase Realtime Database變更。

在一般生命週期中,Firebase Realtime Database 函式會執行下列操作:

  1. 等待特定 Realtime Database 路徑的變更。
  2. 在事件發生時觸發並執行它的工作。
  3. 接收資料物件,其中包含儲存在該路徑的資料快照。

您可以因應 Firebase Realtime Database 中資料庫節點的寫入、建立、更新或刪除作業,觸發函式。如要控管函式觸發時間,請指定其中一個事件處理常式,並指定要監聽事件的 Realtime Database 路徑。

Realtime Database 執行個體位置與函式位置之間的距離,可能會造成顯著的網路延遲。此外,區域不符也可能導致部署失敗。為避免發生上述情況,請指定函式位置,使其與資料庫執行個體位置相符。

處理 Realtime Database 事件

函式可讓您處理兩種特定程度的 Realtime Database 事件;您可以只監聽寫入、建立、更新或刪除事件,也可以監聽參照的任何變更。

以下是可回應 Realtime Database 事件的處理常式:

Node.jsPython
  • onValueWritten()Realtime Database 中建立、更新或刪除資料時觸發。
  • onValueCreated() 只有在 Realtime Database 中建立資料時才會觸發。
  • onValueUpdated() 只有在 Realtime Database 中更新資料時才會觸發。
  • onValueDeleted() 只有在 Realtime Database 中刪除資料時才會觸發。
  • on_value_written()Realtime Database 中建立、更新或刪除資料時觸發。
  • on_value_created() 只有在 Realtime Database 中建立資料時才會觸發。
  • on_value_updated() 只有在 Realtime Database 中更新資料時才會觸發。
  • on_value_deleted() 只有在 Realtime Database 中刪除資料時才會觸發。

匯入必要模組

在函式來源中,您必須匯入要使用的 SDK 模組。在這個範例中,您必須匯入 HTTP 和 Realtime Database 模組,以及用於寫入 Realtime DatabaseFirebase Admin SDK 模組。

Node.jsPython
// The Cloud Functions for Firebase SDK to setup triggers and logging.
const {onRequest} = require("firebase-functions/v2/https");
const {onValueCreated} = require("firebase-functions/v2/database");
const {logger} = require("firebase-functions");

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
# The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
from firebase_functions import db_fn, https_fn

# The Firebase Admin SDK to access the Firebase Realtime Database.
from firebase_admin import initialize_app, db

app = initialize_app()

指定執行個體和路徑

如要控管函式的觸發時間和位置,請使用路徑 (和選用的 Realtime Database 執行個體) 設定函式。如未指定執行個體,函式會監聽函式區域中的所有 Realtime Database 執行個體。您也可以指定 Realtime Database 執行個體模式,部署至同一地區中選取的執行個體子集。

例如:

Node.jsPython
// All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
// There must be at least one Realtime Database present in us-central1.
const onWrittenFunctionDefault = onValueWritten("/user/{uid}", (event) => {
  // …
});

// Instance named "my-app-db-2", at path "/user/{uid}".
// The "my-app-db-2" instance must exist in this region.
const OnWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid}",
    instance: "my-app-db-2"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);

// Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
// There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
const onWrittenFunctionInstance = onValueWritten(
  {
    ref: "/user/{uid=*@gmail.com}",
    instance: "my-app-db-*"
    // This example assumes us-central1, but to set location:
    // region: "europe-west1"
  },
  (event) => {
    // …
  }
);
# All Realtime Database instances in default function region us-central1 at path "/user/{uid}"
# There must be at least one Realtime Database present in us-central1.
@db_fn.on_value_written(r"/user/{uid}")
def onwrittenfunctiondefault(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance named "my-app-db-2", at path "/user/{uid}".
# The "my-app-db-2" instance must exist in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid}",
    instance="my-app-db-2",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

# Instance with "my-app-db-" prefix, at path "/user/{uid}", where uid ends with @gmail.com.
# There must be at least one Realtime Database with "my-app-db-*" prefix in this region.
@db_fn.on_value_written(
    reference=r"/user/{uid=*@gmail.com}",
    instance="my-app-db-*",
    # This example assumes us-central1, but to set location:
    # region="europe-west1",
)
def on_written_function_instance(event: db_fn.Event[db_fn.Change]):
    # ...
    pass

這些參數會引導函式處理 Realtime Database 執行個體內特定路徑的寫入作業。

路徑規格會比對所有觸及路徑的寫入作業,包括發生在路徑下方任何位置的寫入作業。如果將函式的路徑設為 /foo/bar,系統會比對下列兩個位置的事件:

 /foo/bar
 /foo/bar/baz/really/deep/path

無論是哪種情況,Firebase 都會將事件解讀為發生在 /foo/bar,且事件資料會包含 /foo/bar 的舊資料和新資料。如果事件資料可能很大,請考慮在更深層的路徑中使用多個函式,而不是在資料庫根目錄附近使用單一函式。為獲得最佳效能,請盡可能只要求最深層級的資料。

萬用字元和擷取

你可以使用 {key}{key=*}{key=prefix*}{key=*suffix} 擷取畫面。*prefix**suffix,用於單一段落的萬用字元。 注意:** 代表多段萬用字元,Realtime Database 不支援這類字元。 請參閱「瞭解路徑模式」。

路徑萬用字元。您可以將路徑元件指定為萬用字元:

  • 使用星號 *。舉例來說,foo/* 會比對 foo/ 下方節點階層的任何子項。
  • 使用含有星號 (*) 的區隔。舉例來說,foo/app*-us 會比對 foo/ 下方任何子區隔,前置字串為 app,後置字串為 -us

含有萬用字元的路徑可以比對多個事件,例如單一寫入作業。插入

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

會比對路徑 "/foo/*" 兩次:一次使用 "hello": "world",另一次使用 "firebase": "functions"

路徑擷取。您可以將路徑比對結果擷取到具名變數中,以便在函式程式碼中使用 (例如 /user/{uid}/user/{uid=*-us})。

擷取變數的值會顯示在函式的 database.DatabaseEvent.params 物件中。

執行個體萬用字元。您也可以使用萬用字元指定執行個體元件。執行個體萬用字元可以有前置字元、後置字元或兩者皆有 (例如 my-app-*-prod)。

萬用字元和擷取參考資料

使用 Cloud Functions (第 2 代) 和 Realtime Database 時,指定 refinstance 時可使用模式。每個觸發介面都會提供下列函式範圍選項:

指定 ref 指定 instance 行為
單曲 (/foo/bar) 未指定 將處理常式範圍設為函式區域中的所有執行個體。
單曲 (/foo/bar) 單曲 (‘my-new-db') 將範圍處理常式設為函式區域中的特定執行個體。
單曲 (/foo/bar) 圖案 (‘inst-prefix*') 將範圍處理常式套用至函式區域中符合模式的所有執行個體。
圖案 (/foo/{bar}) 未指定 將處理常式範圍設為函式區域中的所有執行個體。
圖案 (/foo/{bar}) 單曲 (‘my-new-db') 將範圍處理常式設為函式區域中的特定執行個體。
圖案 (/foo/{bar}) 圖案 (‘inst-prefix*') 將範圍處理常式套用至函式區域中符合模式的所有執行個體。

處理事件資料

Realtime Database 事件觸發時,會將 Event 物件傳遞至處理常式函式。這個物件具有 data 屬性,其中包含建立或刪除事件的資料快照。

在本例中,函式會擷取參照路徑的資料、將該位置的字串轉換為大寫,然後將修改後的字串寫入資料庫:

Node.jsPython
// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
// for all databases in 'us-central1'
exports.makeuppercase = onValueCreated(
    "/messages/{pushId}/original",
    (event) => {
    // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      logger.log("Uppercasing", event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing
      // asynchronous tasks inside a function, such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the
      // Realtime Database returns a Promise.
      return event.data.ref.parent.child("uppercase").set(uppercase);
    },
);
@db_fn.on_value_created(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[Any]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Grab the value that was written to the Realtime Database.
    original = event.data
    if not isinstance(original, str):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)

讀取先前的值

如果是 writeupdate 事件,data 屬性是 Change 物件,其中包含兩個快照,分別代表觸發事件前後的資料狀態。Change 物件具有 before 屬性,可供您檢查事件儲存至 Realtime Database 的內容,以及代表事件資料狀態的 after 屬性。

舉例來說,before 屬性可用來確保函式只會在首次建立時將文字轉換為大寫:

Node.jsPython
  exports makeUppercase = onValueWritten("/messages/{pushId}/original", (event) => {
        // Only edit data when it is first created.
        if (event.data.before.exists()) {
          return null;
        }
        // Exit when the data is deleted.
        if (!event.data.after.exists()) {
          return null;
        }
        // Grab the current value of what was written to the Realtime Database.
        const original = event.data.after.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        // You must return a Promise when performing asynchronous tasks inside a Functions such as
        // writing to the Firebase Realtime Database.
        // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
        return event.data.after.ref.parent.child('uppercase').set(uppercase);
      });
@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase2(event: db_fn.Event[db_fn.Change]) -> None:
    """Listens for new messages added to /messages/{pushId}/original and
    creates an uppercase version of the message to /messages/{pushId}/uppercase
    """

    # Only edit data when it is first created.
    if event.data.before is not None:
        return

    # Exit when the data is deleted.
    if event.data.after is None:
        return

    # Grab the value that was written to the Realtime Database.
    original = event.data.after
    if not hasattr(original, "upper"):
        print(f"Not a string: {event.reference}")
        return

    # Use the Admin SDK to set an "uppercase" sibling.
    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent = db.reference(event.reference).parent
    if parent is None:
        print("Message can't be root node.")
        return
    parent.child("uppercase").set(upper)