Lớp học lập trình web Cloud Firestore

1. Tổng quan

Bàn thắng

Trong lớp học lập trình này, bạn sẽ tạo một ứng dụng web đề xuất nhà hàng dựa trên Cloud Firestore.

img5.png

Kiến thức bạn sẽ học được

  • Đọc và ghi dữ liệu vào Cloud Firestore từ một ứng dụng web
  • Nghe các thay đổi về dữ liệu trong Cloud Firestore theo thời gian thực
  • Sử dụng tính năng Xác thực Firebase và các quy tắc bảo mật để bảo mật dữ liệu Cloud Firestore
  • Viết các truy vấn phức tạp cho Cloud Firestore

Bạn cần

Trước khi bắt đầu lớp học lập trình này, hãy đảm bảo rằng bạn đã cài đặt:

  • npm thường đi kèm với Node.js – Bạn nên dùng Node 16 trở lên
  • IDE/trình chỉnh sửa văn bản mà bạn chọn, chẳng hạn như WebStorm, VS Code hoặc Sublime

2. Tạo và thiết lập dự án Firebase

Tạo một dự án Firebase

  1. Đăng nhập vào bảng điều khiển của Firebase bằng Tài khoản Google của bạn.
  2. Nhấp vào nút này để tạo một dự án mới, rồi nhập tên dự án (ví dụ: FriendlyEats).
  3. Nhấp vào Tiếp tục.
  4. Nếu được nhắc, hãy xem xét và chấp nhận các điều khoản của Firebase, rồi nhấp vào Tiếp tục.
  5. (Không bắt buộc) Bật tính năng hỗ trợ của AI trong bảng điều khiển của Firebase (còn gọi là "Gemini trong Firebase").
  6. Đối với lớp học lập trình này, bạn không cần Google Analytics, vì vậy hãy tắt lựa chọn Google Analytics.
  7. Nhấp vào Tạo dự án, đợi dự án được cấp phép rồi nhấp vào Tiếp tục.

Thiết lập các sản phẩm của Firebase

Ứng dụng mà chúng ta sẽ tạo sử dụng một số dịch vụ của Firebase có trên web:

  • Xác thực Firebase để dễ dàng xác định người dùng
  • Cloud Firestore để lưu dữ liệu có cấu trúc trên Đám mây và nhận thông báo tức thì khi dữ liệu được cập nhật
  • Lưu trữ Firebase để lưu trữ và phân phát tài sản tĩnh

Đối với lớp học lập trình cụ thể này, chúng tôi đã định cấu hình Firebase Hosting. Tuy nhiên, đối với Firebase Auth và Cloud Firestore, chúng tôi sẽ hướng dẫn bạn cách định cấu hình và bật các dịch vụ bằng bảng điều khiển của Firebase.

Bật tính năng Xác thực ẩn danh

Mặc dù xác thực không phải là trọng tâm của lớp học lập trình này, nhưng điều quan trọng là phải có một số hình thức xác thực trong ứng dụng của chúng ta. Chúng ta sẽ sử dụng Đăng nhập ẩn danh – tức là người dùng sẽ được đăng nhập thầm lặng mà không được nhắc.

Bạn cần bật chế độ Đăng nhập ẩn danh.

  1. Trong bảng điều khiển của Firebase, hãy tìm mục Tạo trong trình đơn điều hướng bên trái.
  2. Nhấp vào Xác thực, sau đó nhấp vào thẻ Phương thức đăng nhập (hoặc nhấp vào đây để chuyển thẳng đến thẻ này).
  3. Bật Nhà cung cấp dịch vụ đăng nhập Ẩn danh, sau đó nhấp vào Lưu.

img7.png

Điều này sẽ cho phép ứng dụng đăng nhập người dùng một cách âm thầm khi họ truy cập vào ứng dụng web. Bạn có thể đọc tài liệu về Xác thực ẩn danh để tìm hiểu thêm.

Bật Cloud Firestore

Ứng dụng này sử dụng Cloud Firestore để lưu và nhận thông tin cũng như điểm xếp hạng của nhà hàng.

Bạn cần bật Cloud Firestore. Trong mục Tạo của bảng điều khiển Firebase, hãy nhấp vào Cơ sở dữ liệu Firestore. Nhấp vào Tạo cơ sở dữ liệu trong ngăn Cloud Firestore.

