Laboratorium internetowe Cloud Firestore

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

1. Przegląd

Cele

W ramach tego ćwiczenia z programowania utworzysz aplikację internetową z rekomendacjami restauracji obsługiwaną przez Cloud Firestore .

img5.png

Czego się nauczysz

  • Odczytuj i zapisuj dane w Cloud Firestore z aplikacji internetowej
  • Słuchaj zmian w danych Cloud Firestore w czasie rzeczywistym
  • Użyj uwierzytelniania Firebase i reguł zabezpieczeń, aby zabezpieczyć dane Cloud Firestore
  • Pisz złożone zapytania Cloud Firestore

Co będziesz potrzebował

Przed rozpoczęciem tego ćwiczenia z programowania upewnij się, że zainstalowałeś:

2. Utwórz i skonfiguruj projekt Firebase

Utwórz projekt Firebase

  1. W konsoli Firebase kliknij Dodaj projekt , a następnie nazwij projekt Firebase FriendlyEats .

Zapamiętaj identyfikator projektu dla swojego projektu Firebase.

  1. Kliknij Utwórz projekt .

Aplikacja, którą zamierzamy zbudować, korzysta z kilku usług Firebase dostępnych w sieci:

  • Uwierzytelnianie Firebase w celu łatwej identyfikacji użytkowników
  • Cloud Firestore do zapisywania uporządkowanych danych w chmurze i otrzymywania natychmiastowych powiadomień, gdy dane zostaną zaktualizowane
  • Hosting Firebase do hostowania i obsługi Twoich zasobów statycznych

Na potrzeby tego konkretnego ćwiczenia z programowania skonfigurowaliśmy już Hosting Firebase. Jednak w przypadku Firebase Auth i Cloud Firestore przeprowadzimy Cię przez konfigurację i włączanie usług przy użyciu konsoli Firebase.

Włącz anonimowe uwierzytelnianie

Chociaż uwierzytelnianie nie jest przedmiotem tego ćwiczenia z programowania, ważne jest, aby w naszej aplikacji istniała jakaś forma uwierzytelniania. Użyjemy logowania anonimowego — co oznacza, że ​​użytkownik zostanie dyskretnie zalogowany bez pytania.

Musisz włączyć logowanie anonimowe.

  1. W konsoli Firebase znajdź sekcję Build w lewym panelu nawigacyjnym.
  2. Kliknij Uwierzytelnianie , a następnie kliknij kartę Metoda logowania (lub kliknij tutaj, aby przejść bezpośrednio do tego miejsca).
  3. Włącz dostawcę logowania anonimowego , a następnie kliknij Zapisz .

img7.png

Umożliwi to aplikacji ciche logowanie użytkowników, gdy uzyskują dostęp do aplikacji internetowej. Zachęcamy do przeczytania dokumentacji uwierzytelniania anonimowego, aby dowiedzieć się więcej.

Włącz Cloud Firestore

Aplikacja korzysta z Cloud Firestore do zapisywania i odbierania informacji i ocen restauracji.

Musisz włączyć Cloud Firestore. W sekcji Buduj w konsoli Firebase kliknij Baza danych Firestore . Kliknij Utwórz bazę danych w panelu Cloud Firestore.

Dostęp do danych w Cloud Firestore jest kontrolowany przez reguły bezpieczeństwa. Porozmawiamy więcej o regułach w dalszej części tego ćwiczenia z programowania, ale najpierw musimy ustawić kilka podstawowych reguł dotyczących naszych danych, aby rozpocząć. Na karcie Reguły konsoli Firebase dodaj następujące reguły, a następnie kliknij Publikuj .

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

Powyższe reguły ograniczają dostęp do danych do zalogowanych użytkowników, co uniemożliwia nieuwierzytelnionym użytkownikom czytanie lub pisanie. Jest to lepsze niż zezwolenie na publiczny dostęp, ale nadal jest dalekie od bezpieczeństwa. Poprawimy te zasady w dalszej części ćwiczenia z programowania.

