Pic-a-daily: Lab 4 – Web-Front-End erstellen

1. Übersicht

In diesem Codelab erstellen Sie ein Web-Front-End in Google App Engine, mit dem Nutzer Bilder aus der Webanwendung hochladen und die hochgeladenen Bilder sowie deren Miniaturansichten durchsuchen können.

21741cd63b425aeb.png

Für diese Webanwendung wird das CSS-Framework Bulma verwendet, um eine ansprechende Benutzeroberfläche zu bieten. Außerdem wird das JavaScript-Frontend-Framework Vue.JS verwendet, um die von Ihnen erstellte API der Anwendung aufzurufen.

Diese Anwendung besteht aus drei Registerkarten:

  • Eine Startseite mit den Miniaturansichten aller hochgeladenen Bilder sowie der Liste der Labels, die das Bild beschreiben (die von der Cloud Vision API in einem vorherigen Lab erkannt wurden).
  • Eine Collagenseite, auf der eine Collage aus den vier zuletzt hochgeladenen Bildern angezeigt wird.
  • Eine Uploadseite, auf der Nutzer neue Bilder hochladen können

Das resultierende Front-End sieht so aus:

6a4d5e5603ba4b73.png

Diese drei Seiten sind einfache HTML-Seiten:

  • Die Startseite (index.html) ruft den Node App Engine-Back-End-Code auf, um die Liste der Miniaturansichten und ihrer Labels über einen AJAX-Aufruf an die URL /api/pictures abzurufen. Die Startseite verwendet zum Abrufen dieser Daten Vue.js.
  • Die Seite Collage (collage.html) zeigt auf das Bild collage.png, aus dem die vier neuesten Bilder zusammengesetzt sind.
  • Die Upload-Seite (upload.html) enthält ein einfaches Formular, mit dem ein Bild per POST-Anfrage an die URL /api/pictures hochgeladen werden kann.

Lerninhalte

  • App Engine
  • Cloud Storage
  • Cloud Firestore

2. Einrichtung und Anforderungen

Umgebung für das selbstbestimmte Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Der Projektname ist der Anzeigename für die Projektteilnehmer. Es handelt sich um eine Zeichenfolge, die von Google APIs nicht verwendet wird und jederzeit aktualisiert werden kann.
  • Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich. Sie kann nach dem Festlegen nicht mehr geändert werden. Die Cloud Console generiert automatisch einen eindeutigen String. ist Ihnen meist egal, was es ist. In den meisten Codelabs musst du auf die Projekt-ID verweisen, die in der Regel als PROJECT_ID identifiziert wird. Wenn es dir nicht gefällt, kannst du eine weitere zufällige Projekt-ID generieren. Du kannst aber auch selbst eine andere testen, um zu sehen, ob sie verfügbar ist. Dann ist es „eingefroren“ nachdem das Projekt erstellt wurde.
  • Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu allen drei Werten finden Sie in der Dokumentation.
  1. Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/APIs verwenden zu können. Dieses Codelab sollte ohne großen Aufwand betrieben werden. Wenn Sie Ressourcen beenden möchten, damit über diese Anleitung hinaus keine Kosten anfallen, führen Sie eine Bereinigung durch am Ende des Codelabs. Neue Google Cloud-Nutzer haben Anspruch auf eine kostenlose Testversion von 300$.

Cloud Shell starten

Sie können Google Cloud zwar von Ihrem Laptop aus aus der Ferne bedienen, in diesem Codelab verwenden Sie jedoch Google Cloud Shell, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.

Klicken Sie in der Google Cloud Console rechts oben in der Symbolleiste auf das Cloud Shell-Symbol:

55efc1aaa7a4d3ad.png

Die Bereitstellung und Verbindung mit der Umgebung dauert nur einen Moment. Wenn er abgeschlossen ist, sollten Sie in etwa Folgendes sehen:

7ffe5cbb04455448.png

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft auf Google Cloud, wodurch die Netzwerkleistung und Authentifizierung erheblich verbessert werden. Sie können alle Aufgaben in diesem Lab ganz einfach in einem Browser erledigen.

3. APIs aktivieren

App Engine erfordert die Compute Engine API. Prüfen Sie, ob sie aktiviert ist:

gcloud services enable compute.googleapis.com

Der Vorgang sollte erfolgreich abgeschlossen sein:

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

4. Code klonen

Überprüfen Sie den Code, falls noch nicht geschehen:

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

Sie können dann zum Verzeichnis mit dem Front-End wechseln:

cd serverless-photosharing-workshop/frontend

Sie haben das folgende Dateilayout für das Front-End:

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

Im Stammverzeichnis unseres Projekts befinden sich drei Dateien:

  • index.js enthält den Node.js-Code.
  • package.json definiert die Bibliotheksabhängigkeiten.
  • app.yaml ist die Konfigurationsdatei für Google App Engine.