Quyền truy cập vào dữ liệu trong Cloud Firestore được kiểm soát bằng Quy tắc bảo mật. Chúng ta sẽ nói thêm về các quy tắc ở phần sau trong lớp học lập trình này, nhưng trước tiên, chúng ta cần thiết lập một số quy tắc cơ bản cho dữ liệu để bắt đầu. Trong thẻ Quy tắc của bảng điều khiển Firebase, hãy thêm các quy tắc sau rồi nhấp vào Xuất bản.

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data)
      && (key in request.resource.data)
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && unchanged("name");

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

Chúng ta sẽ thảo luận về các quy tắc này và cách chúng hoạt động ở phần sau của lớp học lập trình.

3. Nhận mã mẫu

Sao chép kho lưu trữ GitHub từ dòng lệnh:

git clone https://github.com/firebase/friendlyeats-web

Mã mẫu phải được sao chép vào thư mục 📁friendlyeats-web. Từ nay trở đi, hãy nhớ chạy tất cả các lệnh từ thư mục này:

cd friendlyeats-web/vanilla-js

Nhập ứng dụng khởi đầu

Mở hoặc nhập thư mục 📁friendlyeats-web bằng IDE (WebStorm, Atom, Sublime, Visual Studio Code...). Thư mục này chứa mã khởi đầu cho lớp học lập trình, bao gồm một ứng dụng đề xuất nhà hàng chưa hoạt động. Chúng ta sẽ làm cho ứng dụng này hoạt động trong suốt lớp học lập trình này, vì vậy, bạn sẽ cần chỉnh sửa mã trong thư mục đó ngay.

4. Cài đặt Giao diện dòng lệnh của Firebase

Giao diện dòng lệnh (CLI) của Firebase cho phép bạn phân phát ứng dụng web cục bộ và triển khai ứng dụng web của bạn cho Firebase Hosting.

  1. Cài đặt CLI bằng cách chạy lệnh npm sau:
npm -g install firebase-tools
  1. Xác minh rằng bạn đã cài đặt CLI đúng cách bằng cách chạy lệnh sau:
firebase --version

Đảm bảo phiên bản của Firebase CLI là v7.4.0 trở lên.

  1. Uỷ quyền cho Firebase CLI bằng cách chạy lệnh sau:
firebase login

Chúng tôi đã thiết lập mẫu ứng dụng web để kéo cấu hình của ứng dụng cho dịch vụ Lưu trữ Firebase từ thư mục và tệp cục bộ của ứng dụng. Nhưng để làm được việc này, chúng ta cần liên kết ứng dụng của bạn với dự án Firebase.

  1. Đảm bảo rằng dòng lệnh đang truy cập vào thư mục cục bộ của ứng dụng.
  2. Liên kết ứng dụng với dự án Firebase bằng cách chạy lệnh sau:
firebase use --add
  1. Khi được nhắc, hãy chọn Mã dự án, sau đó đặt một biệt hiệu cho dự án Firebase của bạn.

Bí danh sẽ hữu ích nếu bạn có nhiều môi trường (phát hành công khai, dàn dựng, v.v.). Tuy nhiên, đối với lớp học lập trình này, chúng ta chỉ cần sử dụng biệt hiệu của default.

  1. Làm theo các hướng dẫn còn lại trong dòng lệnh.

5. Chạy máy chủ cục bộ

Chúng ta đã sẵn sàng bắt đầu thực hiện công việc trên ứng dụng của mình! Hãy chạy ứng dụng của chúng ta trên thiết bị!

  1. Chạy lệnh sau của Giao diện dòng lệnh (CLI) của Firebase:
firebase emulators:start --only hosting
  1. Dòng lệnh của bạn sẽ hiển thị phản hồi sau:
hosting: Local server: http://localhost:5000

Chúng tôi đang sử dụng trình mô phỏng Lưu trữ Firebase để phân phát ứng dụng của mình trên thiết bị. Giờ đây, bạn có thể truy cập vào ứng dụng web qua http://localhost:5000.

  1. Mở ứng dụng tại http://localhost:5000.

Bạn sẽ thấy bản sao của FriendlyEats đã được kết nối với dự án Firebase.

Ứng dụng đã tự động kết nối với dự án Firebase của bạn và âm thầm đăng nhập cho bạn với tư cách là người dùng ẩn danh.

img2.png

6. Ghi dữ liệu vào Cloud Firestore

