1. Omówienie
W ramach tego ćwiczenia w Codelabs utworzysz frontend internetowy w Google App Engine, który pozwoli użytkownikom przesyłać zdjęcia z aplikacji internetowej, a także przeglądać przesłane obrazy i ich miniatury.
Ta aplikacja internetowa będzie korzystać ze platformy CSS o nazwie Bulma, aby uzyskać atrakcyjny interfejs, oraz ze platformy frontendu JavaScript Vue.JS do wywoływania tworzonego przez Ciebie interfejsu API aplikacji.
Ta aplikacja składa się z 3 kart:
- Strona główna, na której będą wyświetlane miniatury wszystkich przesłanych obrazów wraz z listą etykiet opisujących zdjęcie (tych wykrytych przez interfejs Cloud Vision API w poprzednim module).
- Strona kolażu z kolażem utworzonym z 4 ostatnio przesłanych zdjęć.
- Strona przesyłania, na którą użytkownicy mogą przesyłać nowe zdjęcia.
Otrzymany frontend będzie wyglądał tak:
Te 3 strony to proste strony HTML:
- Strona główna (
index.html
) wywołuje kod backendu Node App Engine, aby pobrać listę miniatur obrazów i ich etykiety przez wywołanie AJAX w adresie URL/api/pictures
. Strona główna korzysta z kodu Vue.js do pobierania tych danych. - Strona kolaż (
collage.html
) wskazuje obrazcollage.png
, z którego składa się 4 najnowsze obrazy. - Na stronie przesyłania (
upload.html
) znajduje się prosty formularz przesyłania zdjęć za pomocą żądania POST na adres URL usługi/api/pictures
.
Czego się nauczysz
- App Engine
- Cloud Storage
- Cloud Firestore
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.
- Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google i w każdej chwili możesz go zaktualizować.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i nie można go zmienić (nie można go zmienić po ustawieniu). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń w Codelabs musisz odwoływać się do identyfikatora projektu (który zwykle nazywa się
PROJECT_ID
), więc jeśli Ci się nie podoba, wygeneruj kolejny losowy kod lub wypróbuj swój własny i sprawdź, czy jest dostępny. Potem urządzenie jest „zawieszone”. po utworzeniu projektu. - Występuje trzecia wartość – numer projektu – używany przez niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w konsoli Cloud, aby móc korzystać z zasobów i interfejsów API Cloud. Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Aby wyłączyć zasoby, aby nie naliczać opłat po zakończeniu tego samouczka, wykonaj czynności „wyczyść” znajdziesz na końcu tego ćwiczenia. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.
Uruchamianie Cloud Shell
Google Cloud można obsługiwać zdalnie z laptopa, ale w ramach tego ćwiczenia z programowania wykorzystasz Google Cloud Shell – środowisko wiersza poleceń działające w Cloud.
W konsoli Google Cloud kliknij ikonę Cloud Shell na górnym pasku narzędzi:
Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno pojawić się coś takiego:
Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, znacząco zwiększając wydajność sieci i uwierzytelnianie. Wszystkie zadania w tym module możesz wykonać w przeglądarce.
3. Włącz interfejsy API
App Engine wymaga interfejsu Compute Engine API. Upewnij się, że jest włączona:
gcloud services enable compute.googleapis.com
Operacja powinna zakończyć się pomyślnie:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Klonowanie kodu
Sprawdź kod, jeśli jeszcze nie został:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Następnie możesz przejść do katalogu zawierającego frontend:
cd serverless-photosharing-workshop/frontend
Frontend będzie mieć następujący układ pliku:
frontend | ├── index.js ├── package.json ├── app.yaml | ├── public | ├── index.html ├── collage.html ├── upload.html | ├── app.js ├── script.js ├── style.css
Głównym elementem naszego projektu są 3 pliki:
index.js
zawiera kod Node.jspackage.json
definiuje zależności bibliotekiapp.yaml
to plik konfiguracji Google App Engine
Folder public
zawiera zasoby statyczne:
index.html
to strona z wszystkimi miniaturami i etykietamicollage.html
pokazuje kolaż najnowszych zdjęćupload.html
zawiera formularz do przesyłania nowych zdjęćapp.js
używa Vue.js do wypełniania danymi na stronieindex.html
script.js
obsługuje menu nawigacyjne i „hamburger” na małych ekranachstyle.css
definiuje niektóre dyrektywy CSS
5. Zapoznaj się z kodem
Zależności
Plik package.json
określa potrzebne zależności biblioteki:
{
"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"
}
}
Działanie aplikacji zależy od tych czynników:
- firestore: aby uzyskać dostęp do Cloud Firestore za pomocą metadanych obrazu,
- storage: aby uzyskać dostęp do Google Cloud Storage, gdzie są przechowywane zdjęcia,
- express: platforma internetowa dla Node.js,
- dayjs: mała biblioteka do wyświetlania dat w sposób zrozumiały dla człowieka.
- bluebird: biblioteka JavaScriptu do celów,
- express-fileupload: biblioteka do łatwego przesyłania plików.
Express frontend
Na początku kontrolera index.js
musisz wymagać wszystkich zależności zdefiniowanych wcześniej w narzędziu 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)
Następnie zostanie utworzona instancja aplikacji Express.
Wykorzystano 2 szybkie programy pośredniczące:
- Wywołanie
express.static()
wskazuje, że w podkatalogupublic
będą dostępne zasoby statyczne. fileUpload()
konfiguruje przesyłanie plików tak, aby ich rozmiar nie przekraczał 10 MB, co spowoduje przesłanie ich lokalnie w systemie plików pamięci, w katalogu/tmp
.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
limits: { fileSize: 10 * 1024 * 1024 },
useTempFiles : true,
tempFileDir : '/tmp/'
}))
Wśród zasobów statycznych są pliki HTML strony głównej, strony z kolażem i strony przesyłania. Strony te będą wywoływać backend interfejsu API. Ten interfejs API będzie mieć następujące punkty końcowe:
POST /api/pictures
Za pomocą formularza w load.html zdjęcia zostaną przesłane za pomocą żądania POSTGET /api/pictures
Ten punkt końcowy zwraca dokument JSON zawierający listę obrazów i ich etykietyGET /api/pictures/:name
Ten adres URL przekierowuje do lokalizacji w chmurze, w której znajduje się pełnowymiarowy obrazGET /api/thumbnails/:name
Ten adres URL przekierowuje do lokalizacji, w której znajduje się obraz miniatury w chmurze.GET /api/collage
Ten ostatni adres URL przekierowuje do lokalizacji w chmurze, do której został wygenerowany obraz kolażu
Prześlij zdjęcie
Zanim zapoznasz się ze zdjęciami i przesłaniem kodu Node.js, spójrz na stronę public/upload.html
.
...
<form method="POST" action="/api/pictures" enctype="multipart/form-data">
...
<input type="file" name="pictures">
<button>Submit</button>
...
</form>
...
Element formularza wskazuje punkt końcowy /api/pictures
z metodą HTTP POST i formatem wieloczęściowym. index.js
musi teraz odpowiedzieć na ten punkt końcowy i metodę oraz wyodrębnić pliki:
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('/');
});
Najpierw sprawdź, czy rzeczywiście jakieś pliki zostały przesłane. Następnie pobierasz pliki lokalnie za pomocą metody mv
pochodzącej z naszego modułu węzła do przesyłania plików. Gdy pliki są już dostępne w lokalnym systemie plików, możesz przesłać je do zasobnika Cloud Storage. Na koniec przekierowujesz użytkownika z powrotem na ekran główny aplikacji.
Wyświetlanie listy zdjęć
Czas pokazać Twoje piękne zdjęcia.
W module obsługi /api/pictures
możesz zajrzeć do kolekcji pictures
bazy danych Firestore, aby pobrać wszystkie obrazy (które zostały wygenerowane przez miniaturę) uporządkowane według daty utworzenia w kolejności malejącej.
Każdy obraz jest przekazywany w tablicy JavaScriptu z jego nazwą, opisami etykiet (pochodzącymi z Cloud Vision API), kolorem dominującym i przyjazną datą utworzenia (dayjs
określa względne przesunięcia czasu, np. „3 dni od teraz”).
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);
});
Ten kontroler zwraca wyniki o tym kształcie:
[
{
"name": "IMG_20180423_163745.jpg",
"labels": [
"Dish",
"Food",
"Cuisine",
"Ingredient",
"Orange chicken",
"Produce",
"Meat",
"Staple food"
],
"color": "#e78012",
"created": "a day ago"
},
...
]
Ta struktura danych jest wykorzystywana przez mały fragment kodu Vue.js ze strony index.html
. Oto uproszczona wersja znaczników z tej strony:
<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>
Identyfikator tagu div wskazuje Vue.js, że jest to część znacznika, która będzie renderowana dynamicznie. Iteracje są wykonywane zgodnie z dyrektywami v-for
.
Znalezione przez Cloud Vision API obramowanie zdjęć ma ładne kolorowe obramowanie zgodne z kolorem dominującym na zdjęciu. W źródłach linków i obrazów wskazujemy miniatury i zdjęcia o pełnej szerokości.
Na koniec podajemy etykiety opisujące obraz.
Oto kod JavaScript fragmentu kodu Vue.js (w pliku public/app.js
zaimportowanym u dołu strony index.html
):
var app = new Vue({
el: '#app',
data() {
return { pictures: [] }
},
mounted() {
axios
.get('/api/pictures')
.then(response => { this.pictures = response.data })
}
})
Kod Vue korzysta z biblioteki Axios do wywołania AJAX do naszego punktu końcowego /api/pictures
. Zwrócone dane są następnie powiązane z kodem widoku w zaobserwowanych wcześniej znacznikach.
Wyświetlanie zdjęć
Od index.html
użytkownicy mogą wyświetlać miniatury zdjęć, klikać je, by zobaczyć obrazy w pełnym rozmiarze, a następnie collage.html
wyświetlać obraz collage.png
.
W znacznikach HTML tych stron obraz src
i link href
wskazują te 3 punkty końcowe, które przekierowują do lokalizacji zdjęć, miniatur i kolażu w Cloud Storage. Nie ma potrzeby kodowania ścieżki na stałe w znacznikach 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`);
});
Uruchamianie aplikacji węzła
Po zdefiniowaniu wszystkich punktów końcowych aplikacja Node.js jest gotowa do uruchomienia. Aplikacja Express nasłuchuje domyślnie na porcie 8080 i jest gotowa do obsługi żądań przychodzących.
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. Przetestuj lokalnie
Przed wdrożeniem kodu w chmurze przetestuj go lokalnie, aby upewnić się, że działa.
Musisz wyeksportować 2 zmienne środowiskowe odpowiadające 2 zasobnikom Cloud Storage:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
W folderze frontend
zainstaluj zależności npm i uruchom serwer:
npm install; npm start
Jeśli wszystko poszło dobrze, serwer powinien uruchamiać się na porcie 8080:
Started web frontend service on port 8080 - Pictures bucket = uploaded-pictures-${GOOGLE_CLOUD_PROJECT} - Thumbnails bucket = thumbnails-${GOOGLE_CLOUD_PROJECT}
W tych logach będą wyświetlane prawdziwe nazwy zasobników, co jest pomocne przy debugowaniu.
W Cloud Shell możesz użyć funkcji podglądu w przeglądarce, aby przejrzeć aplikację działającą lokalnie:
Użyj CTRL-C
, aby wyjść.
7. Wdrażanie w App Engine
Aplikacja jest gotowa do wdrożenia.
Skonfiguruj App Engine
Sprawdź plik konfiguracji app.yaml
dla App Engine:
runtime: nodejs16 env_variables: BUCKET_PICTURES: uploaded-pictures-GOOGLE_CLOUD_PROJECT BUCKET_THUMBNAILS: thumbnails-GOOGLE_CLOUD_PROJECT
W pierwszym wierszu widać, że środowisko wykonawcze opiera się na Node.js 10. Zdefiniowano dwie zmienne środowiskowe, które wskazują 2 segmenty – oryginalne obrazy i miniatury.
Aby zastąpić GOOGLE_CLOUD_PROJECT
rzeczywistym identyfikatorem projektu, możesz uruchomić to polecenie:
sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml
Wdróż
Ustaw preferowany region dla App Engine i użyj tego samego regionu w poprzednich modułach:
gcloud config set compute/region europe-west1
I wdróż:
gcloud app deploy
Po kilku minutach zobaczysz informację, że aplikacja obsługuje ruch:
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
Możesz też otworzyć sekcję App Engine w konsoli Cloud, aby zobaczyć, czy aplikacja została wdrożona, i poznać funkcje App Engine, takie jak obsługa wersji i dzielenie ruchu:
8. Testowanie aplikacji
Aby przetestować, otwórz domyślny URL App Engine aplikacji (https://<YOUR_PROJECT_ID>.appspot.com/
). Interfejs frontendu powinien działać.
9. Czyszczenie (opcjonalnie)
Jeśli nie chcesz zachować aplikacji, możesz zwolnić zasoby, aby ograniczyć koszty i zachować zgodność z zasadami dotyczącymi chmury, usuwając cały projekt:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
10. Gratulacje!
Gratulacje! Ta aplikacja internetowa Node.js hostowana w App Engine łączy wszystkie usługi i umożliwia użytkownikom przesyłanie i wizualizowanie obrazów.
Omówione zagadnienia
- App Engine
- Cloud Storage
- Cloud Firestore