Günlük resim: Laboratuvar 4: Web ön ucu oluşturma

1. Genel Bakış

Bu codelab'de, Google App Engine'de kullanıcıların web uygulamasından resim yüklemesine ve yüklenen resimlere ve küçük resimlerine göz atmasına olanak tanıyan bir web ön ucu oluşturacaksınız.

21741cd63b425aeb.png

Bu web uygulaması, iyi görünümlü bir kullanıcı arayüzüne sahip olmak için Bulma adlı bir CSS çerçevesini ve derleyeceğiniz uygulamanın API'sini çağırmak için Vue.JS JavaScript ön uç çerçevesini kullanacaktır.

Bu uygulama üç sekmeden oluşur:

  • Yüklenen tüm görüntülerin küçük resimlerini ve resmi açıklayan etiketlerin listesini (Cloud Vision API tarafından önceki bir laboratuvarda algılananlar) gösterecek bir ana sayfa.
  • Yüklenen en son 4 resimden oluşturulan kolajın gösterildiği bir kolaj sayfası.
  • Kullanıcıların yeni resimler yükleyebileceği bir yükleme sayfası.

Elde edilen ön uç aşağıdaki gibi görünür:

6a4d5e5603ba4b73.png

Bu 3 sayfa basit HTML sayfalarıdır:

  • Ana sayfa (index.html), /api/pictures URL'sine yapılan bir AJAX çağrısıyla küçük resimlerin ve etiketlerinin listesini almak için Node App Engine arka uç kodunu çağırır. Ana sayfada bu verileri getirmek için Vue.js kullanılıyor.
  • Kolaj sayfası (collage.html), en son 4 resmi bir araya getiren collage.png resmini gösterir.
  • Yükleme sayfası (upload.html), bir POST isteği aracılığıyla /api/pictures URL'sine resim yüklemek için basit bir form sunar.

Neler öğreneceksiniz?

  • App Engine
  • Cloud Storage
  • Cloud Firestore

2. Kurulum ve Gereksinimler

Kendi hızınızda ortam kurulumu

  1. Google Cloud Console'da oturum açıp yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Proje adı, bu projenin katılımcıları için görünen addır. Google API'leri tarafından kullanılmayan bir karakter dizesidir ve bunu istediğiniz zaman güncelleyebilirsiniz.
  • Proje Kimliği, tüm Google Cloud projelerinde benzersiz olmalıdır ve değiştirilemez (belirlendikten sonra değiştirilemez). Cloud Console, otomatik olarak benzersiz bir dize oluşturur. bunun ne olduğunu umursamıyorsunuz. Çoğu codelab'de, Proje Kimliğine referans vermeniz gerekir (ve bu kimlik genellikle PROJECT_ID olarak tanımlanır). Beğenmezseniz başka bir rastgele kod oluşturun ya da kendi proje kimliğinizi deneyip mevcut olup olmadığına bakın. Sıcaklık "soğudu" takip etmeniz gerekir.
  • Bazı API'lerin kullandığı üçüncü bir değer, yani Proje Numarası daha vardır. Bu değerlerin üçü hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Ardından, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. Bu eğitim dışında faturalandırmayla karşılaşmamak için kaynakları kapatmak istiyorsanız tüm "temizleme" işlemlerini uygulayın buradaki talimatları uygulayın. Yeni Google Cloud kullanıcıları, 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Cloud Shell'i başlatma

Google Cloud dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de bu codelab'de, Cloud'da çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacaksınız.

Google Cloud Console'da, sağ üstteki araç çubuğunda bulunan Cloud Shell simgesini tıklayın:

55efc1aaa7a4d3ad.png

Ortamı sağlamak ve bağlamak yalnızca birkaç dakika sürer. Tamamlandığında şuna benzer bir sonuç görmeniz gerekir:

7ffe5cbb04455448

İhtiyacınız olan tüm geliştirme araçlarını bu sanal makinede bulabilirsiniz. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu laboratuvardaki tüm çalışmalarınızı yalnızca bir tarayıcıyla yapabilirsiniz.

3. API'leri etkinleştir

App Engine için Compute Engine API gerekir. Etkin olduğundan emin olun:

gcloud services enable compute.googleapis.com

İşlemin başarıyla tamamlandığını göreceksiniz:

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

4. Kodu klonlama

Henüz yapmadıysanız kodu kontrol edin:

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

Ardından ön ucu içeren dizine gidebilirsiniz:

cd serverless-photosharing-workshop/frontend

Ön uç için aşağıdaki dosya düzenine sahip olursunuz:

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

Projemizin kökünde 3 dosyanız vardır:

  • index.js, Node.js kodunu içerir
  • package.json, kitaplık bağımlılıklarını tanımlar
  • app.yaml, Google App Engine için yapılandırma dosyasıdır