3. Pobierz przykładowy kod

Sklonuj repozytorium GitHub z wiersza poleceń:

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

Przykładowy kod powinien zostać sklonowany do katalogu 📁 friendlyeats-web . Od teraz upewnij się, że uruchamiasz wszystkie polecenia z tego katalogu:

cd friendlyeats-web

Zaimportuj aplikację startową

Używając swojego IDE (WebStorm, Atom, Sublime, Visual Studio Code...) otwórz lub zaimportuj katalog friendlyeats-web . Ten katalog zawiera kod początkowy ćwiczenia z programowania, które składa się z niedziałającej jeszcze aplikacji polecającej restauracje. Sprawimy, że będzie funkcjonował w tym laboratorium, więc wkrótce będziesz musiał edytować kod w tym katalogu.

4. Zainstaluj interfejs wiersza poleceń Firebase

Interfejs wiersza poleceń Firebase (CLI) umożliwia lokalną obsługę aplikacji internetowej i wdrażanie jej w Firebase Hosting.

  1. Zainstaluj CLI, uruchamiając następujące polecenie npm:
npm -g install firebase-tools
  1. Sprawdź, czy interfejs wiersza polecenia został poprawnie zainstalowany, uruchamiając następujące polecenie:
firebase --version

Upewnij się, że wersja interfejsu Firebase CLI to 7.4.0 lub nowsza.

  1. Autoryzuj Firebase CLI, uruchamiając następujące polecenie:
firebase login

Skonfigurowaliśmy szablon aplikacji internetowej, aby pobrać konfigurację Twojej aplikacji dla Hostingu Firebase z lokalnego katalogu i plików aplikacji. Aby to zrobić, musimy jednak powiązać Twoją aplikację z projektem Firebase.

  1. Upewnij się, że wiersz poleceń uzyskuje dostęp do lokalnego katalogu aplikacji.
  2. Powiąż swoją aplikację z projektem Firebase, uruchamiając następujące polecenie:
firebase use --add
  1. Po wyświetleniu monitu wybierz identyfikator projektu , a następnie nadaj projektowi Firebase alias.

Alias ​​jest przydatny, jeśli masz wiele środowisk (produkcyjnych, pomostowych itp.). Jednak w tym ćwiczeniu z programowania użyjmy po prostu aliasu default .

  1. Postępuj zgodnie z pozostałymi instrukcjami w wierszu poleceń.

5. Uruchom serwer lokalny

Jesteśmy gotowi do rozpoczęcia prac nad naszą aplikacją! Uruchommy naszą aplikację lokalnie!

  1. Uruchom następujące polecenie Firebase CLI:
firebase emulators:start --only hosting
  1. Twój wiersz poleceń powinien wyświetlić następującą odpowiedź:
hosting: Local server: http://localhost:5000

Korzystamy z emulatora Firebase Hosting , aby lokalnie obsługiwać naszą aplikację. Aplikacja internetowa powinna być teraz dostępna pod adresem http://localhost:5000 .

  1. Otwórz swoją aplikację na http://localhost:5000 .

Powinieneś zobaczyć swoją kopię FriendlyEats, która została połączona z Twoim projektem Firebase.

Aplikacja automatycznie połączyła się z Twoim projektem Firebase i po cichu zalogowała Cię jako anonimowy użytkownik.

img2.png

6. Zapisz dane w Cloud Firestore

W tej sekcji zapiszemy pewne dane w Cloud Firestore, abyśmy mogli wypełnić interfejs aplikacji. Można to zrobić ręcznie za pomocą konsoli Firebase , ale zrobimy to w samej aplikacji, aby zademonstrować podstawowy zapis w Cloud Firestore.

Model danych

Dane Firestore są podzielone na kolekcje, dokumenty, pola i podkolekcje. Każda restauracja będzie przechowywana jako dokument w zbiorze najwyższego poziomu o nazwie restaurants .

img3.png

