Điều kiện kích hoạt Cơ sở dữ liệu theo thời gian thực

Với Cloud Functions, bạn có thể xử lý các sự kiện trong Firebase Realtime Database mà không cần cập nhật mã máy khách. Cloud Functions cho phép bạn chạy các thao tác trên Realtime Database với đầy đủ quyền quản trị và đảm bảo rằng mỗi thay đổi đối với Realtime Database đều được xử lý riêng lẻ. Bạn có thể thực hiện các thay đổi Firebase Realtime Database thông qua ảnh chụp nhanh dữ liệu hoặc thông qua SDK quản trị.

Trong vòng đời thông thường, hàm Firebase Realtime Database sẽ thực hiện như sau:

  1. Chờ các thay đổi đối với một đường dẫn Realtime Database cụ thể.
  2. Kích hoạt khi một sự kiện xảy ra và thực hiện các tác vụ của sự kiện đó.
  3. Nhận một đối tượng dữ liệu chứa ảnh chụp nhanh dữ liệu được lưu trữ tại đường dẫn đó.

Bạn có thể kích hoạt một hàm để phản hồi việc ghi, tạo, cập nhật hoặc xoá các nút cơ sở dữ liệu trong Firebase Realtime Database. Để kiểm soát thời điểm kích hoạt hàm, hãy chỉ định một trong các trình xử lý sự kiện và chỉ định đường dẫn Realtime Database nơi hàm sẽ theo dõi các sự kiện.

Thiết lập vị trí hàm

Khoảng cách giữa vị trí của một thực thể Realtime Database và vị trí của hàm có thể tạo ra độ trễ mạng đáng kể. Ngoài ra, sự không khớp giữa các khu vực có thể dẫn đến lỗi triển khai. Để tránh những trường hợp này, hãy chỉ định vị trí hàm sao cho khớp với vị trí thực thể cơ sở dữ liệu.

Xử lý các sự kiện Realtime Database

Các hàm cho phép bạn xử lý các sự kiện Realtime Database ở hai mức độ cụ thể; bạn có thể theo dõi cụ thể chỉ các sự kiện ghi, tạo, cập nhật, hoặc xoá, hoặc bạn có thể theo dõi mọi thay đổi thuộc bất kỳ loại nào đối với một tham chiếu.

Bạn có thể sử dụng các trình xử lý này để phản hồi các sự kiện Realtime Database:

Node.js

  • onValueWritten() Kích hoạt khi dữ liệu được tạo, cập nhật hoặc xoá trong Realtime Database.
  • onValueCreated() Chỉ kích hoạt khi dữ liệu được tạo trong Realtime Database.
  • onValueUpdated() Chỉ kích hoạt khi dữ liệu được cập nhật trong Realtime Database.
  • onValueDeleted() Chỉ kích hoạt khi dữ liệu được xoá trong Realtime Database.

Python

  • on_value_written() Kích hoạt khi dữ liệu được tạo, cập nhật hoặc xoá trong Realtime Database.
  • on_value_created() Chỉ kích hoạt khi dữ liệu được tạo trong Realtime Database.
  • on_value_updated() Chỉ kích hoạt khi dữ liệu được cập nhật trong Realtime Database.
  • on_value_deleted() Chỉ kích hoạt khi dữ liệu được xoá trong Realtime Database.

Nhập các mô-đun bắt buộc

Trong nguồn hàm, bạn phải nhập các mô-đun SDK mà bạn muốn sử dụng. Đối với mẫu này, bạn cần nhập các mô-đun HTTP và Realtime Database cùng với mô-đun Firebase Admin SDK để ghi vào Realtime Database.

Node.js

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

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();

Python

# 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()

Chỉ định thực thể và đường dẫn

Để kiểm soát thời điểm và vị trí kích hoạt hàm, hãy định cấu hình hàm bằng một đường dẫn và tuỳ ý một thực thể Realtime Database. Nếu bạn không chỉ định một thực thể, hàm sẽ theo dõi tất cả các thực thể Realtime Database trong khu vực hàm. Bạn cũng có thể chỉ định một mẫu thực thể Realtime Database để triển khai cho một tập hợp con chọn lọc các thực thể trong cùng một khu vực.

Ví dụ:

Node.js

// 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) => {
    // …
  }
);

Python

# 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

Các tham số này hướng hàm của bạn xử lý các thao tác ghi tại một đường dẫn nhất định trong thực thể Realtime Database.

