Pic-a-daily: Лабораторная работа 4. Создание веб-интерфейса

1. Обзор

В этой лаборатории кода вы создадите веб-интерфейс на Google App Engine, который позволит пользователям загружать изображения из веб-приложения, а также просматривать загруженные изображения и их миниатюры.

21741cd63b425aeb.png

Это веб-приложение будет использовать CSS-фреймворк под названием Bulma для создания красивого пользовательского интерфейса, а также интерфейсную среду JavaScript Vue.JS для вызова API приложения, которое вы создадите.

Это приложение будет состоять из трёх вкладок:

  • Домашняя страница, на которой будут отображаться миниатюры всех загруженных изображений, а также список меток, описывающих изображение (те, которые были обнаружены Cloud Vision API в предыдущем лабораторном исследовании).
  • Страница коллажей , на которой будет показан коллаж, сделанный из 4 последних загруженных изображений.
  • Страница загрузки , на которую пользователи могут загружать новые изображения.

В результате интерфейс выглядит следующим образом:

6a4d5e5603ba4b73.png

Эти 3 страницы представляют собой простые HTML-страницы:

  • Домашняя страница ( index.html ) вызывает внутренний код Node App Engine, чтобы получить список миниатюр изображений и их меток, посредством вызова AJAX по URL-адресу /api/pictures . Домашняя страница использует Vue.js для получения этих данных.
  • Страница коллажа ( collage.html ) указывает на изображение collage.png , в котором собраны 4 последних изображения.
  • Страница загрузки ( upload.html ) предлагает простую форму для загрузки изображения с помощью запроса POST по URL-адресу /api/pictures .

Что вы узнаете

  • Механизм приложений
  • Облачное хранилище
  • Облачный пожарный магазин

2. Настройка и требования

Самостоятельная настройка среды

  1. Войдите в Google Cloud Console и создайте новый проект или повторно используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Имя проекта — это отображаемое имя для участников этого проекта. Это строка символов, не используемая API Google, и вы можете обновить ее в любое время.
  • Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (нельзя изменить после его установки). Cloud Console автоматически генерирует уникальную строку; обычно тебя не волнует, что это такое. В большинстве лабораторий кода вам потребуется указать идентификатор проекта (обычно он обозначается как PROJECT_ID ), поэтому, если он вам не нравится, создайте другой случайный идентификатор или попробуйте свой собственный и посмотрите, доступен ли он. Затем он «замораживается» после создания проекта.
  • Существует третье значение — номер проекта , который используют некоторые API. Подробнее обо всех трех этих значениях читайте в документации .
  1. Затем вам необходимо включить выставление счетов в Cloud Console, чтобы использовать облачные ресурсы/API. Прохождение этой лаборатории кода не должно стоить много, если вообще стоит. Чтобы отключить ресурсы и не платить за выставление счетов за пределами этого руководства, следуйте инструкциям по «очистке», которые можно найти в конце лаборатории кода. Новые пользователи Google Cloud имеют право на участие в программе бесплатной пробной версии стоимостью 300 долларов США .

Запустить Cloud Shell

Хотя Google Cloud можно управлять удаленно с вашего ноутбука, в этой лаборатории вы будете использовать Google Cloud Shell , среду командной строки, работающую в облаке.

В Google Cloud Console щелкните значок Cloud Shell на верхней правой панели инструментов:

55efc1aaa7a4d3ad.png

Подготовка и подключение к среде займет всего несколько минут. Когда все будет готово, вы должны увидеть что-то вроде этого:

7ffe5cbb04455448.png

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Всю работу в этой лабораторной работе можно выполнять с помощью простого браузера.

3. Включите API

App Engine требует API Compute Engine. Убедитесь, что он включен:

gcloud services enable compute.googleapis.com

Вы должны увидеть успешное завершение операции:

Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.

4. Клонируйте код

Ознакомьтесь с кодом, если вы еще этого не сделали:

git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop

Затем вы можете перейти в каталог, содержащий интерфейс:

cd serverless-photosharing-workshop/frontend

У вас будет следующий макет файла для внешнего интерфейса:

