Pic-a-day: Lab 4 — یک صفحه وب ایجاد کنید

۱. مرور کلی

در این آزمایشگاه کد، شما یک رابط کاربری وب در Google App Engine ایجاد می‌کنید که به کاربران اجازه می‌دهد تصاویر را از برنامه وب آپلود کنند و همچنین تصاویر آپلود شده و تصاویر کوچک آنها را مرور کنند.

21741cd63b425aeb.png

این برنامه وب از یک فریم‌ورک CSS به نام Bulma برای داشتن رابط کاربری زیبا و همچنین از فریم‌ورک frontend جاوا اسکریپت Vue.JS برای فراخوانی API برنامه‌ای که خواهید ساخت، استفاده خواهد کرد.

این برنامه شامل سه تب خواهد بود:

  • یک صفحه اصلی که تصاویر کوچک تمام تصاویر آپلود شده را به همراه لیستی از برچسب‌های توصیف کننده تصویر (آن‌هایی که توسط Cloud Vision API در آزمایش قبلی شناسایی شده‌اند) نمایش می‌دهد.
  • یک صفحه کلاژ که کلاژ ساخته شده از ۴ عکس آخر آپلود شده را نشان می‌دهد.
  • یک صفحه آپلود ، که در آن کاربران می‌توانند تصاویر جدید را آپلود کنند.

ظاهر نهایی (frontend) به شکل زیر خواهد بود:

6a4d5e5603ba4b73.png

این سه صفحه، صفحات ساده HTML هستند:

  • صفحه اصلی ( index.html ) کد بک‌اند Node App Engine را فراخوانی می‌کند تا فهرست تصاویر کوچک و برچسب‌های آنها را از طریق یک فراخوانی AJAX به آدرس اینترنتی /api/pictures دریافت کند. صفحه اصلی از Vue.js برای دریافت این داده‌ها استفاده می‌کند.
  • صفحه کلاژ ( collage.html ) به تصویر collage.png اشاره می‌کند که چهار تصویر آخر را کنار هم قرار داده است.
  • صفحه آپلود ( upload.html ) یک فرم ساده برای آپلود تصویر از طریق درخواست POST به آدرس /api/pictures ارائه می‌دهد.

آنچه یاد خواهید گرفت

  • موتور برنامه
  • فضای ذخیره‌سازی ابری
  • فروشگاه ابری فایر استور

۲. تنظیمات و الزامات

تنظیم محیط خودتنظیم

  1. وارد کنسول گوگل کلود شوید و یک پروژه جدید ایجاد کنید یا از یک پروژه موجود دوباره استفاده کنید. اگر از قبل حساب جیمیل یا گوگل ورک اسپیس ندارید، باید یکی ایجاد کنید .

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • نام پروژه ، نام نمایشی برای شرکت‌کنندگان این پروژه است. این یک رشته کاراکتری است که توسط APIهای گوگل استفاده نمی‌شود و شما می‌توانید آن را در هر زمانی به‌روزرسانی کنید.
  • شناسه پروژه باید در تمام پروژه‌های گوگل کلود منحصر به فرد باشد و تغییرناپذیر است (پس از تنظیم، قابل تغییر نیست). کنسول کلود به طور خودکار یک رشته منحصر به فرد تولید می‌کند؛ معمولاً برای شما مهم نیست که چیست. در اکثر آزمایشگاه‌های کد، باید به شناسه پروژه ارجاع دهید (و معمولاً با نام PROJECT_ID شناخته می‌شود)، بنابراین اگر آن را دوست ندارید، یک شناسه تصادفی دیگر ایجاد کنید، یا می‌توانید شناسه خودتان را امتحان کنید و ببینید آیا در دسترس است یا خیر. سپس پس از ایجاد پروژه، آن "منجمد" می‌شود.
  • یک مقدار سوم هم وجود دارد، شماره پروژه که برخی از APIها از آن استفاده می‌کنند. برای اطلاعات بیشتر در مورد هر سه این مقادیر به مستندات مراجعه کنید.
  1. در مرحله بعد، برای استفاده از منابع/APIهای ابری، باید پرداخت صورتحساب را در کنسول ابری فعال کنید . اجرای این آزمایشگاه کد، اگر اصلاً هزینه‌ای نداشته باشد، هزینه زیادی نخواهد داشت. برای خاموش کردن منابع به طوری که پس از این آموزش متحمل پرداخت صورتحساب نشوید، دستورالعمل‌های «پاکسازی» موجود در انتهای آزمایشگاه کد را دنبال کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان ۳۰۰ دلاری هستند.

شروع پوسته ابری

اگرچه می‌توان از راه دور و از طریق لپ‌تاپ، گوگل کلود را مدیریت کرد، اما در این آزمایشگاه کد، از گوگل کلود شل ، یک محیط خط فرمان که در فضای ابری اجرا می‌شود، استفاده خواهید کرد.

از کنسول گوگل کلود ، روی آیکون Cloud Shell در نوار ابزار بالا سمت راست کلیک کنید:

55efc1aaa7a4d3ad.png

آماده‌سازی و اتصال به محیط فقط چند لحظه طول می‌کشد. وقتی تمام شد، باید چیزی شبیه به این را ببینید:

7ffe5cbb04455448.png

این ماشین مجازی مجهز به تمام ابزارهای توسعه مورد نیاز شماست. این ماشین یک دایرکتوری خانگی دائمی ۵ گیگابایتی ارائه می‌دهد و روی فضای ابری گوگل اجرا می‌شود که عملکرد شبکه و احراز هویت را تا حد زیادی بهبود می‌بخشد. تمام کارهای شما در این آزمایشگاه را می‌توان به سادگی با یک مرورگر انجام داد.

