Laboratorium internetowe Cloud Firestore

1. Przegląd

Cele

W tym laboratorium kodowania 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ł bezpieczeństwa, aby zabezpieczyć dane Cloud Firestore
  • Pisz złożone zapytania Cloud Firestore

Co będziesz potrzebował

Przed rozpoczęciem tego modułu CodeLab upewnij się, że masz zainstalowane:

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 udostępniania zasobów statycznych

Na potrzeby tego konkretnego laboratorium kodu skonfigurowaliśmy już Hosting Firebase. Jednak w przypadku Firebase Auth i Cloud Firestore przeprowadzimy Cię przez proces konfiguracji i włączania usług przy użyciu konsoli Firebase.

Włącz anonimową autoryzację

Chociaż uwierzytelnianie nie jest głównym tematem tego laboratorium kodowania, ważne jest, aby mieć jakąś formę uwierzytelniania w naszej aplikacji. Będziemy używać logowania anonimowego , co oznacza, że ​​użytkownik będzie logował się po cichu bez monitu.

Musisz włączyć anonimowe logowanie.

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

img7.png

Umożliwi to aplikacji dyskretne logowanie użytkowników, gdy uzyskują oni dostęp do aplikacji internetowej. Zachęcamy do zapoznania się z dokumentacją uwierzytelniania anonimowego, aby dowiedzieć się więcej.

Włącz Cloud Firestore

Aplikacja korzysta z Cloud Firestore do zapisywania i odbierania informacji o restauracjach oraz ocen.

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

Dostęp do danych w Cloud Firestore jest kontrolowany przez Reguły bezpieczeństwa. Porozmawiamy więcej o regułach później w tym laboratorium kodowania, 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 odczytywanie i zapisywanie. Jest to lepsze niż umożliwienie publicznego dostępu, ale nadal jest dalekie od bezpieczeństwa. Poprawimy te zasady później w codelab.

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 startowy dla codelab, który składa się z jeszcze niefunkcjonalnej aplikacji do rekomendacji restauracji. Sprawimy, że będzie działać w całym 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 lokalne udostępnianie aplikacji internetowej i wdrażanie aplikacji internetowej w Hostingu Firebase.

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

Upewnij się, że wersja 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 pobierać konfigurację Twojej aplikacji dla Hostingu Firebase z lokalnego katalogu i plików Twojej aplikacji. Aby to zrobić, musimy powiązać Twoją aplikację z projektem Firebase.

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

Alias ​​jest przydatny, jeśli masz wiele środowisk (produkcyjne, pomostowe itp.). Jednak w tym laboratorium kodu po prostu użyjmy aliasu default .

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

5. Uruchom serwer lokalny

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

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

Używamy emulatora Hostingu Firebase do obsługi naszej aplikacji lokalnie. Aplikacja internetowa powinna być teraz dostępna pod adresem http://localhost:5000 .

  1. Otwórz swoją aplikację pod adresem 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 anonimowego użytkownika.

img2.png

6. Zapisz dane w Cloud Firestore

W tej sekcji zapiszemy niektóre dane w Cloud Firestore, abyśmy mogli zapeł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 kolekcje podrzędne. Każdą restaurację będziemy przechowywać jako dokument w kolekcji najwyższego poziomu o nazwie restaurants .

img3.png

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

img4.png

Dodaj restauracje do Firestore

Głównym modelowym obiektem 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 odniesienie do restaurants z kolekcji Cloud Firestore, a następnie add dane.

Dodajmy restauracje!

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

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

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

img6.png

Gratulacje, właśnie zapisałeś dane w 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świetlaj 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 odbiornika migawek. Ten odbiornik zostanie powiadomiony o wszystkich istniejących danych pasujących do zapytania i będzie otrzymywać aktualizacje w czasie rzeczywistym.

Najpierw skonstruujmy zapytanie, które będzie wyświetlać 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 tworzymy 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 zerowe). Po zadeklarowaniu tego zapytania przekazujemy je do metody getDocumentsInQuery() , która odpowiada za ładowanie i renderowanie danych.

Zrobimy to, dodając odbiornik migawki.

  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 wywoła wywołanie zwrotne za każdym razem, gdy nastąpi zmiana wyniku zapytania.

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

Teraz, gdy zaimplementowaliś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!

Gdy Twoja lista restauracji się zmieni, ten odbiornik będzie aktualizował się automatycznie. Spróbuj przejść do konsoli Firebase i ręcznie usunąć restaurację lub zmienić jej nazwę — zobaczysz, że zmiany natychmiast pojawią się na Twojej stronie!

img5.png

8. Pobierz() dane

Do tej pory 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 określoną restaurację w Twojej aplikacji.

  1. Wróć do 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 każdej restauracji. Po prostu kliknij restaurację na liście i powinieneś zobaczyć stronę szczegółów restauracji:

img1.png

Na razie nie możesz dodawać ocen, ponieważ nadal musimy zaimplementować dodawanie ocen później w codelab.

9. Sortuj i filtruj dane

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

Oto przykład prostego zapytania, które pobierze wszystkie restauracje Dim Sum :

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

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

W naszej aplikacji użytkownik może połączyć wiele filtrów, aby utworzyć określone zapytania, takie jak „Pizza w San Francisco” lub „Owoce morza w Los Angeles zamówione 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 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 zbudowania zapytania złożonego na podstawie danych wprowadzonych przez użytkownika. Nasze zapytanie będzie teraz zwracało tylko restauracje spełniające wymagania użytkownika.

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

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

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

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

10. Wdróż indeksy

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

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

Ten plik opisuje wszystkie indeksy potrzebne dla 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. Zapisz dane w transakcji

W tej sekcji dodamy możliwość przesyłania przez użytkowników recenzji do restauracji. Jak dotąd wszystkie nasze zapisy były atomowe i stosunkowo proste. Jeśli którykolwiek z nich zawierał błąd, prawdopodobnie po prostu poprosilibyśmy użytkownika o ponowienie próby lub nasza aplikacja automatycznie ponowiłaby próbę zapisu.

Nasza aplikacja będzie miała wielu użytkowników, którzy będą chcieli dodać ocenę restauracji, więc będziemy musieli skoordynować wiele odczytów i zapisów. Najpierw należy przesłać samą recenzję, a następnie zaktualizować count ocen i average rating restauracji. Jeśli jeden z nich zawiedzie, a drugi nie, pozostajemy 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 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 uruchamiamy transakcję, aby zaktualizować wartości liczbowe avgRating i numRatings w dokumencie restauracji. Jednocześnie dodajemy nową rating do podkolekcji ratings .

12. Zabezpiecz swoje dane

Na początku tego laboratorium kodowania ustaliliś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 Kompilacja konsoli Firebase kliknij opcję Baza danych Firestore .
  2. Kliknij kartę Reguły w sekcji Cloud Firestore (lub kliknij tutaj, aby przejść bezpośrednio do niej).
  3. Zastąp wartości domyślne następującymi regułami, a następnie kliknij Publikuj .

sklep.reguły

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 upewnić się, że klienci dokonują tylko bezpiecznych zmian. Na przykład:

  • Aktualizacje dokumentu restauracji mogą zmienić tylko oceny, a nie nazwę ani 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 konsoli Firebase możesz użyć Firebase CLI do wdrożenia reguł 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

W tym laboratorium kodowania nauczyłeś się, jak wykonywać podstawowe i zaawansowane odczyty i zapisy w Cloud Firestore, a także jak zabezpieczać dostęp do danych za pomocą reguł bezpieczeństwa. Pełne rozwiązanie można znaleźć w repozytorium quickstarts-js .

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