Później będziemy przechowywać każdą recenzję w podzbiorze o nazwie ratings pod każdą z restauracji.

img4.png

Dodaj restauracje do Firestore

Głównym obiektem modelowym w naszej aplikacji jest restauracja. Napiszmy kod, który dodaje dokument restauracji do kolekcji restaurants .

  1. Z pobranych plików otwórz scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.addRestaurant .
  3. Zastąp całą funkcję następującym kodem.

FriendlyEats.Data.js

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

Powyższy kod dodaje nowy dokument do kolekcji restaurants . Dane dokumentu pochodzą ze zwykłego obiektu JavaScript. Robimy to, najpierw uzyskując odwołanie do restaurants z kolekcji Cloud Firestore, a następnie add dane.

Dodajmy restauracje!

  1. Wróć do swojej aplikacji FriendlyEats w przeglądarce i odśwież ją.
  2. Kliknij opcję Dodaj próbne dane .

Aplikacja automatycznie wygeneruje losowy zestaw obiektów restauracji, a następnie wywoła funkcję addRestaurant . Jednak nie zobaczysz jeszcze danych w swojej rzeczywistej aplikacji internetowej , ponieważ nadal musimy zaimplementować pobieranie danych (następna sekcja ćwiczenia z programowania).

Jeśli jednak przejdziesz do zakładki Cloud Firestore w konsoli Firebase, powinieneś zobaczyć nowe dokumenty w kolekcji restaurants !

img6.png

Gratulacje, właśnie zapisałeś dane do Cloud Firestore z aplikacji internetowej!

W następnej sekcji dowiesz się, jak pobierać dane z Cloud Firestore i wyświetlać je w swojej aplikacji.

7. Wyświetl dane z Cloud Firestore

W tej sekcji dowiesz się, jak pobierać dane z Cloud Firestore i wyświetlać je w swojej aplikacji. Dwa kluczowe kroki to utworzenie zapytania i dodanie detektora migawki. Ten detektor zostanie powiadomiony o wszystkich istniejących danych, które pasują do zapytania i będzie otrzymywać aktualizacje w czasie rzeczywistym.

Najpierw skonstruujmy zapytanie, które będzie obsługiwać domyślną, niefiltrowaną listę restauracji.

  1. Wróć do pliku scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.getAllRestaurants .
  3. Zastąp całą funkcję następującym kodem.

FriendlyEats.Data.js

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

  this.getDocumentsInQuery(query, renderer);
};

W powyższym kodzie konstruujemy zapytanie, które pobierze do 50 restauracji z kolekcji najwyższego poziomu o nazwie restaurants , które są uporządkowane według średniej oceny (obecnie wszystkie zero). Po zadeklarowaniu tego zapytania, przekazujemy je do metody getDocumentsInQuery() , która odpowiada za ładowanie i renderowanie danych.

Zrobimy to, dodając nasłuchiwanie migawek.

  1. Wróć do pliku scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.getDocumentsInQuery .
  3. Zastąp całą funkcję następującym kodem.

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

W powyższym kodzie query.onSnapshot wyzwoli swoje wywołanie zwrotne za każdym razem, gdy nastąpi zmiana wyniku zapytania.

  • Za pierwszym razem wywołanie zwrotne jest wyzwalane z całym zbiorem wyników zapytania – czyli z całą kolekcją restaurants z Cloud Firestore. Następnie przekazuje wszystkie poszczególne dokumenty do funkcji renderer.display .
  • Kiedy dokument zostanie usunięty, change.type równa się removed . W tym przypadku wywołamy funkcję, która usunie restaurację z interfejsu użytkownika.

Teraz, gdy wdrożyliśmy obie metody, odśwież aplikację i sprawdź, czy restauracje, które widzieliśmy wcześniej w konsoli Firebase, są teraz widoczne w aplikacji. Jeśli pomyślnie ukończyłeś tę sekcję, oznacza to, że Twoja aplikacja odczytuje i zapisuje dane w Cloud Firestore!