public klasörü statik kaynakları içerir:

  • index.html tüm küçük resimleri ve etiketleri gösteren sayfadır
  • collage.html, son resimlerden oluşan kolajı gösterir
  • upload.html, yeni resimler yüklemek için bir form içeriyor
  • app.js, index.html sayfasını verilerle doldurmak için Vue.js'yi kullanıyor
  • script.js, gezinme menüsünü ve "hamburger"i işler küçük ekranlarda simge
  • style.css bazı CSS yönergelerini tanımlar

5. Kodu inceleyin

Bağımlılıklar

package.json dosyası, gerekli kitaplık bağımlılıklarını tanımlar:

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

Başvurumuz şunlara bağlıdır:

  • firestore: Resim meta verilerimizle Cloud Firestore'a erişmek için
  • storage: Resimlerin depolandığı Google Cloud Storage'a erişmek için
  • express: Node.js'nin web çerçevesi
  • dayjs: tarihleri insan dostu bir şekilde gösteren küçük bir kitaplık
  • bluebird: JavaScript taahhüt kitaplığı,
  • express-fileupload: Dosya yüklemelerini kolayca işlemek için bir kitaplık.

Express ön uç

index.js denetleyicisinin başında, daha önce package.json içinde tanımlanan tüm bağımlılıklara ihtiyacınız olacak:

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)

Ardından, Express uygulama örneği oluşturulur.

İki Express ara katman yazılımı kullanılır:

  • express.static() çağrısı, statik kaynakların public alt dizininde bulunacağını belirtir.
  • fileUpload(), dosyaları /tmp dizinindeki bellek içi dosya sistemine yerel olarak yüklemek için dosya yüklemeyi, dosya boyutunu 10 MB ile sınırlandıracak şekilde yapılandırır.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
    limits: { fileSize: 10 * 1024 * 1024 },
    useTempFiles : true,
    tempFileDir : '/tmp/'
}))

Statik kaynaklar arasında ana sayfa, kolaj sayfası ve yükleme sayfası için HTML dosyaları bulunur. Bu sayfalar, API arka ucunu çağırır. Bu API aşağıdaki uç noktalara sahip olur:

  • POST /api/pictures Upload.html dosyasındaki form aracılığıyla, resimler POST isteği aracılığıyla yüklenir.
  • GET /api/pictures Bu uç nokta, resimlerin listesini ve etiketlerini içeren bir JSON dokümanı döndürür
  • GET /api/pictures/:name Bu URL, tam boyutlu resmin bulut depolama konumuna yönlendiriyor
  • GET /api/thumbnails/:name Bu URL, küçük resmin bulut depolama konumuna yönlendiriyor
  • GET /api/collage Bu son URL, oluşturulan kolaj resminin bulut depolama konumuna yönlendirir

Resim yükleme

Node.js kodunu resim yükleme işlemini incelemeden önce public/upload.html sayfasına hızlıca göz atın.

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

Form öğesi, bir HTTP POST yöntemi ve çok bölümlü bir biçimde /api/pictures uç noktasını işaret ediyor. index.js kullanıcısının artık bu uç noktaya ve yönteme yanıt vermesi ve dosyaları çıkarması gerekir:

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

Öncelikle, gerçekten de yüklenen dosyaların olup olmadığını kontrol edersiniz. Ardından, dosya yükleme düğümü modülümüzden gelen mv yöntemini kullanarak dosyaları yerel olarak indirirsiniz. Dosyalar yerel dosya sisteminde kullanılabildiğine göre artık resimleri Cloud Storage paketine yükleyebilirsiniz. Son olarak, kullanıcıyı tekrar uygulamanın ana ekranına yönlendirirsiniz.

Resimleri listeleme

Güzel fotoğraflarınızı gösterme zamanı!

/api/pictures işleyicide, küçük resmi oluşturulmuş tüm resimleri oluşturma tarihine göre sıralanmış şekilde almak için Firestore veritabanının pictures koleksiyonuna bakarsınız.