frontend
 |
 ├── index.js
 ├── package.json
 ├── app.yaml
 |
 ├── public
      |
      ├── index.html
      ├── collage.html
      ├── upload.html
      |
      ├── app.js
      ├── script.js
      ├── style.css

В корне нашего проекта у вас есть 3 файла:

  • index.js содержит код Node.js.
  • package.json определяет зависимости библиотеки
  • app.yaml — файл конфигурации Google App Engine.

public папка содержит статические ресурсы:

  • index.html — это страница, на которой показаны все миниатюры изображений и метки.
  • collage.html показывает коллаж из последних изображений.
  • upload.html содержит форму для загрузки новых изображений.
  • app.js использует Vue.js для заполнения страницы index.html данными.
  • script.js обрабатывает меню навигации и значок «гамбургера» на маленьких экранах.
  • style.css определяет некоторые директивы CSS

5. Изучите код

Зависимости

Файл package.json определяет необходимые зависимости библиотеки:

{
  "name": "frontend",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "@google-cloud/firestore": "^3.4.1",
    "@google-cloud/storage": "^4.0.0",
    "express": "^4.16.4",
    "dayjs": "^1.8.22",
    "bluebird": "^3.5.0",
    "express-fileupload": "^1.1.6"
  }
}

Наше приложение зависит от:

  • firestore : для доступа к Cloud Firestore с метаданными наших изображений,
  • хранилище : для доступа к облачному хранилищу Google, где хранятся изображения,
  • express : веб-фреймворк для Node.js,
  • dayjs : небольшая библиотека для удобного отображения дат,
  • bluebird : библиотека обещаний JavaScript,
  • express-fileupload : библиотека для простой обработки загрузки файлов.

Экспресс-интерфейс

В начале контроллера index.js вам потребуются все зависимости, определенные ранее в package.json :

const express = require('express');
const fileUpload = require('express-fileupload');
const Firestore = require('@google-cloud/firestore');
const Promise = require("bluebird");
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const path = require('path');
const dayjs = require('dayjs');
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)

Затем создается экземпляр приложения Express.

Используются два промежуточных программного обеспечения Express:

  • Вызов express.static() указывает, что статические ресурсы будут доступны в public подкаталоге.
  • А fileUpload() настраивает загрузку файлов так, чтобы размер файла был ограничен 10 МБ, чтобы загружать файлы локально в файловую систему в памяти в каталоге /tmp .
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
    limits: { fileSize: 10 * 1024 * 1024 },
    useTempFiles : true,
    tempFileDir : '/tmp/'
}))

Среди статических ресурсов есть HTML-файлы для домашней страницы, страницы коллажей и страницы загрузки. Эти страницы будут вызывать серверную часть API. Этот API будет иметь следующие конечные точки:

  • POST /api/pictures Через форму в upload.html изображения будут загружены с помощью POST-запроса.
  • GET /api/pictures Эта конечная точка возвращает документ JSON, содержащий список изображений и их метки.
  • GET /api/pictures/:name Этот URL-адрес перенаправляет на облачное хранилище полноразмерного изображения.
  • GET /api/thumbnails/:name Этот URL-адрес перенаправляет в облачное хранилище миниатюры изображения.
  • GET /api/collage Этот последний URL-адрес перенаправляет в облачное хранилище сгенерированного изображения коллажа.

Загрузка изображения

Прежде чем изучать код Node.js для загрузки изображений, взгляните на public/upload.html .

... 
<form method="POST" action="/api/pictures" enctype="multipart/form-data">
    ... 
    <input type="file" name="pictures">
    <button>Submit</button>
    ... 
</form>
... 

Элемент формы указывает на конечную точку /api/pictures с помощью метода HTTP POST и многочастного формата. Теперь index.js должен ответить на эту конечную точку и метод и извлечь файлы:

app.post('/api/pictures', async (req, res) => {
    if (!req.files || Object.keys(req.files).length === 0) {
        console.log("No file uploaded");
        return res.status(400).send('No file was uploaded.');
    }
    console.log(`Receiving files ${JSON.stringify(req.files.pictures)}`);

    const pics = Array.isArray(req.files.pictures) ? req.files.pictures : [req.files.pictures];

    pics.forEach(async (pic) => {
        console.log('Storing file', pic.name);
        const newPicture = path.resolve('/tmp', pic.name);
        await pic.mv(newPicture);

        const pictureBucket = storage.bucket(process.env.BUCKET_PICTURES);
        await pictureBucket.upload(newPicture, { resumable: false });
    });


    res.redirect('/');
});