Wraz ze zmianą listy restauracji ten odbiornik będzie aktualizował się automatycznie. Spróbuj przejść do konsoli Firebase i ręcznie usunąć restaurację lub zmienić jej nazwę – zmiany natychmiast pojawią się w Twojej witrynie.

img5.png

8. Get() dane

Jak dotąd pokazaliśmy, jak używać onSnapshot do pobierania aktualizacji w czasie rzeczywistym; jednak nie zawsze tego chcemy. Czasami bardziej sensowne jest pobranie danych tylko raz.

Chcemy zaimplementować metodę, która jest uruchamiana, gdy użytkownik kliknie w określonej restauracji w Twojej aplikacji.

  1. Wróć do swojego pliku scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.getRestaurant .
  3. Zastąp całą funkcję następującym kodem.

FriendlyEats.Data.js

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

Po wdrożeniu tej metody będziesz mógł przeglądać strony dla każdej restauracji. Wystarczy kliknąć restaurację na liście, a powinna wyświetlić się strona szczegółów restauracji:

img1.png

Na razie nie możesz dodawać ocen, ponieważ nadal musimy zaimplementować dodawanie ocen w dalszej części ćwiczenia z programowania.

9. Sortuj i filtruj dane

Obecnie nasza aplikacja wyświetla listę restauracji, ale użytkownik nie ma możliwości filtrowania według swoich potrzeb. W tej sekcji użyjesz zaawansowanych zapytań Cloud Firestore, aby włączyć filtrowanie.

Oto przykład prostego zapytania do pobrania wszystkich restauracji Dim Sum :

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

Jak sama nazwa wskazuje, metoda where() spowoduje, że nasze zapytanie pobierze tylko te elementy kolekcji, których pola spełniają określone przez nas ograniczenia. W tym przypadku pobierze tylko te restauracje, których category to Dim Sum .

W naszej aplikacji użytkownik może połączyć wiele filtrów, aby utworzyć konkretne zapytania, takie jak „Pizza w San Francisco” lub „Owoce morza w Los Angeles uporządkowane według popularności”.

Stworzymy metodę, która zbuduje zapytanie, które będzie filtrować nasze restauracje na podstawie wielu kryteriów wybranych przez naszych użytkowników.

  1. Wróć do swojego pliku scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.getFilteredRestaurants .
  3. Zastąp całą funkcję następującym kodem.

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

Powyższy kod dodaje wiele filtrów where i jedną klauzulę orderBy w celu utworzenia zapytania złożonego na podstawie danych wprowadzonych przez użytkownika. Nasze zapytanie zwróci teraz tylko te restauracje, które spełniają wymagania użytkownika.

Odśwież swoją aplikację FriendlyEats w przeglądarce, a następnie sprawdź, czy możesz filtrować według ceny, miasta i kategorii. Podczas testowania zobaczysz błędy w konsoli JavaScript swojej przeglądarki, które wyglądają tak:

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

Te błędy wynikają z tego, że Cloud Firestore wymaga indeksów w przypadku większości zapytań złożonych. Wymaganie indeksów w zapytaniach sprawia, że ​​Cloud Firestore działa szybko na dużą skalę.

Otwarcie linku z komunikatu o błędzie spowoduje automatyczne otwarcie interfejsu tworzenia indeksu w konsoli Firebase z wypełnionymi poprawnymi parametrami. W następnej sekcji napiszemy i wdrożymy indeksy potrzebne dla tej aplikacji.

10. Wdrażaj indeksy

Jeśli nie chcemy eksplorować każdej ścieżki w naszej aplikacji i podążać za każdym z linków do tworzenia indeksów, możemy łatwo wdrożyć wiele indeksów jednocześnie, korzystając z interfejsu Firebase CLI.

  1. W pobranym katalogu lokalnym aplikacji znajdziesz plik firestore.indexes.json .