Her resmi; adı, resmi açıklayan etiketler (Cloud Vision API'den gelir), baskın renk ve uygun oluşturulma tarihi (dayjs ile "3 gün sonra" gibi göreli zaman farkı bilgileriyle birlikte) bir JavaScript dizisine aktarırsınız.

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

Bu kumanda şu şekilde sonuçlar döndürür:

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

Bu veri yapısı, index.html sayfasındaki küçük bir Vue.js snippet'i tarafından kullanılır. Söz konusu sayfadaki işaretlemenin basitleştirilmiş bir sürümünü burada görebilirsiniz:

<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 öğesinin kimliği, Vue.js'nin dinamik olarak oluşturulacak işaretlemenin bir parçası olduğunu belirtir. Yinelemeler, v-for yönergeleri sayesinde yapılır.

Cloud Vision API tarafından da görüldüğü gibi, resimlerdeki baskın renge karşılık gelen hoş renkli bir kenarlık olur. Ayrıca, bağlantı ve resim kaynaklarındaki küçük resimler ile tam genişlikteki resimleri gösteriyoruz.

Son olarak, resmi açıklayan etiketleri listeleriz.

Vue.js snippet'i (index.html sayfasının alt kısmına içe aktarılan public/app.js dosyasında) için JavaScript kodu:

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

Vue kodu, /api/pictures uç noktamıza bir AJAX çağrısı yapmak için Axios kitaplığını kullanır. Döndürülen veriler, daha önce gördüğünüz işaretlemedeki görünüm koduna bağlanır.

Resimleri görüntüleme

index.html bağlantısından kullanıcılarımız resimlerin küçük resimlerini görüntüleyebilir, resimleri tam boyutlu olarak görüntülemek için bu resimleri tıklayabilir ve collage.html üzerinden collage.png resmi görüntüleyebilir.

Bu sayfaların HTML işaretlemesinde src resmi ve href bağlantısı bu 3 uç noktayı işaret ediyor ve resimler, küçük resimler ve kolajın Cloud Storage konumlarına yönlendirme yapıyor. Yolun HTML işaretlemesine sabit bir şekilde kodlanmasına gerek yoktur.

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 uygulamasını çalıştırma

Tüm uç noktalar tanımlandıktan sonra, Node.js uygulamanız başlatılmaya hazırdır. Express uygulaması varsayılan olarak 8080 numaralı bağlantı noktasında dinleme yapar ve gelen istekleri sunmaya hazırdır.

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. Yerel olarak test et

Buluta dağıtmadan önce çalıştığından emin olmak için kodu yerel olarak test edin.

İki Cloud Storage paketine karşılık gelen iki ortam değişkenini dışa aktarmanız gerekir:

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

frontend klasörünün içinde, npm bağımlılarını yükleyin ve sunucuyu başlatın:

npm install; npm start

Her şey yolundaysa sunucu, 8080 numaralı bağlantı noktasından başlatılmalıdır:

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

Paketlerinizin gerçek adları bu günlüklerde görünür ve hata ayıklama açısından faydalıdır.

Yerel olarak çalışan uygulamayı taramak için Cloud Shell'den web önizleme özelliğini kullanabilirsiniz:

82fa3266d48c0d0a.png

Çıkmak için CTRL-C tuşunu kullanın.

7. App Engine'e Dağıtım

Uygulamanız dağıtılmaya hazır.

App Engine'i yapılandırma

App Engine için app.yaml yapılandırma dosyasını inceleyin:

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

İlk satır, çalışma zamanının Node.js 10'u temel aldığını bildirir. Orijinal resimler ve küçük resimler için iki grubu işaret etmek üzere iki ortam değişkeni tanımlanmıştır.

GOOGLE_CLOUD_PROJECT öğesini gerçek proje kimliğinizle değiştirmek için aşağıdaki komutu çalıştırabilirsiniz:

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

Dağıtma

App Engine için tercih ettiğiniz bölgeyi ayarlayın. Önceki laboratuvarlarda aynı bölgeyi kullandığınızdan emin olun:

gcloud config set compute/region europe-west1

Ve dağıtım:

gcloud app deploy

Bir veya iki dakika sonra, uygulamanın trafik sunduğu size bildirilir:

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

Uygulamanın dağıtıldığını görmek, sürüm belirleme ve trafik bölme gibi App Engine özelliklerini keşfetmek için Cloud Console'un App Engine bölümünü de ziyaret edebilirsiniz:

db0e196b00fceab1.png

8. Uygulamayı test etme

Test etmek için uygulamanın (https://<YOUR_PROJECT_ID>.appspot.com/) varsayılan App Engine URL'sine gidin. Ön uç kullanıcı arayüzünün çalışır durumda olduğunu göreceksiniz.

6a4d5e5603ba4b73.png

9. Temizleme (İsteğe bağlı)

Uygulamayı tutmak istemiyorsanız projenin tamamını silerek maliyet tasarrufu yapmak ve genel olarak iyi bir bulut vatandaşı olmak için kaynakları temizleyebilirsiniz:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

10. Tebrikler!

Tebrikler! App Engine'de barındırılan bu Node.js web uygulaması, tüm hizmetlerinizi birbirine bağlar ve kullanıcılarınızın resim yükleyip görselleştirmesine olanak tanır.

İşlediklerimiz

  • App Engine
  • Cloud Storage
  • Cloud Firestore

Sonraki Adımlar