Сначала вы проверяете, действительно ли загружаются файлы. Затем вы загружаете файлы локально с помощью метода mv , полученного из нашего модуля Node для загрузки файлов. Теперь, когда файлы доступны в локальной файловой системе, вы загружаете изображения в корзину Cloud Storage. Наконец, вы перенаправляете пользователя обратно на главный экран приложения.

Перечисление фотографий

Пришло время показать ваши красивые фотографии!

В обработчике /api/pictures вы просматриваете коллекцию pictures базы данных Firestore, чтобы получить все изображения (миниатюры которых были созданы), упорядоченные по убыванию даты создания.

Вы помещаете каждое изображение в массив JavaScript с его именем, описывающими его метками (поступающими из API Cloud Vision), доминирующим цветом и удобной датой создания (с помощью dayjs мы соотносим временные смещения, например «через 3 дня». " ).

app.get('/api/pictures', async (req, res) => {
    console.log('Retrieving list of pictures');

    const thumbnails = [];
    const pictureStore = new Firestore().collection('pictures');
    const snapshot = await pictureStore
        .where('thumbnail', '==', true)
        .orderBy('created', 'desc').get();

    if (snapshot.empty) {
        console.log('No pictures found');
    } else {
        snapshot.forEach(doc => {
            const pic = doc.data();
            thumbnails.push({
                name: doc.id,
                labels: pic.labels,
                color: pic.color,
                created: dayjs(pic.created.toDate()).fromNow()
            });
        });
    }
    console.table(thumbnails);
    res.send(thumbnails);
});

Этот контроллер возвращает результаты следующей формы:

[
   {
      "name": "IMG_20180423_163745.jpg",
      "labels": [
         "Dish",
         "Food",
         "Cuisine",
         "Ingredient",
         "Orange chicken",
         "Produce",
         "Meat",
         "Staple food"
      ],
      "color": "#e78012",
      "created": "a day ago"
   },
   ...
]

Эта структура данных используется небольшим фрагментом Vue.js со страницы index.html . Вот упрощенная версия разметки с этой страницы:

<div id="app">
        <div class="container" id="app">
                <div id="picture-grid">
                        <div class="card" v-for="pic in pictures">
                                <div class="card-content">
                                        <div class="content">
                                                <div class="image-border" :style="{ 'border-color': pic.color }">
                                                        <a :href="'/api/pictures/' + pic.name">
                                                                <img :src="'/api/thumbnails/' + pic.name">
                                                        </a>
                                                </div>
                                                <a class="panel-block" v-for="label in pic.labels" :href="'/?q=' + label">
                                                        <span class="panel-icon">
                                                                <i class="fas fa-bookmark"></i> &nbsp;
                                                        </span>
                                                        {{ label }}
                                                </a>
                                        </div>
                                </div>
                        </div>
            </div>
        </div>
</div>

Идентификатор div будет указывать Vue.js, что эта часть разметки будет динамически отображаться. Итерации выполняются благодаря директивам v-for .

Изображения получают красивую цветную рамку, соответствующую доминирующему цвету изображения, как обнаружено Cloud Vision API, и мы указываем на миниатюры и полноразмерные изображения в ссылках и источниках изображений.

Наконец, мы перечисляем метки, описывающие картинку.

Вот код JavaScript для фрагмента Vue.js (в файле public/app.js , импортированном внизу страницы index.html ):

var app = new Vue({
  el: '#app',
  data() {
    return { pictures: [] }
  },
  mounted() {
    axios
      .get('/api/pictures')
      .then(response => { this.pictures = response.data })
  }
})

Код Vue использует библиотеку Axios для выполнения AJAX-вызова нашей конечной точки /api/pictures . Возвращенные данные затем привязываются к коду представления в разметке, которую вы видели ранее.

Просмотр фотографий

Из index.html наши пользователи могут просматривать миниатюры изображений, нажимать на них, чтобы просмотреть полноразмерные изображения, а из collage.html пользователи просматривают изображение collage.png .

В HTML-разметке этих страниц изображение src и ссылка href указывают на эти три конечные точки, которые перенаправляют в облачное хранилище изображений, миниатюр и коллажей. Нет необходимости жестко прописывать путь в разметке HTML.

app.get('/api/pictures/:name', async (req, res) => {
    res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_PICTURES}/${req.params.name}`);
});

app.get('/api/thumbnails/:name', async (req, res) => {
    res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/${req.params.name}`);
});

