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

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

О практической работе

subjectПоследнее обновление: апр. 14, 2022
account_circleАвторы: Guillaume Laforge, Mete Atamel

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 с его именем, описывающими его метками (поступающими из Cloud Vision API), доминирующим цветом и удобной датой создания (с помощью 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, объединяет все ваши сервисы и позволяет вашим пользователям загружать и визуализировать изображения.

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

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

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