Ein public-Ordner enthält die statischen Ressourcen:

  • index.html ist die Seite mit allen Miniaturansichten und Labels.
  • collage.html zeigt die Collage aus den letzten Bildern
  • upload.html enthält ein Formular zum Hochladen neuer Bilder
  • app.js verwendet Vue.js, um die Seite index.html mit Daten zu füllen
  • script.js übernimmt das Navigationsmenü und den zugehörigen „Hamburger“ Symbol auf kleinen Bildschirmen
  • style.css definiert einige CSS-Anweisungen

5. Code ansehen

Abhängigkeiten

Die Datei package.json definiert die erforderlichen Bibliotheksabhängigkeiten:

{
  "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"
  }
}

Unsere Bewerbung hängt von folgenden Faktoren ab:

  • firestore: für den Zugriff auf Cloud Firestore mit unseren Bildmetadaten
  • storage: Auf Google Cloud Storage zugreifen, in dem Bilder gespeichert sind
  • express: das Web-Framework für Node.js
  • dayjs: eine kleine Bibliothek, in der Datumsangaben übersichtlich dargestellt werden.
  • bluebird: eine JavaScript-Promise-Bibliothek
  • express-fileupload: eine Bibliothek zur einfachen Bearbeitung von Dateiuploads.

Express-Frontend

Am Anfang des index.js-Controllers benötigen Sie alle Abhängigkeiten, die zuvor in package.json definiert wurden:

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)

Als Nächstes wird die Express-Anwendungsinstanz erstellt.

Es werden zwei Express-Middleware verwendet:

  • Der express.static()-Aufruf gibt an, dass statische Ressourcen im Unterverzeichnis public verfügbar sind.
  • Und fileUpload() konfiguriert den Datei-Upload so, dass die Dateigröße auf 10 MB begrenzt wird, um die Dateien lokal in das speicherinterne Dateisystem im Verzeichnis /tmp hochzuladen.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
    limits: { fileSize: 10 * 1024 * 1024 },
    useTempFiles : true,
    tempFileDir : '/tmp/'
}))

Zu den statischen Ressourcen gehören die HTML-Dateien für die Startseite, die Collagenseite und die Upload-Seite. Diese Seiten rufen das API-Back-End auf. Diese API hat die folgenden Endpunkte:

  • POST /api/pictures Bilder werden über das Formular in upload.html per POST-Anfrage hochgeladen.
  • GET /api/pictures Dieser Endpunkt gibt ein JSON-Dokument mit der Liste der Bilder und deren Labels zurück
  • GET /api/pictures/:name Diese URL leitet zum Cloud Storage-Speicherort des Bildes in voller Größe weiter.
  • GET /api/thumbnails/:name Diese URL leitet zum Cloud-Speicherort des Thumbnail-Bildes weiter.
  • GET /api/collage Diese letzte URL leitet zum Cloud-Speicherort des generierten Collagenbilds weiter.

Bilder hochladen

Bevor Sie sich mit dem Hochladen von Node.js-Code für Bilder befassen, werfen Sie einen Blick auf public/upload.html.

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

Das Formularelement verweist mit einer HTTP-POST-Methode und einem mehrteiligen Format auf den Endpunkt /api/pictures. index.js muss jetzt auf diesen Endpunkt und diese Methode antworten und die Dateien extrahieren:

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('/');
});

Zuerst prüfen Sie, ob tatsächlich Dateien hochgeladen werden. Anschließend laden Sie die Dateien lokal über die Methode mv aus unserem Dateiupload-Knotenmodul herunter. Nachdem die Dateien im lokalen Dateisystem verfügbar sind, laden Sie die Bilder in den Cloud Storage-Bucket hoch. Schließlich leiten Sie die Nutzenden zurück zum Hauptbildschirm der Anwendung.

Bilder auflisten

Zeit, Ihre schönen Bilder zu präsentieren!

Im /api/pictures-Handler sehen Sie sich die pictures-Sammlung der Firestore-Datenbank an, um alle Bilder abzurufen, deren Miniaturansicht generiert wurde. Sie sind nach absteigendem Erstellungsdatum sortiert.

Sie übertragen jedes Bild in einem JavaScript-Array, mit dem Namen, den Labels, die es beschreiben (aus der Cloud Vision API), der dominanten Farbe und einem nutzerfreundlichen Erstellungsdatum. Mit dayjs wird die relative Zeitverschiebung wie „3 Tage ab heute“ angegeben.

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

Dieser Controller gibt Ergebnisse der folgenden Form zurück:

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

Diese Datenstruktur wird von einem kleinen Vue.js-Snippet auf der index.html-Seite verarbeitet. Hier ist eine vereinfachte Version des Markups von dieser Seite:

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