app.get('/api/collage', async (req, res) => {
    res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/collage.png`);
});

Запуск приложения Node

Когда все конечные точки определены, ваше приложение Node.js готово к запуску. Приложение Express по умолчанию прослушивает порт 8080 и готово обслуживать входящие запросы.

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {
    console.log(`Started web frontend service on port ${PORT}`);
    console.log(`- Pictures bucket = ${process.env.BUCKET_PICTURES}`);
    console.log(`- Thumbnails bucket = ${process.env.BUCKET_THUMBNAILS}`);
});

6. Тестируйте локально

Перед развертыванием в облаке протестируйте код локально, чтобы убедиться, что он работает.

Вам необходимо экспортировать две переменные среды, соответствующие двум сегментам Cloud Storage:

export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT}
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Внутри папки frontend установите зависимости npm и запустите сервер:

npm install; npm start

Если все прошло успешно, сервер должен запуститься на порту 8080:

Started web frontend service on port 8080
- Pictures bucket = uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
- Thumbnails bucket = thumbnails-${GOOGLE_CLOUD_PROJECT}

Настоящие имена ваших сегментов появятся в этих журналах, что полезно для целей отладки.

В Cloud Shell вы можете использовать функцию веб-предварительного просмотра для просмотра приложения, работающего локально:

82fa3266d48c0d0a.png

Используйте CTRL-C для выхода.

7. Развертывание в App Engine

Ваше приложение готово к развертыванию.

Настройка App Engine

Изучите файл конфигурации app.yaml для App Engine:

runtime: nodejs16
env_variables:
  BUCKET_PICTURES: uploaded-pictures-GOOGLE_CLOUD_PROJECT
  BUCKET_THUMBNAILS: thumbnails-GOOGLE_CLOUD_PROJECT

В первой строке объявляется, что среда выполнения основана на Node.js 10. Определены две переменные среды, указывающие на два сегмента: для исходных изображений и для миниатюр.

Чтобы заменить GOOGLE_CLOUD_PROJECT на фактический идентификатор проекта, вы можете запустить следующую команду:

sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml

Развертывать

Укажите предпочтительный регион для App Engine. Обязательно используйте тот же регион, что и в предыдущих лабораторных работах:

gcloud config set compute/region europe-west1

И развернуть:

gcloud app deploy

Через минуту-две вам сообщат, что приложение обслуживает трафик:

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 8 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://GOOGLE_CLOUD_PROJECT.appspot.com]
You can stream logs from the command line by running:
  $ gcloud app logs tail -s default
To view your application in the web browser run:
  $ gcloud app browse

Вы также можете посетить раздел App Engine в Cloud Console, чтобы увидеть, что приложение развернуто, и изучить такие функции App Engine, как управление версиями и разделение трафика:

db0e196b00fceab1.png

8. Проверьте приложение

Чтобы протестировать, перейдите по URL-адресу App Engine по умолчанию для приложения ( https://<YOUR_PROJECT_ID>.appspot.com/ ), и вы увидите работающий интерфейс интерфейса!

6a4d5e5603ba4b73.png

9. Очистка (необязательно)

Если вы не собираетесь сохранять приложение, вы можете очистить ресурсы, чтобы сэкономить средства и стать в целом хорошим гражданином облака, удалив весь проект:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

10. Поздравляем!

Поздравляем! Это веб-приложение Node.js, размещенное на App Engine, объединяет все ваши сервисы и позволяет вашим пользователям загружать и визуализировать изображения.

Что мы рассмотрели

  • Механизм приложений
  • Облачное хранилище
  • Облачный пожарный магазин

Следующие шаги