۳. فعال کردن APIها

موتور برنامه به رابط برنامه‌نویسی Compute Engine نیاز دارد. مطمئن شوید که فعال است:

gcloud services enable compute.googleapis.com

باید ببینید که عملیات با موفقیت انجام شد:

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

۴. کد را کپی کنید

اگر هنوز کد را بررسی نکرده‌اید، آن را بررسی کنید:

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

سپس می‌توانید به دایرکتوری حاوی frontend بروید:

cd serverless-photosharing-workshop/frontend

شما طرح فایل زیر را برای frontend خواهید داشت:

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

در ریشه پروژه ما، شما ۳ فایل دارید:

  • 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 را تعریف می‌کند.

۵. کد را بررسی کنید

وابستگی‌ها

فایل 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 با فراداده‌های تصویر ما،
  • storage : برای دسترسی به فضای ذخیره‌سازی ابری گوگل که تصاویر در آن ذخیره می‌شوند،
  • اکسپرس : چارچوب وب برای Node.js،
  • dayjs : یک کتابخانه کوچک برای نمایش تاریخ‌ها به روشی کاربرپسند،
  • bluebird : یک کتابخانه promise جاوا اسکریپت
  • 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 backend را فراخوانی می‌کنند. این API دارای نقاط پایانی زیر خواهد بود:

  • POST /api/pictures از طریق فرم موجود در upload.html، تصاویر از طریق درخواست POST آپلود می‌شوند.
  • GET /api/pictures این نقطه پایانی یک سند JSON حاوی لیست تصاویر و برچسب‌های آنها را برمی‌گرداند.
  • GET /api/pictures/:name این آدرس به محل ذخیره‌سازی ابری تصویر با اندازه کامل هدایت می‌شود.
  • GET /api/thumbnails/:name این آدرس به محل ذخیره‌سازی ابری تصویر کوچک هدایت می‌شود.
  • 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 ما می‌آید، دانلود می‌کنید. اکنون که فایل‌ها در سیستم فایل محلی موجود هستند، تصاویر را در مخزن ذخیره‌سازی ابری آپلود می‌کنید. در نهایت، کاربر را به صفحه اصلی برنامه هدایت می‌کنید.

فهرست کردن تصاویر

وقتشه عکس‌های قشنگت رو به نمایش بذاری!

در هندلر /api/pictures ، شما به مجموعه pictures پایگاه داده Firestore نگاه می‌کنید تا تمام تصاویر (که تصویر بندانگشتی آنها ایجاد شده است) را به ترتیب نزولی تاریخ ایجاد، بازیابی کنید.

شما هر تصویر را در یک آرایه جاوا اسکریپت قرار می‌دهید، به همراه نام آن، برچسب‌هایی که آن را توصیف می‌کنند (که از API Cloud Vision گرفته شده‌اند)، رنگ غالب و یک تاریخ ایجاد (با dayjs ، ما فاصله‌های زمانی نسبی مانند "۳ روز از الان" را تعیین می‌کنیم).

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 یافت می‌شود و ما به تصاویر کوچک و تصاویر تمام عرض در منابع لینک و تصویر اشاره می‌کنیم.

در آخر، برچسب‌هایی را که تصویر را توصیف می‌کنند، فهرست می‌کنیم.

کد جاوا اسکریپت مربوط به قطعه کد 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 ما استفاده می‌کند. سپس داده‌های برگردانده شده به کد view در کد نشانه‌گذاری که قبلاً دیدید، متصل می‌شوند.

مشاهده تصاویر

کاربران ما می‌توانند از طریق فایل index.html تصاویر کوچک (thumbnail) را مشاهده کنند، برای مشاهده تصاویر در اندازه کامل روی آنها کلیک کنند و از طریق فایل 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 به طور پیش‌فرض به پورت ۸۰۸۰ گوش می‌دهد و آماده ارائه درخواست‌های ورودی است.

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

۶. به صورت محلی آزمایش کنید

قبل از انتشار در فضای ابری، کد را به صورت محلی آزمایش کنید تا از عملکرد آن مطمئن شوید.

شما باید دو متغیر محیطی مربوط به دو مخزن ذخیره‌سازی ابری را اکسپورت کنید:

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

درون پوشه frontend ، وابستگی‌های npm را نصب کنید و سرور را راه‌اندازی کنید:

npm install; npm start

اگر همه چیز خوب پیش رفته باشد، باید سرور روی پورت ۸۰۸۰ شروع به کار کند:

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 استفاده کنید.

۷. استقرار در موتور برنامه

اپلیکیشن شما آماده‌ی استقرار است.

پیکربندی موتور برنامه

فایل پیکربندی 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

۸. اپلیکیشن را تست کنید

برای آزمایش، به آدرس پیش‌فرض App Engine برای برنامه ( https://<YOUR_PROJECT_ID>.appspot.com/ ) بروید و باید رابط کاربری frontend را در حال اجرا ببینید!

6a4d5e5603ba4b73.png

۹. تمیز کردن (اختیاری)

اگر قصد ندارید برنامه را نگه دارید، می‌توانید با حذف کل پروژه، منابع را پاکسازی کنید تا در هزینه‌ها صرفه‌جویی کنید و در کل یک شهروند ابری خوب باشید:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

۱۰. تبریک می‌گویم!

تبریک! این برنامه وب Node.js که در App Engine میزبانی می‌شود، تمام سرویس‌های شما را به هم متصل می‌کند و به کاربران شما اجازه می‌دهد تصاویر را آپلود و نمایش دهند.

آنچه ما پوشش داده‌ایم

  • موتور برنامه
  • فضای ذخیره‌سازی ابری
  • فروشگاه ابری فایر استور

مراحل بعدی