Thông số đường dẫn khớp với tất cả các thao tác ghi chạm vào một đường dẫn, bao gồm cả các thao tác ghi xảy ra ở bất kỳ vị trí nào bên dưới đường dẫn đó. Nếu bạn đặt đường dẫn cho hàm là /foo/bar, thì đường dẫn này sẽ khớp với các sự kiện ở cả hai vị trí sau:

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

Trong cả hai trường hợp, Firebase diễn giải rằng sự kiện xảy ra tại /foo/bar và dữ liệu sự kiện bao gồm dữ liệu cũ và mới tại /foo/bar. Nếu dữ liệu sự kiện có thể lớn, hãy cân nhắc sử dụng nhiều hàm ở các đường dẫn sâu hơn thay vì một hàm duy nhất gần gốc của cơ sở dữ liệu. Để có hiệu suất tốt nhất, chỉ yêu cầu dữ liệu ở cấp độ sâu nhất có thể.

Sử dụng ký tự đại diện và thu thập

Bạn có thể sử dụng {key}, {key=*}, {key=prefix*}, {key=*suffix} để thu thập. *, prefix*, *suffix để sử dụng ký tự đại diện một phân đoạn. Lưu ý: ** đại diện cho việc sử dụng ký tự đại diện nhiều phân đoạn mà Realtime Database không hỗ trợ. Xem bài viết Tìm hiểu các mẫu đường dẫn.