Trong phần này, chúng ta sẽ ghi một số dữ liệu vào Cloud Firestore để có thể điền sẵn dữ liệu vào giao diện người dùng của ứng dụng. Bạn có thể thực hiện việc này theo cách thủ công thông qua bảng điều khiển của Firebase, nhưng chúng ta sẽ thực hiện trong chính ứng dụng để minh hoạ một thao tác ghi cơ bản vào Cloud Firestore.

Mô hình dữ liệu

Dữ liệu Firestore được chia thành các bộ sưu tập, tài liệu, trường và bộ sưu tập con. Chúng ta sẽ lưu trữ mỗi nhà hàng dưới dạng một tài liệu trong một tập hợp cấp cao nhất có tên là restaurants.

img3.png

Sau đó, chúng ta sẽ lưu trữ từng bài đánh giá trong một tập hợp con có tên là ratings trong mỗi nhà hàng.

img4.png

Thêm nhà hàng vào Firestore

Đối tượng mô hình chính trong ứng dụng của chúng ta là một nhà hàng. Hãy viết một đoạn mã để thêm tài liệu về nhà hàng vào tập hợp restaurants.

  1. Trong số các tệp bạn đã tải xuống, hãy mở tệp scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.addRestaurant.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

Đoạn mã trên sẽ thêm một tài liệu mới vào tập hợp restaurants. Dữ liệu tài liệu đến từ một đối tượng JavaScript thuần tuý. Chúng ta thực hiện việc này bằng cách trước tiên lấy một tham chiếu đến bộ sưu tập Cloud Firestore restaurants rồi add dữ liệu.

Hãy thêm nhà hàng!

  1. Quay lại ứng dụng FriendlyEats trong trình duyệt rồi làm mới ứng dụng.
  2. Nhấp vào Thêm dữ liệu mô phỏng.

Ứng dụng sẽ tự động tạo một tập hợp ngẫu nhiên các đối tượng nhà hàng, sau đó gọi hàm addRestaurant của bạn. Tuy nhiên, bạn sẽ chưa thấy dữ liệu trong ứng dụng web thực tế của mình vì chúng ta vẫn cần triển khai việc truy xuất dữ liệu (phần tiếp theo của lớp học lập trình).

Tuy nhiên, nếu chuyển đến thẻ Cloud Firestore trong bảng điều khiển của Firebase, thì giờ đây, bạn sẽ thấy các tài liệu mới trong bộ sưu tập restaurants!

img6.png

Xin chúc mừng! Bạn vừa ghi dữ liệu vào Cloud Firestore từ một ứng dụng web!

Trong phần tiếp theo, bạn sẽ tìm hiểu cách truy xuất dữ liệu từ Cloud Firestore và hiển thị dữ liệu đó trong ứng dụng.

7. Hiển thị dữ liệu từ Cloud Firestore

Trong phần này, bạn sẽ tìm hiểu cách truy xuất dữ liệu từ Cloud Firestore và hiển thị dữ liệu đó trong ứng dụng. Hai bước chính là tạo truy vấn và thêm trình nghe ảnh chụp nhanh. Trình nghe này sẽ được thông báo về tất cả dữ liệu hiện có khớp với truy vấn và sẽ nhận được thông tin cập nhật theo thời gian thực.

Trước tiên, hãy tạo truy vấn sẽ phân phát danh sách nhà hàng mặc định, chưa được lọc.

  1. Quay lại tệp scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.getAllRestaurants.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

Trong đoạn mã trên, chúng ta tạo một truy vấn sẽ truy xuất tối đa 50 nhà hàng từ bộ sưu tập cấp cao nhất có tên là restaurants, được sắp xếp theo điểm xếp hạng trung bình (hiện tại tất cả đều là 0). Sau khi khai báo truy vấn này, chúng ta sẽ truyền truy vấn đó đến phương thức getDocumentsInQuery(). Phương thức này chịu trách nhiệm tải và hiển thị dữ liệu.

Chúng ta sẽ thực hiện việc này bằng cách thêm một trình nghe ảnh chụp nhanh.

  1. Quay lại tệp scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.getDocumentsInQuery.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