Ten plik opisuje wszystkie indeksy potrzebne do wszystkich możliwych kombinacji filtrów.

firestore.indexes.json

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

   ...

 ]
}
  1. Wdróż te indeksy za pomocą następującego polecenia:
firebase deploy --only firestore:indexes

Po kilku minutach Twoje indeksy będą aktywne, a komunikaty o błędach znikną.

11. Wpisz dane w transakcji

W tej sekcji dodamy możliwość przesyłania opinii do restauracji przez użytkowników. Jak dotąd wszystkie nasze teksty były atomowe i stosunkowo proste. Jeśli któryś z nich się pomylił, prawdopodobnie po prostu poprosimy użytkownika o ponowną próbę lub nasza aplikacja spróbuje automatycznie ponowić zapis.

Nasza aplikacja będzie miała wielu użytkowników, którzy będą chcieli dodać ocenę restauracji, więc będziemy musieli koordynować wiele odczytów i zapisów. Najpierw należy przesłać samą recenzję, a następnie zaktualizować count ocen restauracji i average rating . Jeśli jeden z nich zawiedzie, ale nie drugi, pozostaniemy w niespójnym stanie, w którym dane w jednej części naszej bazy danych nie pasują do danych w innej.

Na szczęście Cloud Firestore zapewnia funkcjonalność transakcyjną, która pozwala nam wykonywać wiele odczytów i zapisów w jednej atomowej operacji, zapewniając spójność naszych danych.

  1. Wróć do swojego pliku scripts/FriendlyEats.Data.js .
  2. Znajdź funkcję FriendlyEats.prototype.addRating .
  3. Zastąp całą funkcję następującym kodem.

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

W powyższym bloku wyzwalamy transakcję, aby zaktualizować wartości liczbowe avgRating i numRatings w dokumencie restauracji. Jednocześnie dodajemy nową rating do podzbioru ratings .

12. Zabezpiecz swoje dane

Na początku tego ćwiczenia z kodowania ustawiliśmy reguły bezpieczeństwa naszej aplikacji, aby całkowicie otworzyć bazę danych na dowolny odczyt lub zapis. W prawdziwej aplikacji chcielibyśmy ustawić znacznie bardziej szczegółowe reguły, aby zapobiec niepożądanemu dostępowi do danych lub ich modyfikacji.

  1. W sekcji Buduj w konsoli Firebase kliknij Baza danych Firestore .
  2. Kliknij kartę Reguły w sekcji Cloud Firestore (lub kliknij tutaj, aby przejść bezpośrednio tam).
  3. Zastąp wartości domyślne następującymi regułami, a następnie kliknij Opublikuj .

firestore.zasady

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

Te reguły ograniczają dostęp, aby zapewnić, że klienci wprowadzają tylko bezpieczne zmiany. Na przykład:

  • Aktualizacje dokumentu restauracji mogą zmienić tylko oceny, a nie nazwę lub inne niezmienne dane.
  • Oceny można tworzyć tylko wtedy, gdy identyfikator użytkownika jest zgodny z zalogowanym użytkownikiem, co zapobiega podszywaniu się.

Alternatywnie do korzystania z konsoli Firebase możesz użyć interfejsu wiersza polecenia Firebase, aby wdrożyć reguły w swoim projekcie Firebase. Plik firestore.rules w twoim katalogu roboczym zawiera już powyższe reguły. Aby wdrożyć te reguły z lokalnego systemu plików (zamiast używać konsoli Firebase), uruchom następujące polecenie:

firebase deploy --only firestore:rules

13. Wniosek

Podczas tego ćwiczenia kodowania nauczyłeś się, jak wykonywać podstawowe i zaawansowane odczyty i zapisy w Cloud Firestore, a także jak zabezpieczyć dostęp do danych za pomocą reguł bezpieczeństwa. Pełne rozwiązanie znajdziesz w repozytorium quickstarts-js .

Aby dowiedzieć się więcej o Cloud Firestore, odwiedź następujące zasoby: