1. סקירה כללית
ב-Codelab הזה תיצרו ממשק אינטרנט ב-Google App Engine שיאפשר למשתמשים להעלות תמונות מאפליקציית האינטרנט, וגם לעיין בתמונות שהועלו ובתמונות הממוזערות שלהן.
אפליקציית האינטרנט הזו תשתמש במסגרת CSS שנקראת Bulma, כדי לספק ממשק משתמש בעל מראה טוב, וגם במסגרת Vue.JS של JavaScript כדי לקרוא ל-API של האפליקציה שתפתחו.
האפליקציה הזו תכלול שלוש כרטיסיות:
- דף בית שבו יוצגו התמונות הממוזערות של כל התמונות שהועלו, יחד עם רשימת התוויות שמתארות את התמונה (אלה שזוהו על ידי Cloud Vision API בשיעור Lab קודם).
- דף קולאז' שבו יוצג הקולאז' של 4 התמונות האחרונות שהועלו.
- דף העלאה, שבו המשתמשים יכולים להעלות תמונות חדשות.
החזית שתתקבל תיראה כך:
3 הדפים האלה הם דפי HTML פשוטים:
- דף הבית (
index.html
) קורא לקוד העורפי של Node App Engine כדי לקבל את רשימת התמונות הממוזערות והתוויות שלהן, באמצעות קריאת AJAX לכתובת ה-URL/api/pictures
. דף הבית משתמש ב-Vue.js לאחזור הנתונים האלה. - דף הקולאז' (
collage.html
) מפנה לתמונהcollage.png
שמרכיבה את 4 התמונות האחרונות. - דף ההעלאה (
upload.html
) כולל טופס פשוט להעלאת תמונה באמצעות בקשת POST לכתובת ה-URL של/api/pictures
.
מה תלמדו
- App Engine
- Cloud Storage
- Cloud Firestore
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמאי
- נכנסים למסוף Google Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.
- Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא נעשה בה שימוש ב-Google APIs, ואפשר לעדכן אותה בכל שלב.
- Project ID חייב להיות ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (ובדרך כלל הוא מזוהה כ-
PROJECT_ID
), כך שאם הוא לא מוצא חן בעיניכם, תוכלו ליצור פרויקט אקראי אחר או לנסות בעצמכם ולבדוק אם הוא זמין. ואז המכשיר 'קפוא' לאחר יצירת הפרויקט. - יש ערך שלישי, Project Number, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
- בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, פועלים לפי ההנחיות לניקוי בסוף ה-Codelab. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.
הפעלת Cloud Shell
אומנם אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-Codelab הזה משתמשים ב-Google Cloud Shell, סביבת שורת הפקודה שפועלת ב-Cloud.
במסוף Google Cloud, לוחצים על הסמל של Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:
נדרשים רק כמה דקות כדי להקצות את הסביבה ולהתחבר אליה. בסיום התהליך, אתם אמורים לראות משהו כזה:
למכונה הווירטואלית הזו נטען כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. כל העבודה בשיעור ה-Lab הזה יכולה להתבצע באמצעות דפדפן בלבד.
3. הפעלת ממשקי API
ל-App Engine נדרש ממשק API של Compute Engine. צריך לוודא שהיא מופעלת:
gcloud services enable compute.googleapis.com
אתם אמורים לראות שהפעולה הושלמה בהצלחה:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. שכפול הקוד
בודקים את הקוד, אם עדיין לא עשיתם זאת:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
לאחר מכן אפשר לעבור לספרייה שמכילה את הקצה הקדמי:
cd serverless-photosharing-workshop/frontend
תהיה לכם פריסת קבצים עבור הקצה הקדמי:
frontend | ├── index.js ├── package.json ├── app.yaml | ├── public | ├── index.html ├── collage.html ├── upload.html | ├── app.js ├── script.js ├── style.css
ברמה הבסיסית (root) של הפרויקט שלנו יש 3 קבצים:
- הקוד
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
5. לבדיקת הקוד
תלות
הקובץ 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: כדי לגשת ל-Google Cloud Storage שבו תמונות מאוחסנות,
- express: ה-framework של האינטרנט עבור Node.js,
- dayjs: ספרייה קטנה להצגת תאריכים באופן ידידותי לאנשים,
- bluebird: ספריית בטוחות של JavaScript,
- express-fileupload: ספרייה שמאפשרת לטפל בהעלאות קבצים בקלות.
קצה קדמי של Express
בתחילת השלט הרחוק של 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()
מוגדרת העלאת קבצים להגביל את גודל הקובץ ל-10MB, כדי להעלות את הקבצים באופן מקומי במערכת הקבצים שבזיכרון, בספרייה/tmp
.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
limits: { fileSize: 10 * 1024 * 1024 },
useTempFiles : true,
tempFileDir : '/tmp/'
}))
בין המשאבים הסטטיים יש קובצי HTML לדף הבית, לדף הקולאז' ולדף ההעלאה. דפים אלה יקראו לקצה העורפי של ה-API. ה-API הזה יכלול את נקודות הקצה הבאות:
POST /api/pictures
באמצעות הטופס ב-upload.html, התמונות יועלו באמצעות בקשת POSTGET /api/pictures
נקודת הקצה הזו מחזירה מסמך JSON שמכיל את רשימת התמונות והתוויות שלהןGET /api/pictures/:name
כתובת ה-URL הזו מפנה למיקום האחסון בענן של התמונה בגודל מלאGET /api/thumbnails/:name
כתובת ה-URL הזו מפנה למיקום האחסון בענן של התמונה הממוזערת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 להעלאת קבצים. עכשיו, כשהקבצים זמינים במערכת הקבצים המקומית, אפשר להעלות את התמונות לקטגוריה של Cloud Storage. לבסוף, מפנים את המשתמש חזרה למסך הראשי של האפליקציה.
רישום התמונות
הגיע הזמן להציג את התמונות היפהפיות שלך!
ב-handler של /api/pictures
, בוחנים את אוסף pictures
של מסד הנתונים של Firestore, כדי לאחזר את כל התמונות (שהתמונה הממוזערת שלהן נוצרה), לפי תאריך היצירה שלהן בסדר יורד.
דוחפים כל תמונה במערך JavaScript בצירוף השם שלה, התוויות שמתארות אותה (מ-Cloud Vision API), הצבע הדומיננטי ותאריך יצירה ידידותי (ב-dayjs
, אנחנו יכולים לשמור על הפרשי זמן יחסיים כמו "3 ימים מעכשיו").
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>
</span>
{{ label }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
מזהה ה-div יציין ל-Vue.js שהוא החלק בתג העיצוב שיעובד באופן דינמי. האיטרציות מבוצעות בהתאם להוראות v-for
.
התמונות מקבלות גבול צבעוני יפה שתואם לצבע הדומיננטי בתמונה, כמו ב-Cloud Vision API, ואנחנו מצביעים על התמונות הממוזערות ועל התמונות ברוחב המלא בקישור ובמקורות התמונה.
לבסוף, אנחנו מפרטים את התוויות שמתארות את התמונה.
הנה קוד ה-JavaScript של קטע הקוד של 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
. הנתונים המוחזרים קשורים לאחר מכן לקוד התצוגה שבתגי העיצוב שראיתם קודם לכן.
צפייה בתמונות
החל מ-index.html
, המשתמשים שלנו יכולים לראות את התמונות הממוזערות של התמונות, ללחוץ עליהן כדי לראות את התמונות בגודל מלא ו-collage.html
, המשתמשים רואים את התמונה collage.png
.
בתגי העיצוב של ה-HTML בדפים האלה, התמונה src
והקישור href
מפנים ל-3 נקודות הקצה האלה, שמפנות אל מיקומי Cloud Storage של התמונות, התמונות הממוזערות והקולאז'. אין צורך לקודד בתוך הקוד את הנתיב בתגי העיצוב של 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 מקשיבה ליציאה 8080 כברירת מחדל, ומוכנה למלא את הבקשות הנכנסות.
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. בדיקה מקומית
מומלץ לבדוק את הקוד באופן מקומי כדי לוודא שהוא פועל לפני הפריסה בענן.
עליכם לייצא את שני משתני הסביבה שתואמים לשתי הקטגוריות של Cloud Storage:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
בתוך התיקייה frontend
, מתקינים יחסי תלות של npm ומפעילים את השרת:
npm install; npm start
אם הכול תקין, צריך להפעיל את השרת ביציאה 8080:
Started web frontend service on port 8080 - Pictures bucket = uploaded-pictures-${GOOGLE_CLOUD_PROJECT} - Thumbnails bucket = thumbnails-${GOOGLE_CLOUD_PROJECT}
השמות האמיתיים של הקטגוריות יופיעו ביומנים האלה. דבר זה שימושי למטרות ניפוי באגים.
תוכלו להשתמש בפיצ'ר התצוגה המקדימה של האינטרנט ב-Cloud Shell כדי להשתמש בדפדפן של האפליקציה שפועלת באופן מקומי:
כדי לצאת מהמצב הזה, אפשר להשתמש בCTRL-C
.
7. פריסה ל-App Engine
האפליקציה שלך מוכנה לפריסה.
הגדרת App Engine
בודקים את קובץ התצורה 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, ומקפידים להשתמש באותו אזור בשיעורי ה-Lab הקודמים:
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 כדי לראות אם האפליקציה פרוסה ולגלות תכונות של App Engine כמו ניהול גרסאות ופיצול תנועה:
8. בדיקת האפליקציה
כדי לבדוק זאת, יש לעבור לכתובת ה-URL של האפליקציה (https://<YOUR_PROJECT_ID>.appspot.com/
) שמוגדרת כברירת מחדל. ממשק המשתמש של הקצה הקדמי פועל.
9. הסרת המשאבים (אופציונלי)
אם אתם לא מתכוונים להשאיר את האפליקציה, תוכלו למחוק את הפרויקט כולו כדי לפנות משאבים כדי לחסוך בעלויות ולהיות אזרחי ענן טובים באופן כללי:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
10. מעולה!
מעולה! אפליקציית האינטרנט הזו, מסוג Node.js, שמתארחת ב-App Engine, מחברת את כל השירותים שלכם יחד, ומאפשרת למשתמשים להעלות ולהמחיש תמונות.
אילו נושאים דיברנו?
- App Engine
- Cloud Storage
- Cloud Firestore