1. Übersicht
In diesem Codelab erstellen Sie ein Web-Frontend in Google App Engine, mit dem Nutzer Bilder aus der Webanwendung hochladen und die hochgeladenen Bilder und ihre Miniaturansichten durchsuchen können.

Für diese Webanwendung wird ein CSS-Framework namens Bulma verwendet, um eine ansprechende Benutzeroberfläche zu erstellen. Außerdem wird das JavaScript-Frontend-Framework Vue.JS verwendet, um die API der Anwendung aufzurufen, die Sie erstellen werden.
Diese Anwendung besteht aus drei Tabs:
- Eine Startseite, auf der die Thumbnails aller hochgeladenen Bilder zusammen mit der Liste der Labels angezeigt werden, die das Bild beschreiben (die von der Cloud Vision API in einem vorherigen Lab erkannt wurden).
- Eine Collage-Seite mit einer Collage aus den vier zuletzt hochgeladenen Bildern.
- Eine Uploadseite, auf der Nutzer neue Bilder hochladen können.
Das resultierende Frontend sieht so aus:

Diese drei Seiten sind einfache HTML-Seiten:
- Auf der Startseite (
index.html) wird der Node App Engine-Backend-Code aufgerufen, um über einen AJAX-Aufruf der/api/pictures-URL die Liste der Vorschaubilder und deren Labels abzurufen. Auf der Startseite wird Vue.js zum Abrufen dieser Daten verwendet. - Die Seite Collage (
collage.html) verweist auf das Bildcollage.png, das aus den vier neuesten Bildern zusammengesetzt ist. - Auf der Seite upload (
upload.html) finden Sie ein einfaches Formular zum Hochladen eines Bildes über eine POST-Anfrage an die URL/api/pictures.
Lerninhalte
- App Engine
- Cloud Storage
- Cloud Firestore
2. Einrichtung und Anforderungen
Umgebung zum selbstbestimmten Lernen einrichten
- 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.



- Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird und den Sie jederzeit aktualisieren können.
- Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich (kann nach der Festlegung nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser aussieht. In den meisten Codelabs müssen Sie auf die Projekt-ID verweisen (die in der Regel als
PROJECT_IDangegeben wird). Wenn Ihnen die ID nicht gefällt, können Sie eine andere zufällige ID generieren oder eine eigene ID ausprobieren und sehen, ob sie verfügbar ist. Nachdem das Projekt erstellt wurde, wird es „eingefroren“. - Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
- Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs verwenden zu können. Die Durchführung dieses Codelabs sollte keine oder nur geringe Kosten verursachen. Wenn Sie Ressourcen herunterfahren möchten, damit nach Abschluss dieses Codelabs keine Gebühren anfallen, folgen Sie den Bereinigungsanweisungen am Ende des Codelabs. Neue Nutzer von Google Cloud kommen für das Programm für kostenlose Testversionen mit einem Guthaben von 300$ infrage.
Cloud Shell starten
Während Sie Google Cloud von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, 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:

Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern. Anschließend sehen Sie in etwa Folgendes:

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Für dieses Lab benötigen Sie lediglich einen Browser.
3. APIs aktivieren
Für App Engine ist die Compute Engine API erforderlich. Prüfen Sie, ob die Funktion aktiviert ist:
gcloud services enable compute.googleapis.com
Der Vorgang sollte erfolgreich abgeschlossen werden:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Code klonen
Checken Sie den Code aus, falls noch nicht geschehen:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Anschließend können Sie das Verzeichnis mit dem Frontend aufrufen:
cd serverless-photosharing-workshop/frontend
Das Frontend hat die folgende Dateistruktur:
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.jsenthält den Node.js-Code.package.jsondefiniert die Bibliotheksabhängigkeiten.app.yamlist die Konfigurationsdatei für Google App Engine.
Ein public-Ordner enthält die statischen Ressourcen:
index.htmlist die Seite mit allen Thumbnails und Labels.collage.htmlzeigt die Collage der letzten Bilderupload.htmlenthält ein Formular zum Hochladen neuer Bilder.app.jsverwendet Vue.js, um die Seiteindex.htmlmit den Daten zu füllen.script.jsverarbeitet das Navigationsmenü und das zugehörige Dreistrich-Symbol auf kleinen Bildschirmen.style.cssdefiniert einige CSS-Anweisungen.
5. Code ansehen
Abhängigkeiten
In der Datei package.json werden die erforderlichen Bibliotheksabhängigkeiten definiert:
{
"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 Folgendem ab:
- firestore: für den Zugriff auf Cloud Firestore mit unseren Bildmetadaten.
- storage: für den Zugriff auf Google Cloud Storage, wo Bilder gespeichert sind,
- express: das Web-Framework für Node.js,
- dayjs: Eine kleine Bibliothek, mit der sich Datumsangaben in einem für Menschen lesbaren Format anzeigen lassen.
- bluebird: eine JavaScript-Promise-Bibliothek
- express-fileupload: Eine Bibliothek, mit der sich Dateiuploads einfach verarbeiten lassen.
Express-Frontend
Zu Beginn 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-Middlewares verwendet:
- Der Aufruf
express.static()gibt an, dass statische Ressourcen im Unterverzeichnispublicverfügbar sind. - Mit
fileUpload()wird der Dateiupload so konfiguriert, dass die Dateigröße auf 10 MB begrenzt wird und die Dateien lokal im In-Memory-Dateisystem im Verzeichnis/tmphochgeladen werden.
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 Collageseite und die Uploadseite. Auf diesen Seiten wird das API-Back-End aufgerufen. Diese API hat die folgenden Endpunkte:
POST /api/picturesÜber das Formular in „upload.html“ werden Bilder über eine POST-Anfrage hochgeladen.GET /api/picturesDieser Endpunkt gibt ein JSON-Dokument mit der Liste der Bilder und ihrer Labels zurück.GET /api/pictures/:nameDiese URL leitet zum Cloud Storage-Speicherort des Bildes in Originalgröße weiter.GET /api/thumbnails/:nameDiese URL leitet zum Cloud Storage-Speicherort des Miniaturbildes weiter.GET /api/collageDiese letzte URL leitet zum Cloud Storage-Speicherort des generierten Collagebilds weiter.
Bild hochladen
Bevor Sie sich den Node.js-Code zum Hochladen von Bildern ansehen, werfen Sie einen kurzen 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 Multipart-Format auf den Endpunkt /api/pictures. Der index.js muss jetzt auf diesen Endpunkt und diese Methode reagieren 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('/');
});
Prüfen Sie zuerst, ob tatsächlich Dateien hochgeladen werden. Anschließend laden Sie die Dateien lokal über die mv-Methode aus unserem Node-Modul für den Dateiupload herunter. Nachdem die Dateien im lokalen Dateisystem verfügbar sind, laden Sie die Bilder in den Cloud Storage-Bucket hoch. Schließlich leiten Sie den Nutzer zurück zum Hauptbildschirm der Anwendung.
Bilder auflisten
Zeit, deine schönen Bilder zu präsentieren!
Im /api/pictures-Handler sehen Sie sich die Sammlung pictures der Firestore-Datenbank an, um alle Bilder abzurufen, deren Miniaturansicht generiert wurde. Die Bilder sind nach absteigendem Erstellungsdatum sortiert.
Jedes Bild wird in ein JavaScript-Array eingefügt, zusammen mit seinem Namen, den Labels, die es beschreiben (aus der Cloud Vision API), der dominanten Farbe und einem benutzerfreundlichen Erstellungsdatum (mit dayjs werden relative Zeitabweichungen wie „3 Tage ab jetzt“ 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 in folgender 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 Seite index.html verwendet. 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>
</span>
{{ label }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
Die ID des Div-Elements gibt Vue.js an, dass es sich um den Teil des Markups handelt, der dynamisch gerendert wird. Die Iterationen werden durch die v-for-Anweisungen ermöglicht.
Die Bilder erhalten einen farbigen Rahmen, der der dominanten Farbe im Bild entspricht, die von der Cloud Vision API ermittelt wurde. In den Link- und Bildquellen verweisen wir auf die Thumbnails und die Bilder in voller Breite.
Zuletzt werden die Labels aufgeführt, die das Bild beschreiben.
Hier ist der JavaScript-Code für das Vue.js-Snippet (in der Datei public/app.js, die unten auf der Seite index.html importiert wird):
var app = new Vue({
el: '#app',
data() {
return { pictures: [] }
},
mounted() {
axios
.get('/api/pictures')
.then(response => { this.pictures = response.data })
}
})
Im Vue-Code wird die Axios-Bibliothek verwendet, 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.
Bilder ansehen
Über index.html können unsere Nutzer die Thumbnails der Bilder aufrufen und darauf klicken, um die Bilder in voller Größe zu sehen. Über collage.html können Nutzer das Bild collage.png aufrufen.
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. Sie müssen den Pfad nicht im HTML-Markup hartcodieren.
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-Anwendung ausführen
Nachdem alle Endpunkte definiert wurden, kann Ihre Node.js-Anwendung gestartet werden. Die Express-Anwendung überwacht standardmäßig Port 8080 und ist bereit, eingehende Anfragen zu 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 geklappt hat, 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}
In diesen Logs werden die tatsächlichen Namen Ihrer Buckets angezeigt, was für das Debugging hilfreich ist.
In Cloud Shell können Sie die Webvorschau verwenden, um die lokal ausgeführte Anwendung im Browser anzusehen:

Verwenden Sie zum Beenden CTRL-C.
7. Auf der App Engine bereitstellen
Ihre Anwendung kann jetzt bereitgestellt werden.
App Engine konfigurieren
Sehen Sie sich die app.yaml-Konfigurationsdatei 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 deklariert, dass die Laufzeit auf Node.js 10 basiert. Es sind zwei Umgebungsvariablen definiert, die auf die beiden Buckets für die Originalbilder und die Miniaturansichten verweisen.
Wenn Sie GOOGLE_CLOUD_PROJECT durch Ihre tatsächliche Projekt-ID ersetzen möchten, können Sie den folgenden Befehl ausführen:
sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml
Bereitstellen
Legen Sie Ihre bevorzugte Region für App Engine fest. Verwenden Sie dabei dieselbe Region wie in den vorherigen Labs:
gcloud config set compute/region europe-west1
Und so stellen Sie sie bereit:
gcloud app deploy
Nach ein oder 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, dass die App bereitgestellt wurde, und App Engine-Funktionen wie Versionsverwaltung und Trafficaufteilung zu nutzen:

8. Anwendung testen
Rufen Sie zum Testen die App Engine-Standard-URL für die App (https://<YOUR_PROJECT_ID>.appspot.com/) auf. Die Frontend-Benutzeroberfläche sollte nun ausgeführt werden.

9. Bereinigen (optional)
Wenn Sie die App nicht behalten möchten, können Sie Ressourcen bereinigen, um Kosten zu sparen und nicht mehr benötigte Ressourcen für andere freizugeben. Löschen Sie dazu das gesamte Projekt:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
10. Glückwunsch!
Glückwunsch! Diese in App Engine gehostete Node.js-Webanwendung verbindet alle Ihre Dienste und ermöglicht es Ihren Nutzern, Bilder hochzuladen und zu visualisieren.
Behandelte Themen
- App Engine
- Cloud Storage
- Cloud Firestore