Trong đoạn mã trên, query.onSnapshot sẽ kích hoạt lệnh gọi lại của nó mỗi khi có thay đổi đối với kết quả của truy vấn.

  • Lần đầu tiên, lệnh gọi lại được kích hoạt với toàn bộ tập kết quả của truy vấn, tức là toàn bộ tập hợp restaurants từ Cloud Firestore. Sau đó, hàm này sẽ truyền tất cả các tài liệu riêng lẻ đến hàm renderer.display.
  • Khi một tài liệu bị xoá, change.type sẽ bằng removed. Trong trường hợp này, chúng ta sẽ gọi một hàm xoá nhà hàng khỏi giao diện người dùng.

Bây giờ, khi bạn đã triển khai cả hai phương thức, hãy làm mới ứng dụng và xác minh rằng những nhà hàng mà bạn thấy trước đó trong bảng điều khiển của Firebase hiện đã xuất hiện trong ứng dụng. Nếu bạn hoàn thành phần này, thì ứng dụng của bạn hiện đang đọc và ghi dữ liệu bằng Cloud Firestore!

Khi danh sách nhà hàng của bạn thay đổi, trình nghe này sẽ tự động cập nhật. Hãy thử truy cập vào bảng điều khiển Firebase và xoá một nhà hàng theo cách thủ công hoặc thay đổi tên của nhà hàng đó. Bạn sẽ thấy các thay đổi xuất hiện ngay lập tức trên trang web của mình!

img5.png

8. Dữ liệu Get()

Từ trước đến nay, chúng ta đã thấy cách sử dụng onSnapshot để truy xuất thông tin cập nhật theo thời gian thực; tuy nhiên, không phải lúc nào chúng ta cũng muốn làm như vậy. Đôi khi, bạn chỉ cần tìm nạp dữ liệu một lần.

Bạn sẽ muốn triển khai một phương thức được kích hoạt khi người dùng nhấp vào một nhà hàng cụ thể trong ứng dụng của bạn.

  1. Quay lại tệp của bạn scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.getRestaurant.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

Sau khi triển khai phương thức này, bạn sẽ có thể xem các trang cho từng nhà hàng. Bạn chỉ cần nhấp vào một nhà hàng trong danh sách và bạn sẽ thấy trang chi tiết của nhà hàng đó:

img1.png

Hiện tại, bạn không thể thêm điểm xếp hạng vì chúng ta vẫn cần triển khai việc thêm điểm xếp hạng sau này trong lớp học lập trình.

9. Sắp xếp và lọc dữ liệu

Hiện tại, ứng dụng của chúng tôi hiển thị danh sách các nhà hàng, nhưng người dùng không có cách nào để lọc dựa trên nhu cầu của họ. Trong phần này, bạn sẽ sử dụng tính năng truy vấn nâng cao của Cloud Firestore để bật tính năng lọc.

Sau đây là ví dụ về một truy vấn đơn giản để tìm nạp tất cả nhà hàng Dim Sum:

var filteredQuery = query.where('category', '==', 'Dim Sum')

Như tên gọi của nó, phương thức where() sẽ giúp truy vấn của chúng ta chỉ tải xuống những thành phần trong bộ sưu tập có các trường đáp ứng những hạn chế mà chúng ta đặt ra. Trong trường hợp này, ứng dụng sẽ chỉ tải những nhà hàng có categoryDim Sum.

Trong ứng dụng của chúng tôi, người dùng có thể kết hợp nhiều bộ lọc để tạo các cụm từ tìm kiếm cụ thể, chẳng hạn như "Pizza ở San Francisco" hoặc "Hải sản ở Los Angeles được sắp xếp theo Mức độ phổ biến".

Chúng ta sẽ tạo một phương thức xây dựng truy vấn để lọc các nhà hàng dựa trên nhiều tiêu chí do người dùng chọn.

  1. Quay lại tệp của bạn scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.getFilteredRestaurants.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

Đoạn mã trên thêm nhiều bộ lọc where và một mệnh đề orderBy để tạo một cụm từ tìm kiếm phức tạp dựa trên thông tin đầu vào của người dùng. Giờ đây, truy vấn của chúng ta sẽ chỉ trả về những nhà hàng đáp ứng yêu cầu của người dùng.

Làm mới ứng dụng FriendlyEats trong trình duyệt, sau đó xác minh rằng bạn có thể lọc theo giá, thành phố và danh mục. Trong quá trình kiểm thử, bạn sẽ thấy các lỗi trong Bảng điều khiển JavaScript của trình duyệt có dạng như sau:

The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...