Sử dụng ký tự đại diện cho đường dẫn. Bạn có thể chỉ định một thành phần đường dẫn làm ký tự đại diện:

  • Sử dụng dấu hoa thị, *. Ví dụ: foo/* khớp với mọi thành phần con ở một cấp độ của hệ phân cấp nút bên dưới foo/.
  • Sử dụng một phân đoạn chứa chính xác dấu hoa thị, *. Ví dụ: foo/app*-us khớp với mọi phân đoạn con bên dưới foo/ có tiền tố app và hậu tố -us.

Các đường dẫn có ký tự đại diện có thể khớp với nhiều sự kiện, chẳng hạn như một thao tác ghi duy nhất. Thao tác chèn

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

khớp với đường dẫn "/foo/*" hai lần: một lần với "hello": "world" và một lần nữa với "firebase": "functions".

Thu thập đường dẫn. Bạn có thể thu thập các kết quả khớp đường dẫn vào các biến được đặt tên để sử dụng trong mã hàm (ví dụ: /user/{uid}, /user/{uid=*-us}).

Giá trị của các biến thu thập có trong đối tượng database.DatabaseEvent.params của hàm.

Sử dụng ký tự đại diện cho thực thể. Bạn cũng có thể chỉ định một thành phần thực thể bằng cách sử dụng ký tự đại diện. Ký tự đại diện thực thể có thể có tiền tố, hậu tố hoặc cả hai (ví dụ: my-app-*-prod).

Tham chiếu ký tự đại diện và thu thập

Với Cloud Functions (thế hệ thứ 2) và Realtime Database, bạn có thể sử dụng một mẫu khi chỉ định refinstance. Mỗi giao diện kích hoạt sẽ có các lựa chọn sau để xác định phạm vi của một hàm:

Chỉ định ref Chỉ định instance Hành vi
Đơn lẻ (/foo/bar) Không chỉ định Xác định phạm vi trình xử lý cho tất cả các thực thể trong khu vực hàm.
Đơn lẻ (/foo/bar) Đơn lẻ (‘my-new-db') Xác định phạm vi trình xử lý cho thực thể cụ thể trong khu vực hàm.
Đơn lẻ (/foo/bar) Mẫu (‘inst-prefix*') Xác định phạm vi trình xử lý cho tất cả các thực thể khớp với mẫu trong khu vực hàm.
Mẫu (/foo/{bar}) Không chỉ định Xác định phạm vi trình xử lý cho tất cả các thực thể trong khu vực hàm.
Mẫu (/foo/{bar}) Đơn lẻ (‘my-new-db') Xác định phạm vi trình xử lý cho thực thể cụ thể trong khu vực hàm.
Mẫu (/foo/{bar}) Mẫu (‘inst-prefix*') Xác định phạm vi trình xử lý cho tất cả các thực thể khớp với mẫu trong khu vực hàm.

Xử lý dữ liệu sự kiện

Khi một sự kiện Realtime Database kích hoạt, sự kiện đó sẽ truyền một đối tượng Event đến hàm trình xử lý của bạn. Đối tượng này có một thuộc tính data, thuộc tính này, đối với các sự kiện tạo và xoá, chứa ảnh chụp nhanh dữ liệu được tạo hoặc xoá.

Trong ví dụ này, hàm sẽ truy xuất dữ liệu cho đường dẫn được tham chiếu, chuyển đổi chuỗi tại vị trí đó thành chữ hoa và ghi chuỗi đã sửa đổi đó vào cơ sở dữ liệu:

Node.js

// 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);
    },
);

Python

@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)

Đọc giá trị trước đó

Đối với các sự kiện write hoặc update, thuộc tính data là một đối tượng Change chứa hai ảnh chụp nhanh đại diện cho trạng thái dữ liệu trước và sau sự kiện kích hoạt. Đối tượng Change có một thuộc tính before cho phép bạn kiểm tra những gì đã được lưu vào Realtime Database trước sự kiện và một thuộc tính after đại diện cho trạng thái của dữ liệu sau khi sự kiện xảy ra.

Ví dụ: bạn có thể sử dụng thuộc tính before để đảm bảo hàm chỉ chuyển đổi văn bản thành chữ hoa khi văn bản đó được tạo lần đầu tiên:

Node.js

  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);
      });

Python

@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)

Truy cập vào bối cảnh xác thực

Đối với các hàm được kích hoạt bởi các sự kiện RTDB eventarc, bối cảnh xác thực được đưa vào tải trọng sự kiện:

  • authtype: Loại thực thể đã kích hoạt sự kiện. Các giá trị có thể là:
    • app_user: Người dùng cuối của ứng dụng dành cho nhà phát triển.
    • admin: Tài khoản dịch vụ.
    • unauthenticated: Người dùng chưa xác thực.
    • unknown: Giá trị mặc định khi không có thông tin xác thực.
  • authid: Giá trị nhận dạng duy nhất của thực thể.
    • Nếu authtypeapp_user, thì đây là UID của người dùng.
    • Nếu authtypeadmin, thì đây là tài khoản dịch vụ hoặc email người dùng IAM.

Mã này chỉ chuyển đổi văn bản của thông báo thành chữ hoa nếu người dùng kích hoạt hàm không phải là quản trị viên. Ngoài ra, mã này cũng kiểm tra xem người dùng kích hoạt thông báo có phải là người gửi thực tế của thông báo hay không.

Node.js

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

admin.initializeApp();

exports.dbtrigger = onValueWritten("/messages/{pushId}/original", async (event) => {
  // 1. Check whether authtype is admin. If it is, skip this operation.
  if (event.authType === "admin") {
    logger.log("Modification by admin detected. Skipping uppercase conversion.");
    return null;
  }

  // 2. Retrieve the userID of the sender (assumed sibling node 'senderId')
  const snapshot = await event.data.after.ref.parent.child("senderId").get();
  const senderId = snapshot.val();

  // 3. Check if userID of sender of message = event.authid
  if (senderId !== event.authId) {
    logger.error(`Unauthorized write: senderId (${senderId}) does not match authId (${event.authId})`);
    return null;
  }

  // Grab the value that was written to the Realtime Database.
  const original = event.data.after.val();
  logger.log("Uppercasing", event.params.pushId, original);
  const uppercase = original.toUpperCase();

  // Return the promise to set the "uppercase" sibling node.
  return event.data.after.ref.parent.child("uppercase").set(uppercase);
});

Python

from firebase_functions import db_fn
from firebase_admin import initialize_app, db

initialize_app()

@db_fn.on_value_written(reference="/messages/{pushId}/original")
def makeuppercase(event: db_fn.Event[db_fn.Change]) -> None:
    # 1. Check whether authtype is admin. If it is, skip this operation.
    if event.auth_type == "admin":
        print("Admin user detected. Skipping.")
        return

    # 2. Retrieve the userID of the sender (assumed sibling node: 'senderId')
    parent_ref = db.reference(event.reference).parent
    sender_id = parent_ref.child("senderId").get()

    # 3. Check if userID of sender = event.auth_id
    if sender_id != event.auth_id:
        print(f"Unauthorized: sender_id {sender_id} != auth_id {event.auth_id}")
        return

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

    # Grab the value and uppercase it
    original = event.data.after
    if not isinstance(original, str):
        return

    print(f"Uppercasing {event.params['pushId']}: {original}")
    upper = original.upper()
    parent_ref.child("uppercase").set(upper)