Die ID des div-Elements gibt an, dass Vue.js der Teil des Markups ist, der dynamisch gerendert wird. Die Iterationen erfolgen mithilfe der v-for-Anweisungen.

Die Bilder erhalten einen farbigen Rahmen, der der dominanten Farbe auf dem Bild entspricht, wie sie von der Cloud Vision API gefunden wurde. Wir zeigen auf die Miniaturansichten und die Bilder mit voller Breite in den Links und Bildquellen.

Als Letztes listen wir die Labels auf, die das Bild beschreiben.

Hier ist der JavaScript-Code für das Vue.js-Snippet (in der public/app.js-Datei, die unten auf der index.html-Seite importiert wird):

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

Der Vue-Code verwendet die Axios-Bibliothek, um einen AJAX-Aufruf an unseren /api/pictures-Endpunkt zu senden. Die zurückgegebenen Daten werden dann an den Ansichtscode im Markup gebunden, das Sie zuvor gesehen haben.

Anzeigen der Bilder

Unter index.html können unsere Nutzer die Miniaturansichten der Bilder ansehen, auf sie klicken, um sie in voller Größe anzuzeigen. Unter collage.html sehen Nutzer das Bild collage.png.

Im HTML-Markup dieser Seiten verweisen das Bild src und der Link href auf diese drei Endpunkte, die zu den Cloud Storage-Speicherorten der Bilder, Miniaturansichten und Collagen weiterleiten. Der Pfad muss im HTML-Markup nicht hartcodiert werden.

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

Knotenanwendung ausführen

Wenn alle Endpunkte definiert sind, kann Ihre Node.js-Anwendung gestartet werden. Die Express-Anwendung überwacht standardmäßig Port 8080 und kann eingehende Anfragen verarbeiten.

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. Lokal testen

Testen Sie den Code lokal, um sicherzustellen, dass er funktioniert, bevor Sie ihn in der Cloud bereitstellen.

Sie müssen die beiden Umgebungsvariablen exportieren, die den beiden Cloud Storage-Buckets entsprechen:

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

Installieren Sie im Ordner frontend die npm-Abhängigkeiten und starten Sie den Server:

npm install; npm start

Wenn alles gut gelaufen ist, sollte der Server auf Port 8080 gestartet werden:

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

Die echten Namen Ihrer Buckets werden in diesen Logs angezeigt, was für die Fehlerbehebung hilfreich ist.

In Cloud Shell können Sie die Webvorschaufunktion verwenden, um die lokal ausgeführte Anwendung im Browser zu öffnen:

82fa3266d48c0d0a.png

Verwende zum Beenden CTRL-C.

7. Auf der App Engine bereitstellen

Ihre Anwendung kann bereitgestellt werden.

App Engine konfigurieren

Sehen Sie sich die Konfigurationsdatei app.yaml für App Engine an:

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

In der ersten Zeile wird angegeben, dass die Laufzeit auf Node.js 10 basiert. Es sind zwei Umgebungsvariablen definiert, die auf die beiden Buckets verweisen: für die Originalbilder und für die Miniaturansichten.

Führen Sie den folgenden Befehl aus, um GOOGLE_CLOUD_PROJECT durch Ihre tatsächliche Projekt-ID zu ersetzen:

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

Bereitstellen

Legen Sie Ihre bevorzugte Region für App Engine fest und verwenden Sie dieselbe Region wie in den vorherigen Labs:

gcloud config set compute/region europe-west1

Und stellen Sie Folgendes bereit:

gcloud app deploy

Nach ein bis zwei Minuten wird Ihnen mitgeteilt, dass die Anwendung Traffic verarbeitet:

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

Sie können auch den App Engine-Bereich der Cloud Console aufrufen, um zu sehen, ob die Anwendung bereitgestellt wurde, und sich Funktionen von App Engine wie Versionsverwaltung und Traffic-Aufteilung anzusehen:

db0e196b00fceab1.png

8. App testen

Rufen Sie zum Testen die Standard-App Engine-URL für die Anwendung (https://<YOUR_PROJECT_ID>.appspot.com/) auf. Sie sollten nun die Frontend-Benutzeroberfläche sehen und ausführen können.

6a4d5e5603ba4b73.png

9. Bereinigen (optional)

Wenn Sie die Anwendung nicht behalten möchten, können Sie Ressourcen bereinigen, um Kosten zu sparen und die Cloud insgesamt einwandfrei zu gestalten, indem Sie das gesamte Projekt löschen:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

10. Glückwunsch!

Glückwunsch! Diese in App Engine gehostete Node.js-Webanwendung bindet alle Ihre Dienste zusammen und ermöglicht Ihren Nutzern, Bilder hochzuladen und zu visualisieren.

Behandelte Themen

  • App Engine
  • Cloud Storage
  • Cloud Firestore

Nächste Schritte