Những lỗi này xảy ra do Cloud Firestore yêu cầu chỉ mục cho hầu hết các truy vấn kết hợp. Việc yêu cầu chỉ mục trên các truy vấn giúp Cloud Firestore duy trì tốc độ nhanh ở quy mô lớn.

Khi bạn mở đường liên kết trong thông báo lỗi, giao diện người dùng tạo chỉ mục sẽ tự động mở trong bảng điều khiển của Firebase với các thông số chính xác đã được điền sẵn. Trong phần tiếp theo, chúng ta sẽ viết và triển khai các chỉ mục cần thiết cho ứng dụng này.

10. Triển khai chỉ mục

Nếu không muốn khám phá mọi đường dẫn trong ứng dụng và theo dõi từng đường liên kết tạo chỉ mục, chúng ta có thể dễ dàng triển khai nhiều chỉ mục cùng một lúc bằng Firebase CLI.

  1. Trong thư mục cục bộ đã tải xuống của ứng dụng, bạn sẽ thấy một tệp firestore.indexes.json.

Tệp này mô tả tất cả các chỉ mục cần thiết cho mọi tổ hợp bộ lọc có thể có.

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. Triển khai các chỉ mục này bằng lệnh sau:
firebase deploy --only firestore:indexes

Sau vài phút, các chỉ mục của bạn sẽ hoạt động và thông báo lỗi sẽ biến mất.

11. Ghi dữ liệu trong một giao dịch

Trong phần này, chúng ta sẽ thêm chức năng cho phép người dùng gửi bài đánh giá cho nhà hàng. Cho đến nay, tất cả các thao tác ghi của chúng ta đều là nguyên tử và tương đối đơn giản. Nếu có bất kỳ thao tác nào trong số đó gặp lỗi, có thể chúng ta chỉ cần nhắc người dùng thử lại hoặc ứng dụng của chúng ta sẽ tự động thử lại thao tác ghi.

Ứng dụng của chúng tôi sẽ có nhiều người dùng muốn thêm điểm xếp hạng cho một nhà hàng, vì vậy, chúng ta cần điều phối nhiều lượt đọc và ghi. Trước tiên, bạn phải gửi bài đánh giá, sau đó cần cập nhật điểm xếp hạng countaverage rating của nhà hàng. Nếu một trong hai thao tác này không thành công nhưng thao tác còn lại thành công, chúng ta sẽ rơi vào trạng thái không nhất quán, trong đó dữ liệu ở một phần của cơ sở dữ liệu không khớp với dữ liệu ở phần khác.

Rất may, Cloud Firestore cung cấp chức năng giao dịch cho phép chúng ta thực hiện nhiều thao tác đọc và ghi trong một thao tác nguyên tử duy nhất, đảm bảo dữ liệu của chúng ta luôn nhất quán.

  1. Quay lại tệp của bạn scripts/FriendlyEats.Data.js.
  2. Tìm hàm FriendlyEats.prototype.addRating.
  3. Thay thế toàn bộ hàm bằng mã sau.

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

Trong khối ở trên, chúng ta kích hoạt một giao dịch để cập nhật các giá trị dạng số của avgRatingnumRatings trong tài liệu nhà hàng. Đồng thời, chúng ta thêm rating mới vào tập hợp con ratings.

12. Bảo mật dữ liệu của bạn

Khi bắt đầu lớp học lập trình này, chúng ta sẽ đặt các quy tắc bảo mật cho ứng dụng để hạn chế quyền truy cập vào ứng dụng.

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data)
      && (key in request.resource.data)
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && unchanged("name");

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

Các quy tắc này hạn chế quyền truy cập để đảm bảo rằng các ứng dụng chỉ thực hiện những thay đổi an toàn. Ví dụ:

  • Nội dung cập nhật cho một tài liệu nhà hàng chỉ có thể thay đổi điểm xếp hạng, chứ không thể thay đổi tên hoặc bất kỳ dữ liệu cố định nào khác.
  • Bạn chỉ có thể tạo điểm xếp hạng nếu mã nhận dạng người dùng khớp với người dùng đã đăng nhập, điều này giúp ngăn chặn hành vi giả mạo.

Ngoài việc sử dụng bảng điều khiển của Firebase, bạn có thể sử dụng Firebase CLI để triển khai các quy tắc cho dự án Firebase của mình. Tệp firestore.rules trong thư mục làm việc của bạn đã chứa các quy tắc nêu trên. Để triển khai các quy tắc này từ hệ thống tệp cục bộ (thay vì sử dụng bảng điều khiển Firebase), bạn sẽ chạy lệnh sau:

