透過 Cloud Functions 擴充即時資料庫


有了 Cloud Functions,您就能在 Firebase Realtime Database 中處理事件,而無需更新用戶端程式碼。Cloud Functions 可讓您以完整的管理權限執行 Realtime Database 作業,並確保 Realtime Database 的每項變更都會個別處理。您可以透過 DataSnapshotAdmin SDK 變更 Firebase Realtime Database

在典型的生命週期中,Firebase Realtime Database 函式會執行以下操作:

  1. 等待特定 Realtime Database 位置的變更。
  2. 在事件發生時觸發,並執行相關工作 (請參閱「我可以使用 Cloud Functions 做什麼?請參閱使用案例示例)。
  3. 接收含有指定文件中儲存資料快照的資料物件。

觸發 Realtime Database 函式

使用 functions.databaseRealtime Database 事件建立新函式。如要控制函式觸發時機,請指定其中一個事件處理常式,並指定要監聽事件的 Realtime Database 路徑。

設定事件處理常式

函式可讓您以兩種精確度層級處理 Realtime Database 事件;您可以專門監聽建立、更新或刪除事件,也可以監聽路徑的任何變更。Cloud Functions 支援 Realtime Database 的下列事件處理常式:

  • onWrite():在 Realtime Database 中建立、更新或刪除資料時觸發。
  • onCreate():在 Realtime Database 中建立新資料時觸發。
  • onUpdate():在 Realtime Database 中更新資料時觸發。
  • onDelete():當資料從 Realtime Database 中刪除時觸發。

指定執行個體和路徑

如要控制函式應觸發的時機和位置,請呼叫 ref(path) 指定路徑,並視需要使用 instance('INSTANCE_NAME') 指定 Realtime Database 例項。如果未指定執行個體,函式會部署至 Firebase 專案的預設 Realtime Database 執行個體。例如:

  • 預設 Realtime Database 例項:functions.database.ref('/foo/bar')
  • 名為「my-app-db-2」的執行個體:functions.database.instance('my-app-db-2').ref('/foo/bar')

這些方法會指示函式在 Realtime Database 例項的特定路徑中處理寫入作業。路徑規格會比對觸及路徑的「所有」寫入作業,包括在路徑下方任何位置發生的寫入作業。如果您將函式的路徑設為 /foo/bar,則會比對下列兩個位置的事件:

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

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

您可以將路徑元件指定為萬用字元,方法是將其前後加上大括號;ref('foo/{bar}') 會比對 /foo 的任何子項。這些萬用字路徑元件的值可在函式的 EventContext.params 物件中使用。在這個範例中,值為 context.params.bar

含萬用字元的路徑可比對單一寫入作業中的多個事件。插入

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

比對路徑 "/foo/{bar}" 兩次:一次使用 "hello": "world",另一次使用 "firebase": "functions"

處理事件資料

處理 Realtime Database 事件時,傳回的資料物件為 DataSnapshot。對於 onWriteonUpdate 事件,第一個參數是 Change 物件,其中包含兩個快照,分別代表觸發事件前後的資料狀態。對於 onCreateonDelete 事件,系統會傳回建立或刪除的資料快照。

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

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      functions.logger.log('Uppercasing', context.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 snapshot.ref.parent.child('uppercase').set(uppercase);
    });

存取使用者驗證資訊

您可以透過 EventContext.authEventContext.authType,存取觸發函式的使用者資訊 (包括權限)。這對於強制執行安全性規則來說非常實用,可讓函式根據使用者的權限等級完成不同的作業:

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

此外,您也可以利用使用者驗證資訊「冒用」使用者,並代表使用者執行寫入作業。請務必刪除應用程式執行個體,如以下所示,以免發生並行問題:

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

讀取先前的值

Change 物件具有 before 屬性,可讓您檢查事件發生前儲存至 Realtime Database 的內容。before 屬性會傳回 DataSnapshot,其中所有方法 (例如 val()exists()) 都會參照先前的值。您可以使用原始 DataSnapshot 或讀取 after 屬性,再次讀取新值。任何 Change 上的這項屬性都是另一個 DataSnapshot,代表事件發生「後」資料的狀態。

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

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.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 change.after.ref.parent.child('uppercase').set(uppercase);
    });