Điều kiện kích hoạt Cơ sở dữ liệu theo thời gian thực (thế hệ thứ nhất)

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 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 Firebase Realtime Database thay đổi thông qua DataSnapshot hoặc thông qua SDK quản trị.

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

  1. Chờ các thay đổi đối với một vị trí 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 đó (xem phần Tôi có thể làm gì với Cloud Functions? để biết các ví dụ về trường hợp sử dụng).
  3. Nhận một đối tượng dữ liệu chứa ảnh chụp nhanh dữ liệu được lưu trữ trong tài liệu đã chỉ định.

Kích hoạt hàm Realtime Database

Tạo hàm mới cho Realtime Database sự kiện bằng functions.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.

Đặt trình xử lý sự kiện

Các hàm cho phép bạn xử lý Realtime Database sự kiện ở 2 mức độ cụ thể; bạn có thể theo dõi cụ thể chỉ các sự kiện 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 đường dẫn. Cloud Functions hỗ trợ các trình xử lý sự kiện này cho Realtime Database:

  • onWrite(), kích hoạt khi dữ liệu được tạo, cập nhật hoặc xoá trong Realtime Database.
  • onCreate(), kích hoạt khi dữ liệu mới được tạo trong Realtime Database.
  • onUpdate(), kích hoạt khi dữ liệu được cập nhật trong Realtime Database .
  • onDelete(), kích hoạt khi dữ liệu bị xoá khỏi Realtime Database .

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 gọi ref(path) để chỉ định một đường dẫn và tuỳ ý chỉ định một thực thể Realtime Database bằng instance('INSTANCE_NAME'). Nếu bạn không chỉ định một thực thể, hàm sẽ triển khai đến thực thể Realtime Database mặc định cho dự án Firebase. Ví dụ:

  • Thực thể Realtime Database mặc định: functions.database.ref('/foo/bar')
  • Thực thể có tên là "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Các phương thức này hướng hàm của bạn xử lý các thao tác ghi ở một đường dẫn nhất định trong thực thể Realtime Database. Thông số kỹ thuật về đườ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ể.

Bạn có thể chỉ định một thành phần đường dẫn làm ký tự đại diện bằng cách đặt thành phần đó trong dấu ngoặc nhọn ; ref('foo/{bar}') khớp với mọi phần tử con của /foo. Giá trị của các thành phần đường dẫn ký tự đại diện này có trong đối tượng EventContext.paramscủa hàm. Trong ví dụ này, giá trị có dạng context.params.bar.

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

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

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

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

Khi xử lý một sự kiện Realtime Database, đối tượng dữ liệu được trả về là DataSnapshot. Đối với các sự kiện onWrite hoặc onUpdate, tham số đầu tiên là đối tượng Change chứa 2 ả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 với các sự kiện onCreateonDelete, đối tượng dữ liệu được trả về là ảnh chụp nhanh dữ liệu được tạo hoặc xoá.

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

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

Truy cập vào thông tin xác thực người dùng

Từ EventContext.authEventContext.authType, bạn có thể truy cập vào thông tin người dùng, bao gồm cả quyền, cho người dùng đã kích hoạt một hàm. Điều này có thể hữu ích để thực thi các quy tắc bảo mật, cho phép hàm hoàn tất các thao tác khác nhau dựa trên cấp quyền của người dùng:

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

Ngoài ra, bạn có thể tận dụng thông tin xác thực người dùng để "mạo danh" người dùng và thực hiện các thao tác ghi thay cho người dùng. Hãy nhớ xoá thực thể ứng dụng như minh hoạ bên dưới để tránh các vấn đề về tính đồng thời:

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

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

Đối tượng Change có một before thuộc tính cho phép bạn kiểm tra những gì đã được lưu vào Realtime Database trước sự kiện. Thuộc tính before trả về một DataSnapshot trong đó tất cả các phương thức (ví dụ: val()exists()) đều tham chiếu đến giá trị trước đó. Bạn có thể đọc lại giá trị mới bằng cách sử dụng DataSnapshot ban đầu hoặc đọc after thuộc tính. Thuộc tính này trên bất kỳ Change nào là một DataSnapshot khác đạ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 văn bản thành chữ hoa khi văn bản đó được tạo lần đầu tiên:

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