firebase deploy --only firestore:rules

13. Kết luận

Trong lớp học lập trình này, bạn đã tìm hiểu cách thực hiện các thao tác đọc và ghi cơ bản cũng như nâng cao bằng Cloud Firestore, cũng như cách bảo mật quyền truy cập vào dữ liệu bằng các quy tắc bảo mật. Bạn có thể tìm thấy giải pháp đầy đủ trong kho lưu trữ quickstarts-js.

Để tìm hiểu thêm về Cloud Firestore, hãy truy cập vào các tài nguyên sau:

14. [Không bắt buộc] Thực thi bằng tính năng Kiểm tra ứng dụng

Firebase Kiểm tra ứng dụng giúp bảo vệ bằng cách xác thực và ngăn chặn lưu lượng truy cập không mong muốn đến ứng dụng của bạn. Trong bước này, bạn sẽ bảo mật quyền truy cập vào các dịch vụ của mình bằng cách thêm tính năng Kiểm tra ứng dụng bằng reCAPTCHA Enterprise.

Trước tiên, bạn cần bật App Check và reCAPTCHA.

Bật reCAPTCHA Enterprise

  1. Trong Cloud Console, hãy tìm và chọn reCAPTCHA Enterprise trong mục Bảo mật.
  2. Bật dịch vụ theo lời nhắc rồi nhấp vào Tạo khoá.
  3. Nhập tên hiển thị theo lời nhắc và chọn Trang web làm loại nền tảng.
  4. Thêm các URL đã triển khai vào Danh sách miền và đảm bảo rằng bạn bỏ chọn lựa chọn "Sử dụng thử thách bằng hộp đánh dấu".
  5. Nhấp vào Tạo khoá rồi lưu trữ khoá đã tạo ở một nơi an toàn. Bạn sẽ cần đến khoá này ở bước sau.

Bật tính năng Kiểm tra ứng dụng

  1. Trong bảng điều khiển của Firebase, hãy tìm mục Tạo ở bảng điều khiển bên trái.
  2. Nhấp vào App Check, sau đó nhấp vào nút Bắt đầu (hoặc chuyển hướng trực tiếp đến bảng điều khiển).
  3. Nhấp vào Đăng ký rồi nhập khoá reCaptcha Enterprise khi được nhắc, sau đó nhấp vào Lưu.
  4. Trong chế độ xem API, hãy chọn Bộ nhớ rồi nhấp vào Thực thi. Làm tương tự cho Cloud Firestore.

Giờ đây, App Check sẽ được thực thi! Làm mới ứng dụng rồi thử tạo/xem một nhà hàng. Bạn sẽ nhận được thông báo lỗi:

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

Điều này có nghĩa là App Check sẽ chặn các yêu cầu chưa được xác thực theo mặc định. Bây giờ, hãy thêm quy trình xác thực vào ứng dụng của bạn.

Chuyển đến tệp FriendlyEats.View.js, cập nhật hàm initAppCheck và thêm khoá reCAPTCHA để khởi chạy App Check.

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

Thực thể appCheck được khởi chạy bằng ReCaptchaEnterpriseProvider có khoá của bạn và isTokenAutoRefreshEnabled cho phép mã thông báo tự động làm mới trong ứng dụng.

Để bật tính năng kiểm thử cục bộ, hãy tìm phần khởi chạy ứng dụng trong tệp FriendlyEats.js, rồi thêm dòng sau vào hàm FriendlyEats.prototype.initAppCheck:

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

Thao tác này sẽ ghi một mã thông báo gỡ lỗi vào bảng điều khiển của ứng dụng web cục bộ, tương tự như:

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

Bây giờ, hãy chuyển đến Chế độ xem ứng dụng của tính năng Kiểm tra ứng dụng trong bảng điều khiển của Firebase.

Nhấp vào trình đơn tràn rồi chọn Quản lý mã thông báo gỡ lỗi.

Sau đó, hãy nhấp vào Add debug token (Thêm mã gỡ lỗi) rồi dán mã gỡ lỗi từ bảng điều khiển khi được nhắc.

Xin chúc mừng! Lúc này, App Check sẽ hoạt động trong ứng dụng của bạn.