1. Обзор
Цели
В этой лаборатории кода вы создадите веб-приложение с рекомендациями ресторанов на базе Cloud Firestore .
Что вы узнаете
- Чтение и запись данных в Cloud Firestore из веб-приложения.
- Слушайте изменения в данных Cloud Firestore в режиме реального времени
- Используйте правила аутентификации и безопасности Firebase для защиты данных Cloud Firestore.
- Написание сложных запросов Cloud Firestore
Что вам понадобится
Прежде чем приступить к этой лабораторной работе, убедитесь, что вы установили:
2. Создайте и настройте проект Firebase.
Создать проект Firebase
- В консоли Firebase нажмите «Добавить проект» , затем назовите проект Firebase FriendlyEats .
Запомните идентификатор вашего проекта Firebase.
- ��а��мите Создать проект .
Приложение, которое мы собираемся создать, использует несколько сервисов Firebase, доступных в Интернете:
- Аутентификация Firebase для легкой идентификации ваших пользователей
- Cloud Firestore для сохранения структурированных данных в облаке и мгновенного получения уведомлений при обновлении данных.
- Хостинг Firebase для размещения и обслуживания ваших статических ресурсов.
Для этой конкретной лаборатории мы уже настроили хостинг Firebase. Однако для Firebase Auth и Cloud Firestore мы покажем вам настройку и включение сервисов с помощью консоли Firebase.
Включить анонимную аутентификацию
Хотя аутентификация не является целью этой лаборатории, важно иметь ту или иную форму аутентификации в нашем приложении. Мы будем использовать анонимный вход – это означает, что пользователь будет входить в систему автоматически, без соответствующего запроса.
Вам необходимо включить анонимный вход.
- В консоли Firebase найдите раздел «Сборка» в левой навигационной панели.
- Нажмите «Аутентификация » , затем перейдите на вкладку «Метод входа » (или нажмите здесь , чтобы перейти непосредственно туда).
- Включите поставщика анонимного входа, затем нажмите «Сохранить» .
Это позволит приложению автоматически входить в систему ваших пользователей, когда они получают доступ к веб-приложению. Не стесняйтесь читать документацию по анонимной аутентификации , чтобы узнать больше.
Включить Cloud Firestore
Приложение использует Cloud Firestore для сохранения и получения информации и рейтингов ресторанов.
Вам нужно будет включить Cloud Firestore. В разделе «Сборка» консоли Firebase нажмите «База данных Firestore» . Нажмите Создать базу данных на панели Cloud Firestore.
Доступ к данным в Cloud Firestore контролируется правилами безопасности. Мы поговорим о правилах подробнее позже в этой лаборатории кода, но сначала нам нужно установить некоторые базовые правила для наших данных, чтобы начать. На вкладке «Правила» консоли Firebase добавьте следующие правила и нажмите «Опубликовать» .
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; } } }
Приведенные выше правила ограничивают доступ к данным для пользователей, вошедших в систему, что предотвращает чтение или запись неаутентифицированными пользователями. Это лучше, чем разрешить публичный доступ, но все еще далеко от безопасности. Мы улучшим эти правила позже в лаборатории кода.
3. Получите пример кода
Клонируйте репозиторий GitHub из командной строки:
git clone https://github.com/firebase/friendlyeats-web
Пример кода должен был быть клонирован в каталог 📁 friendlyeats-web
. С этого момента обязательно запускайте все свои команды из этого каталога:
cd friendlyeats-web/vanilla-js
Импортируйте начальное приложение
Используя свою IDE (WebStorm, Atom, Sublime, Visual Studio Code...), откройте или импортируйте каталог 📁 friendlyeats-web
. Этот каталог содержит начальный код для ��аборатории кода, которая представляет собой еще не работающее приложение для рекомендаций ресторанов. Мы сделаем его функциональным на протяжении всей этой лаборатории кода, поэтому вскоре вам нужно будет отредактирова��ь код в этом каталоге.
4. Установите интерфейс командной строки Firebase.
Интерфейс командной строки Firebase (CLI) позволяет вам обслуживать ваше веб-приложение локально и развертывать его на хостинге Firebase.
- Установите CLI, выполнив следующую команду npm:
npm -g install firebase-tools
- Убедитесь, что CLI установлен правильно, выполнив следующую команду:
firebase --version
Убедитесь, что версия Firebase CLI — v7.4.0 или новее.
- Авторизуйте Firebase CLI, выполнив следующую команду:
firebase login
Мы настроили шаблон веб-приложения, чтобы получить конфигурацию вашего приложения для хостинга Firebase из локального каталога и файлов вашего приложения. Но для этого нам нужно связать ваше приложение с вашим проектом Firebase.
- Убедитесь, что ваша командная строка обращается к локальному каталогу вашего приложения.
- Свяжите свое приложение с проектом Firebase, выполнив следующую команду:
firebase use --add
- При появлении запроса выберите идентификатор проекта и присвойте проекту Firebase псевдоним.
Псевдоним полезен, если у вас несколько сред (производственная, промежуточная и т. д.). Однако для этой кодовой лаборатории давайте просто воспользуемся псевдонимом default
.
- Следуйте остальным инструкциям в командной строке.
5. Запустите локальный сервер
Мы готовы начать работу над нашим приложением! Давайте запустим наше приложение локально!
- Выполните следующую команду Firebase CLI:
firebase emulators:start --only hosting
- В вашей командной строке должен появиться следующий ответ:
hosting: Local server: http://localhost:5000
Мы используем эмулятор хостинга Firebase для локального обслуживания нашего приложения. Веб-приложение теперь должно быть доступно по адресу http://localhost:5000 .
- Откройте свое приложение по адресу http://localhost:5000 .
Вы должны увидеть свою копию FriendlyEats, подключенную к вашему проекту Firebase.
Приложение автоматически подключилось к вашему проекту Firebase и автоматически выполнило вход как анонимный пользователь.
6. Запишите данные в Cloud Firestore.
В этом разделе мы запишем некоторые данные в Cloud Firestore, чтобы можно было заполнить пользовательский интерфейс приложения. Это можно сделать вручную через консоль Firebase , но мы сделаем это в самом приложении, чтобы продемонстрировать базовую запись в Cloud Firestore.
Модель данных
Данные Firestore разделены на коллекции, документы, поля и подколлекции. Мы будем хранить каждый ресторан как документ в коллекции верхнего уровня под названием restaurants
.
Позже мы сохраним каждый отзыв в подколлекции под названием ratings
под каждым рестораном.
Добавьте рестораны в Firestore
Основным объектом модели в нашем приложении является ресторан. Давайте напишем код, который добавляет документ ресторана в коллекцию restaurants
.
- Из загруженных файлов откройте
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.addRestaurant
. - Замените всю функцию следующим кодом.
FriendlyEats.Data.js
FriendlyEats.prototype.addRestaurant = function(data) { var collection = firebase.firestore().collection('restaurants'); return collection.add(data); };
Приведенный выше код добавляет новый документ в коллекцию restaurants
. Данные документа поступают из простого объекта JavaScript. Мы делаем это, сначала получая ссылку на restaurants
из коллекции Cloud Firestore, а затем add
данные.
Давайте добавим рестораны!
- Вернитесь в приложение FriendlyEats в браузере и обновите его.
- Нажмите «Добавить фиктивные данные» .
Приложение автоматически сгенерирует случайный набор объектов ресторанов, а затем вызовет функцию addRestaurant
. Однако вы пока не увидите данные в своем реальном веб-приложении, поскольку нам все еще нужно реализовать получение данных (следующий раздел кодовой лаборатории).
Однако если вы перейдете на вкладку Cloud Firestore в консоли Firebase, вы теперь должны увидеть новые документы в коллекции restaurants
!
Поздравляем, вы только что записали данные в Cloud Firestore из веб-приложения!
В следующем разделе вы узнаете, как получать данные из Cloud Firestore и отображать их в своем приложении.
7. Отображение данных из Cloud Firestore.
В этом разделе вы узнаете, как получать данные из Cloud Firestore и отображать их в своем приложении. Два ключевых шага — создание запроса и добавление прослушивателя снимков. Этот прослушиватель будет уведомлен обо всех существующих данных, соответствующих запросу, и будет получать обновления в режиме реального времени.
Сначала давайте создадим запрос, который будет обслуживать нефильтрованный список ресторанов по умолчанию.
- Вернитесь к файлу
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.getAllRestaurants
. - Замените всю функцию следующим кодом.
FriendlyEats.Data.js
FriendlyEats.prototype.getAllRestaurants = function(renderer) { var query = firebase.firestore() .collection('restaurants') .orderBy('avgRating', 'desc') .limit(50); this.getDocumentsInQuery(query, renderer); };
В приведенном выше коде мы создаем запрос, который извлекает до 50 ресторанов из коллекции верхнего уровня с именем restaurants
, упорядоченных по среднему рейтингу (в настоящее время все ноль). После объявления этого запроса мы передаем ��го методу getDocumentsInQuery()
, который отвечает за загрузку и отображение данных.
Мы сделаем это, добавив прослушиватель снимков.
- Вернитесь к файлу
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.getDocumentsInQuery
. - Замените всю функцию следующим кодом.
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); } }); }); };
В приведенном выше коде query.onSnapshot
запускает обратный вызов каждый раз, когда происходит изменение результата запроса.
- В первый раз обратный вызов запускается со всем набором результатов запроса, то есть со всей коллекцией
restaurants
из Cloud Firestore. Затем он передает все отдельные документы функцииrenderer.display
. - Когда документ удаляется,
change.type
равенremoved
. В данном случае мы вызовем функцию, которая удалит ресторан из пользовательского интерфейса.
Теперь, когда мы реализовали оба метода, обновите приложение и убедитесь, что рестораны, которые мы видели ранее в консоли Firebase, теперь видны в приложении. Если вы успешно завершили этот раздел, то ваше приложение теперь читает и записывает данные с помощью Cloud Firestore!
По мере изменения вашего списка ресторанов этот прослушиватель будет автоматически обновляться. Попробуйте зайти в консоль Firebase и вручную удалить ресторан или изменить его название — изменения сразу же отобразятся на вашем сайте!
8. Получить() данные
До сих пор мы показали, как использовать onSnapshot
для получения обновлений в реальном времени; однако это не всегда то, чего мы хотим. Иногда имеет смысл получить данные только один раз.
Мы хотим реализовать метод, который срабатывает, когда пользователь нажимает на определенный ресторан в вашем приложении.
- Вернитесь к своему файлу
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.getRestaurant
. - Замените всю функцию следующим кодом.
FriendlyEats.Data.js
FriendlyEats.prototype.getRestaurant = function(id) { return firebase.firestore().collection('restaurants').doc(id).get(); };
После реализации этого метода вы сможете просматривать страницы каждого ресторана. Пр��сто нажмите на ресторан в списке, и вы увидите страницу с подробной информацией о ресторане:
На данный момент вы не можете добавлять рейтинги, поскольку нам еще нужно реализовать добавление рейтингов позже в лаборатории кода.
9. Сортировка и фильтрация данных
В настоящее время наше приложение отображает список ресторанов, но у пользователя нет возможности фильтровать его по своим потребностям. В этом разделе вы будете использовать расширенные запросы Cloud Firestore для включения фильтрации.
Вот пример простого запроса для получения всех ресторанов Dim Sum
:
var filteredQuery = query.where('category', '==', 'Dim Sum')
Как следует из названия, методwhere where()
заставит наш запрос загружать только те элементы коллекции, поля которых соответствуют установленным нами ограничениям. В этом случае будут загружены только рестораны с category
Dim Sum
.
В нашем приложении пользователь может объединить несколько фильтров для создания конкретных запросов, например «Пицца в Сан-Франциско» или «Морепродукты в Лос-Анджелесе в порядке популярности».
Мы создадим метод, который создаст запрос, который будет фильтровать наши рестораны на основе нескольких критериев, выбранных нашими пользователями.
- Вернитесь к своему файлу
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.getFilteredRestaurants
. - Замените всю функцию следующим кодом.
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); };
В приведенном выше коде добавлено несколько where
и одно предложение orderBy
для построения составного запроса на основе пользовательского ввода. Наш запрос теперь будет возвращать только те рестораны, которые соответствуют ��ребованиям пользователя.
Обновите приложение FriendlyEats в браузере, а затем убедитесь, что вы можете фильтровать по цене, городу и категории. Во время тестирования вы увидите ошибки в консоли JavaScript вашего браузера, которые выглядят следующим образом:
The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...
Эти ошибки связаны с тем, что Cloud Firestore требует индексов для большинства составных запросов. Требование индексов для запросов позволяет Cloud Firestore быстро масштабироваться.
Открытие ссылки из сообщения об ошибке автоматически откроет пользовательский интерфейс создания индекса в консоли Firebase с заполненными правильными параметрами. В следующем разделе мы напишем и развернем индексы, необходимые для этого приложения.
10. Развертывание индексов
Если мы не хотим исследовать каждый путь в нашем приложении и переходить по каждой ссылке для создания индекса, мы можем легко развернуть множество индексов одновременно с помощью Firebase CLI.
- В загруженном локальном каталоге вашего приложения вы найдете файл
firestore.indexes.json
.
Этот файл описывает все индексы, необходимые для всех возможных комбинаций фильтров.
firestore.indexes.json
{ "indexes": [ { "collectionGroup": "restaurants", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "city", "order": "ASCENDING" }, { "fieldPath": "avgRating", "order": "DESCENDING" } ] }, ... ] }
- Разверните эти индексы с помощью следующей команды:
firebase deploy --only firestore:indexes
Через несколько минут ваши индексы станут активными, а сообщения об ошибках исчезнут.
11. Запись данных в транзакцию
В этом разделе мы добавим возможность пользователям оставлять отзывы о ресторанах. До сих пор все наши операции записи были атомарными и относи��ельно простыми. Если какой-либо из них приведет к ошибке, мы, скорее всего, просто предложим пользователю повторить попытку, или наше приложение автоматически повторит попытку записи.
В нашем приложении будет много пользователей, которые захотят добавить рейтинг ресторана, поэтому нам нужно будет координировать несколько операций чтения и записи. Сначала необходимо отправить сам отзыв, затем обновить count
ресторана и average rating
. Если один из них дает сбой, а другой нет, мы остаемся в противоречивом состоянии, когда данные в одной части нашей базы данных не совпадают с данными в другой.
К счастью, Cloud Firestore предоставляет функциональность транзакций, которая позволяет нам выполнять несколько операций чтения и записи за одну атомарную операцию, гарантируя, что наши данные остаются согласованными.
- Вернитесь к своему файлу
scripts/FriendlyEats.Data.js
. - Найдите функцию
FriendlyEats.prototype.addRating
. - Замените всю функцию следующим кодом.
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); }); }); };
В приведенном выше блоке мы запускаем транзакцию для обновления числовых значений avgRating
и numRatings
в документе ресторана. Одновременно мы добавляем новый rating
в подколлекцию ratings
.
12. Защитите свои данные
В начале этой лабораторной ра��от�� ��ы уста��ов��ли ��равила безопасности нашего приложения, чтобы полностью открыть базу данных для любого чтения или записи. В реальном приложении нам хотелось бы установить гораздо более детальные правила, чтобы предотвратить нежелательный доступ к данным или их изменение.
- В разделе «Сборка» консоли Firebase нажмите «База данных Firestore» .
- Перейдите на вкладку «Правила» в разделе Cloud Firestore (или нажмите здесь , чтобы перейти непосредственно туда).
- Замените значения по умолчанию следующими правилами, затем нажмите «Опубликовать» .
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; } } } }
Эти правила ограничивают доступ, чтобы клиенты могли вносить только безопасные изменения. Например:
- Обновления документа о ресторане могут изменить только рейтинги, но не имя или какие-либо другие неизменяемые данные.
- Рейтинги можно создавать только в том случае, если идентификатор пользователя соответствует и��ентификатору вошедшего в систему пользователя, что предотвращает подделку.
В качестве альтернативы использованию консоли Firebase вы можете использовать интерфейс командной строки Firebase для развертывания правил в вашем проекте Firebase. Файл firestore.rules в вашем рабочем каталоге уже содержит приведенные выше правила. Чтобы развернуть эти правила из вашей локальной файловой системы (вместо использования консоли Firebase), вы должны выполнить следующую команду:
firebase deploy --only firestore:rules
13. Заключение
В этой лабораторной работе вы узнали, как выполнять базовые и расширенные операции чтения и записи с помощью Cloud Firestore, а также как защитить доступ к данным с помощью правил безопасности. Полное решение вы можете найти в репозитории faststarts-js .
Чтобы узнать больше об Cloud Firestore, посетите следующие ресурсы:
14. [Необязательно] Принудительное применение с помощью проверки приложений
Firebase App Check обеспечивает защиту, помогая проверять и предотвращать нежелательный трафик в ваше приложение. На этом этапе вы защитите доступ к своим сервисам, добавив проверку приложений с помощью reCAPTCHA Enterprise .
Сначала вам нужно включить проверку приложений и reCaptcha.
Включение reCaptcha Enterprise
- В облачной консоли найдите и выберите reCaptcha Enterprise в разделе «Безопасность».
- Включите службу согласно запросу и нажмите «Созд��ть ключ» .
- Введите отображаемое имя в соответствии с запросом и выберите Веб-сайт в качестве типа платформы.
- Добавьте развернутые URL-адреса в список доменов и убедитесь, что параметр «Использовать вызов флажка» не выбран .
- Нажмите «Создать ключ» и сохраните сгенерированный ключ где-нибудь для безопасного хранения. Он понадобится вам позже на этом этапе.
Включение проверки приложений
- В консоли Firebase найдите раздел «Сборка» на левой панели.
- Нажмите «Проверка приложений» , затем нажмите кнопку «Начать» (или перенаправьте непосредственно на консоль ).
- Нажмите «Зарегистрировать» и при появлении запроса введите ключ reCaptcha Enterprise, затем нажмите «Сохранить» .
- В представлении API выберите «Хранилище» и нажмите «Применить» . Сделайте то же самое для Cloud Firestore .
Проверка приложений теперь должна быть обязательной! Обновите приложение и попробуйте создать/просмотреть ресторан. Вы должны получить сообщение об ошибке:
Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Это означает, что проверка приложений по умолчанию блокирует непроверенные запросы. Теперь давайте добавим проверку в ваше приложение.
Перейдите к файлу FriendlyEats.View.js , обновите функцию initAppCheck
и добавьте ключ reCaptcha для инициализации проверки приложений.
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.
);
};
Экземпляр appCheck
инициализируется с помощью ReCaptchaEnterpriseProvider
с вашим ключом, а isTokenAutoRefreshEnabled
позволяет токенам автоматически обновляться в вашем приложении.
Чтобы включить локальное тестирование, найдите раздел, в котором инициализируется приложение, в файле FriendlyEats.js и добавьте сле��ующую строку в функцию FriendlyEats.prototype.initAppCheck
:
if(isLocalhost) {
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}
Это зарегистрирует токен отладки в консоли вашего локального веб-приложения, аналогичный:
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.
Теперь перейдите в представление «Проверка приложений» в консоли Firebase.
Нажмите на дополнительное меню и выберите «Управление токенами отладки ».
Затем нажмите «Добавить токен отладки» и вставьте токен отладки с консоли, как будет предложено.
Поздравляем! Проверка приложений теперь должна работать в вашем приложении.