วิธีใช้ App Engine Blobstore (โมดูล 15)

1. ภาพรวม

ชุด Codelab ของ Serverless Migration Station (บทแนะนำแบบลงมือทำด้วยตนเอง) และวิดีโอที่เกี่ยวข้องมีจุดมุ่งหมายเพื่อช่วยให้นักพัฒนาซอฟต์แวร์ Google Cloud Serverless ปรับปรุงแอปพลิเคชันให้ทันสมัยโดยแนะนำการย้ายข้อมูลอย่างน้อย 1 รายการ ซึ่งส่วนใหญ่เป็นการย้ายออกจากบริการเดิม การทำเช่นนี้จะช่วยให้แอปของคุณพกพาได้มากขึ้น รวมถึงมีตัวเลือกและความยืดหยุ่นมากขึ้น ซึ่งจะช่วยให้คุณผสานรวมและเข้าถึงผลิตภัณฑ์ Cloud ได้หลากหลายมากขึ้น และอัปเกรดเป็นภาษาที่ใหม่กว่าได้ง่ายขึ้น แม้ว่าในตอนแรกจะมุ่งเน้นไปที่ผู้ใช้ Cloud รุ่นแรกๆ ซึ่งส่วนใหญ่เป็นนักพัฒนาซอฟต์แวร์ App Engine (สภาพแวดล้อมมาตรฐาน) แต่ซีรีส์นี้ก็กว้างพอที่จะรวมแพลตฟอร์มแบบ Serverless อื่นๆ เช่น Cloud Functions และ Cloud Run หรือที่อื่นๆ หากเกี่ยวข้อง

Codelab โมดูล 15 นี้อธิบายวิธีเพิ่มการใช้งาน App Engine blobstore ลงในแอปตัวอย่างจากโมดูล 0 จากนั้นคุณจะพร้อมย้ายข้อมูลการใช้งานดังกล่าวไปยัง Cloud Storage ในโมดูล 16

คุณจะได้เรียนรู้วิธีต่อไปนี้

  • เพิ่มการใช้ API/ไลบรารี Blobstore ของ App Engine
  • จัดเก็บการอัปโหลดของผู้ใช้ไปยังบริการ blobstore
  • เตรียมพร้อมสำหรับขั้นตอนถัดไปในการย้ายข้อมูลไปยัง Cloud Storage

สิ่งที่คุณต้องมี

แบบสำรวจ

คุณจะใช้บทแนะนำนี้อย่างไร

อ่านอย่างเดียว อ่านและทำแบบฝึกหัด

คุณจะให้คะแนนประสบการณ์การใช้งาน Python เท่าใด

ผู้ฝึกหัด ระดับกลาง ผู้ชำนาญ

คุณจะให้คะแนนประสบการณ์การใช้บริการ Google Cloud เท่าใด

ผู้ฝึกหัด ผู้มีทักษะระดับกลาง ผู้ชำนาญ

2. ฉากหลัง

หากต้องการย้ายข้อมูลจาก App Engine Blobstore API ให้เพิ่มการใช้งานลงในแอป App Engine ndb พื้นฐานที่มีอยู่จากโมดูล 0 แอปตัวอย่างจะแสดงการเข้าชมล่าสุด 10 ครั้งต่อผู้ใช้ เรากำลังแก้ไขแอปเพื่อแจ้งให้ผู้ใช้ปลายทางอัปโหลดอาร์ติแฟกต์ (ไฟล์) ที่สอดคล้องกับ "การเข้าชม" ของตน หากผู้ใช้ไม่ต้องการดำเนินการดังกล่าว ก็มีตัวเลือก "ข้าม" ให้เลือก ไม่ว่าผู้ใช้จะตัดสินใจอย่างไร หน้าถัดไปจะแสดงผลลัพธ์เหมือนกับแอปจากโมดูล 0 (และโมดูลอื่นๆ อีกหลายโมดูลในชุดนี้) เมื่อติดตั้งใช้งานการผสานรวม App Engine blobstore นี้แล้ว เราจะย้ายข้อมูลไปยัง Cloud Storage ได้ใน Codelab ถัดไป (โมดูล 16)

App Engine ให้สิทธิ์เข้าถึงระบบการสร้างเทมเพลต Django และ Jinja2 และสิ่งหนึ่งที่ทำให้ตัวอย่างนี้แตกต่าง (นอกเหนือจากการเพิ่มสิทธิ์เข้าถึง Blobstore) คือการเปลี่ยนจากการใช้ Django ในโมดูล 0 เป็น Jinja2 ในโมดูล 15 นี้ ขั้นตอนสำคัญในการปรับปรุงแอป App Engine ให้ทันสมัยคือการย้ายข้อมูลเว็บเฟรมเวิร์กจาก webapp2 ไปยัง Flask โดยอย่างหลังใช้ Jinja2 เป็นระบบเทมเพลตเริ่มต้น เราจึงเริ่มเปลี่ยนไปใช้ Jinja2 ขณะที่ยังคงใช้ webapp2 เพื่อเข้าถึง Blobstore เนื่องจาก Flask ใช้ Jinja2 โดยค่าเริ่มต้น จึงหมายความว่าไม่จำเป็นต้องทำการเปลี่ยนแปลงเทมเพลตล่วงหน้าในโมดูล 16

3. การตั้งค่า/งานเตรียมความพร้อม

ก่อนที่จะเข้าสู่ส่วนหลักของบทแนะนำ ให้ตั้งค่าโปรเจ็กต์ รับโค้ด และทำให้แอปพื้นฐานใช้งานได้เพื่อเริ่มต้นด้วยโค้ดที่ใช้งานได้

1. ตั้งค่าโปรเจ็กต์

หากคุณติดตั้งใช้งานแอปโมดูล 0 แล้ว เราขอแนะนำให้ใช้โปรเจ็กต์ (และโค้ด) เดียวกันซ้ำ หรือคุณจะสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์อื่นที่มีอยู่ซ้ำก็ได้ ตรวจสอบว่าโปรเจ็กต์มีบัญชีสำหรับการเรียกเก็บเงินที่ใช้งานอยู่และเปิดใช้ App Engine แล้ว

2. ดาวน์โหลดแอปตัวอย่างพื้นฐาน

ข้อกำหนดเบื้องต้นอย่างหนึ่งของ Codelab นี้คือต้องมีแอปตัวอย่างโมดูล 0 ที่ใช้งานได้ หากไม่มี คุณสามารถรับได้จากโฟลเดอร์ "START" ของโมดูล 0 (ลิงก์ด้านล่าง) Codelab นี้จะแนะนำคุณทีละขั้นตอน โดยสรุปด้วยโค้ดที่คล้ายกับโค้ดในโฟลเดอร์ "FINISH" ของโมดูล 15

ไดเรกทอรีของไฟล์ START ของโมดูล 0 ควรมีลักษณะดังนี้

$ ls
README.md               index.html
app.yaml                main.py

3. (อีกครั้ง) ติดตั้งใช้งานแอปพื้นฐาน

ขั้นตอนการเตรียมงานที่เหลือซึ่งคุณต้องดำเนินการในตอนนี้มีดังนี้

  1. ทำความคุ้นเคยกับเครื่องมือบรรทัดคำสั่ง gcloud อีกครั้ง
  2. ทำให้แอปตัวอย่างใช้งานได้อีกครั้งด้วย gcloud app deploy
  3. ยืนยันว่าแอปทำงานใน App Engine ได้โดยไม่มีปัญหา

เมื่อทำตามขั้นตอนเหล่านั้นสำเร็จและเห็นว่าเว็บแอปทำงานได้ (โดยมีเอาต์พุตคล้ายกับด้านล่าง) คุณก็พร้อมที่จะเพิ่มการใช้การแคชลงในแอปแล้ว

a7a9d2b80d706a2b.png

4. อัปเดตไฟล์การกำหนดค่า

app.yaml

ไม่มีการเปลี่ยนแปลงที่เป็นสาระสำคัญในการกำหนดค่าแอปพลิเคชัน อย่างไรก็ตาม ตามที่กล่าวไว้ก่อนหน้านี้ เรากำลังเปลี่ยนจากเทมเพลต Django (ค่าเริ่มต้น) เป็น Jinja2 ดังนั้นหากต้องการเปลี่ยน ผู้ใช้ควรระบุ Jinja2 เวอร์ชันล่าสุดที่พร้อมใช้งานบนเซิร์ฟเวอร์ App Engine โดยเพิ่มลงในส่วนไลบรารีของบุคคลที่สามในตัวของ app.yaml

ก่อน:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

แก้ไขไฟล์ app.yaml โดยเพิ่มส่วน libraries ใหม่ดังที่เห็นที่นี่

หลัง:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

คุณไม่จำเป็นต้องอัปเดตไฟล์การกำหนดค่าอื่นๆ ดังนั้นเรามาดูไฟล์แอปพลิเคชันกันต่อเลย

5. แก้ไขไฟล์แอปพลิเคชัน

การนำเข้าและการรองรับ Jinja2

การเปลี่ยนแปลงชุดแรกสำหรับ main.py ได้แก่ การเพิ่มการใช้ Blobstore API และการแทนที่เทมเพลต Django ด้วย Jinja2 สิ่งที่จะเปลี่ยนแปลงมีดังนี้

  1. วัตถุประสงค์ของโมดูล os คือการสร้างชื่อเส้นทางไฟล์ไปยังเทมเพลต Django เนื่องจากเราเปลี่ยนไปใช้ Jinja2 ซึ่งจัดการเรื่องนี้อยู่แล้ว จึงไม่จำเป็นต้องใช้ os และโปรแกรมแสดงผลเทมเพลต Django google.appengine.ext.webapp.template อีกต่อไป เราจึงนำออก
  2. นำเข้า Blobstore API: google.appengine.ext.blobstore
  3. นำเข้าแฮนเดิล Blobstore ที่พบในเฟรมเวิร์ก webapp เดิม เนื่องจากไม่มีใน webapp2: google.appengine.ext.webapp.blobstore_handlers
  4. นำเข้าการรองรับ Jinja2 จากแพ็กเกจ webapp2_extras

ก่อน:

import os
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

ใช้การเปลี่ยนแปลงในรายการด้านบนโดยแทนที่ส่วนการนําเข้าปัจจุบันใน main.py ด้วยข้อมูลโค้ดด้านล่าง

หลัง:

import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers

หลังจากนำเข้าแล้ว ให้เพิ่มโค้ด Boilerplate เพื่อรองรับการใช้ Jinja2 ตามที่ระบุไว้ในเอกสารประกอบของ webapp2_extras ข้อมูลโค้ดต่อไปนี้จะรวมคลาสตัวแฮนเดิลคำขอ webapp2 มาตรฐานเข้ากับฟังก์ชันการทำงานของ Jinja2 ดังนั้นให้เพิ่มบล็อกโค้ดนี้ลงใน main.py ต่อจากการนำเข้า

class BaseHandler(webapp2.RequestHandler):
    'Derived request handler mixing-in Jinja2 support'
    @webapp2.cached_property
    def jinja2(self):
        return jinja2.get_jinja2(app=self.app)

    def render_response(self, _template, **context):
        self.response.write(self.jinja2.render_template(_template, **context))

เพิ่มการรองรับ Blobstore

ตัวอย่างนี้แตกต่างจากการย้ายข้อมูลอื่นๆ ในชุดนี้ที่เราคงฟังก์ชันการทำงานหรือเอาต์พุตของแอปตัวอย่างไว้เหมือนเดิม (หรือเกือบเหมือนเดิม) โดยไม่มีการเปลี่ยนแปลง UX (มากนัก) โดยตัวอย่างนี้จะแตกต่างจากปกติอย่างมาก แทนที่จะลงทะเบียนการเข้าชมใหม่ทันทีแล้วแสดงการเข้าชม 10 ครั้งล่าสุด เรากำลังอัปเดตแอปเพื่อขออาร์ติแฟกต์ไฟล์จากผู้ใช้เพื่อลงทะเบียนการเข้าชม จากนั้นผู้ใช้ปลายทางจะอัปโหลดไฟล์ที่เกี่ยวข้องหรือเลือก "ข้าม" เพื่อไม่อัปโหลดอะไรเลยก็ได้ เมื่อขั้นตอนนี้เสร็จสมบูรณ์แล้ว หน้า "การเข้าชมล่าสุด" จะปรากฏขึ้น

การเปลี่ยนแปลงนี้ช่วยให้แอปของเราใช้บริการ Blobstore เพื่อจัดเก็บ (และอาจแสดงผลในภายหลัง) รูปภาพหรือประเภทไฟล์อื่นๆ ในหน้าการเข้าชมล่าสุดได้

อัปเดตโมเดลข้อมูลและนำไปใช้

เรากำลังจัดเก็บข้อมูลเพิ่มเติม โดยเฉพาะการอัปเดตโมเดลข้อมูลเพื่อจัดเก็บรหัส (เรียกว่า "BlobKey") ของไฟล์ที่อัปโหลดไปยัง Blobstore และเพิ่มการอ้างอิงเพื่อบันทึกไว้ใน store_visit() เนื่องจากระบบจะแสดงข้อมูลเพิ่มเติมนี้พร้อมกับข้อมูลอื่นๆ ทั้งหมดเมื่อมีการค้นหา fetch_visits() จึงยังคงเหมือนเดิม

ต่อไปนี้คือภาพก่อนและหลังการอัปเดตเหล่านี้ซึ่งมี file_blob ซึ่งเป็นndb.BlobKeyProperty:

ก่อน:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

หลัง:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent),
            file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

ต่อไปนี้คือภาพแสดงการเปลี่ยนแปลงที่เกิดขึ้นจนถึงตอนนี้

2270783776759f7f.png

รองรับการอัปโหลดไฟล์

การเปลี่ยนแปลงที่สำคัญที่สุดในฟังก์ชันการทำงานคือการรองรับการอัปโหลดไฟล์ ไม่ว่าจะเป็นการแจ้งให้ผู้ใช้ส่งไฟล์ การรองรับฟีเจอร์ "ข้าม" หรือการแสดงผลไฟล์ที่สอดคล้องกับการเข้าชม ทั้งหมดนี้เป็นส่วนหนึ่งของภาพรวม การเปลี่ยนแปลงที่จำเป็นเพื่อรองรับการอัปโหลดไฟล์มีดังนี้

  1. คำขอ GET ของแฮนเดิลหลักจะไม่ดึงข้อมูลการเข้าชมล่าสุดมาแสดงอีกต่อไป แต่จะแจ้งให้ผู้ใช้อัปโหลดแทน
  2. เมื่อผู้ใช้ปลายทางส่งไฟล์เพื่ออัปโหลดหรือข้ามกระบวนการดังกล่าว POST จากแบบฟอร์มจะส่งต่อการควบคุมไปยัง UploadHandler ใหม่ ซึ่งได้มาจาก google.appengine.ext.webapp.blobstore_handlers.BlobstoreUploadHandler
  3. เมธอด POST ของ UploadHandler จะทำการอัปโหลด เรียกใช้ store_visit() เพื่อลงทะเบียนการเข้าชม และทริกเกอร์การเปลี่ยนเส้นทาง HTTP 307 เพื่อส่งผู้ใช้กลับไปยัง "/" ซึ่ง...
  4. เมธอด POST ของตัวแฮนเดิลหลักจะค้นหา (ผ่าน fetch_visits()) และแสดงการเข้าชมล่าสุด หากผู้ใช้เลือก "ข้าม" ระบบจะไม่อัปโหลดไฟล์ แต่จะยังคงลงทะเบียนการเข้าชมตามด้วยการเปลี่ยนเส้นทางเดียวกัน
  5. การแสดงการเข้าชมล่าสุดจะมีช่องใหม่ที่แสดงต่อผู้ใช้ ซึ่งเป็น "ดู" ที่มีไฮเปอร์ลิงก์หากมีไฟล์อัปโหลด หรือ "ไม่มี" หากไม่มีไฟล์อัปโหลด การเปลี่ยนแปลงเหล่านี้จะเกิดขึ้นในเทมเพลต HTML พร้อมกับการเพิ่มแบบฟอร์มการอัปโหลด (ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้จะพร้อมให้บริการเร็วๆ นี้)
  6. หากผู้ใช้ปลายทางคลิกลิงก์ "ดู" สำหรับการเข้าชมที่มีวิดีโอที่อัปโหลด ระบบจะส่งคำขอ GET ไปยัง ViewBlobHandler ใหม่ ซึ่งได้มาจาก google.appengine.ext.webapp.blobstore_handlers.BlobstoreDownloadHandler โดยจะแสดงผลไฟล์หากเป็นรูปภาพ (ในเบราว์เซอร์หากรองรับ) แจ้งให้ดาวน์โหลดหากไม่ใช่ หรือแสดงข้อผิดพลาด HTTP 404 หากไม่พบ
  7. นอกเหนือจากคลาสตัวแฮนเดิลคู่ใหม่และเส้นทางคู่ใหม่เพื่อส่งการเข้าชมไปยังคลาสเหล่านั้นแล้ว ตัวแฮนเดิลหลักยังต้องมีเมธอด POST ใหม่เพื่อรับการเปลี่ยนเส้นทาง 307 ที่อธิบายไว้ข้างต้นด้วย

ก่อนการอัปเดตเหล่านี้ แอปโมดูล 0 มีเฉพาะตัวแฮนเดิลหลักที่มีเมธอด GET และเส้นทางเดียว ดังนี้

ก่อน:

class MainHandler(webapp2.RequestHandler):
    'main application (GET) handler'
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10)
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

เมื่อใช้การอัปเดตเหล่านั้น ตอนนี้จะมีแฮนเดิล 3 รายการ ได้แก่ 1) แฮนเดิลการอัปโหลดที่มีเมธอด POST 2) แฮนเดิลการดาวน์โหลด "ดู Blob" ที่มีเมธอด GET และ 3) แฮนเดิลหลักที่มีเมธอด GET และ POST ทำการเปลี่ยนแปลงเหล่านี้เพื่อให้ส่วนที่เหลือของแอปมีลักษณะดังต่อไปนี้

หลัง:

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    'Upload blob (POST) handler'
    def post(self):
        uploads = self.get_uploads()
        blob_id = uploads[0].key() if uploads else None
        store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
        self.redirect('/', code=307)

class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
    'view uploaded blob (GET) handler'
    def get(self, blob_key):
        self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)

class MainHandler(BaseHandler):
    'main application (GET/POST) handler'
    def get(self):
        self.render_response('index.html',
                upload_url=blobstore.create_upload_url('/upload'))

    def post(self):
        visits = fetch_visits(10)
        self.render_response('index.html', visits=visits)

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/upload', UploadHandler),
    ('/view/([^/]+)?', ViewBlobHandler),
], debug=True)

มีการเรียกใช้ที่สำคัญหลายรายการในโค้ดที่เราเพิ่งเพิ่มเข้าไป ดังนี้

  • ใน MainHandler.get มีการโทรหา blobstore.create_upload_url การเรียกนี้จะสร้าง URL ที่แบบฟอร์ม POST ไปยัง โดยเรียกใช้ตัวแฮนเดิลการอัปโหลดเพื่อส่งไฟล์ไปยัง Blobstore
  • ใน UploadHandler.post มีการโทรหา blobstore_handlers.BlobstoreUploadHandler.get_uploads นี่คือเวทมนตร์ที่แท้จริงที่ใส่ไฟล์ลงใน Blobstore และแสดงผลรหัสที่ไม่ซ้ำกันและถาวรสำหรับไฟล์นั้น ซึ่งก็คือ BlobKey
  • ใน ViewBlobHandler.get การเรียกใช้ blobstore_handlers.BlobstoreDownloadHandler.send ด้วย BlobKey ของไฟล์จะส่งผลให้มีการดึงข้อมูลไฟล์และส่งต่อไปยังเบราว์เซอร์ของผู้ใช้ปลายทาง

การเรียกเหล่านี้แสดงถึงการเข้าถึงฟีเจอร์ส่วนใหญ่ที่เพิ่มลงในแอป ต่อไปนี้เป็นภาพแสดงการเปลี่ยนแปลงชุดที่ 2 และชุดสุดท้ายใน main.py

da2960525ac1b90d.png

อัปเดตเทมเพลต HTML

การอัปเดตบางอย่างในแอปพลิเคชันหลักส่งผลต่ออินเทอร์เฟซผู้ใช้ (UI) ของแอป ดังนั้นจึงต้องทำการเปลี่ยนแปลงที่สอดคล้องกันในเทมเพลตเว็บ ซึ่งมี 2 รายการดังนี้

  1. ต้องมีแบบฟอร์มอัปโหลดไฟล์ที่มีองค์ประกอบอินพุต 3 รายการ ได้แก่ ไฟล์และปุ่มส่ง 2 ปุ่มสำหรับการอัปโหลดไฟล์และการข้ามตามลำดับ
  2. อัปเดตเอาต์พุตการเข้าชมล่าสุดโดยเพิ่มลิงก์ "ดู" สำหรับการเข้าชมที่มีการอัปโหลดไฟล์ที่เกี่ยวข้อง หรือ "ไม่มี" ในกรณีอื่นๆ

ก่อน:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

ใช้การเปลี่ยนแปลงในรายการด้านบนเพื่อสร้างเทมเพลตที่อัปเดตแล้ว

หลัง:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
{% if upload_url %}

<h3>Welcome... upload a file? (optional)</h3>
<form action="{{ upload_url }}" method="POST" enctype="multipart/form-data">
    <input type="file" name="file"><p></p>
    <input type="submit"> <input type="submit" value="Skip">
</form>

{% else %}

<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }}
    <i><code>
    {% if visit.file_blob %}
        (<a href="/view/{{ visit.file_blob }}" target="_blank">view</a>)
    {% else %}
        (none)
    {% endif %}
    </code></i>
    from {{ visit.visitor }}
</li>
{% endfor %}
</ul>

{% endif %}

</body>
</html>

รูปภาพนี้แสดงการอัปเดตที่จำเป็นสำหรับ index.html

8583e975f25aa9e7.png

การเปลี่ยนแปลงสุดท้ายคือ Jinja2 ต้องการเทมเพลตในโฟลเดอร์ templates ดังนั้นให้สร้างโฟลเดอร์นั้นและย้าย index.html เข้าไปข้างใน การดำเนินการขั้นสุดท้ายนี้ถือเป็นการเปลี่ยนแปลงที่จำเป็นทั้งหมดสำหรับการเพิ่มการใช้ Blobstore ลงในแอปตัวอย่างของโมดูล 0

(ไม่บังคับ) "การเพิ่มประสิทธิภาพ" Cloud Storage

ในที่สุดพื้นที่เก็บข้อมูล Blobstore ก็พัฒนาเป็น Cloud Storage ซึ่งหมายความว่าการอัปโหลด Blobstore จะปรากฏในคอนโซล Cloud โดยเฉพาะเบราว์เซอร์ Cloud Storage คำถามคือจะหาได้จากที่ไหน คำตอบคือที่เก็บข้อมูล Cloud Storage เริ่มต้นของแอป App Engine ชื่อของบัญชีบริการคือชื่อโดเมนแบบเต็มของแอป App Engine ซึ่งก็คือ PROJECT_ID.appspot.com ซึ่งสะดวกมากเพราะรหัสโปรเจ็กต์ทั้งหมดไม่ซ้ำกันเลยใช่ไหม

การอัปเดตที่ทำกับแอปพลิเคชันตัวอย่างจะวางไฟล์ที่อัปโหลดลงในที่เก็บข้อมูลนั้น แต่นักพัฒนาแอปมีตัวเลือกในการเลือกตำแหน่งที่เจาะจงมากขึ้น บัคเก็ตเริ่มต้นจะเข้าถึงได้แบบเป็นโปรแกรมผ่าน google.appengine.api.app_identity.get_default_gcs_bucket_name() ซึ่งต้องมีการนำเข้าใหม่หากต้องการเข้าถึงค่านี้ เช่น เพื่อใช้เป็นคำนำหน้าสำหรับการจัดระเบียบไฟล์ที่อัปโหลด เช่น การจัดเรียงตามประเภทไฟล์

f61f7a23a1518705.png

หากต้องการใช้โค้ดลักษณะดังกล่าวกับรูปภาพ คุณจะมีโค้ดดังต่อไปนี้พร้อมกับโค้ดที่ตรวจสอบประเภทไฟล์เพื่อเลือกชื่อที่เก็บข้อมูลที่ต้องการ

ROOT_BUCKET = app_identity.get_default_gcs_bucket_name()
IMAGE_BUCKET = '%s/%s' % (ROOT_BUCKET, 'images')

นอกจากนี้ คุณยังต้องตรวจสอบรูปภาพที่อัปโหลดโดยใช้เครื่องมืออย่างโมเดล imghdr ของ Python Standard Library เพื่อยืนยันประเภทรูปภาพด้วย สุดท้าย คุณอาจต้องการจำกัดขนาดของการอัปโหลดในกรณีที่มีผู้ไม่ประสงค์ดี

สมมติว่าคุณได้ดำเนินการทั้งหมดแล้ว เราจะอัปเดตแอปเพื่อรองรับการระบุตำแหน่งที่จะจัดเก็บไฟล์ที่อัปโหลดได้อย่างไร สิ่งสำคัญคือต้องปรับแต่งการเรียก blobstore.create_upload_url ใน MainHandler.get เพื่อระบุตำแหน่งที่ต้องการใน Cloud Storage สำหรับการอัปโหลดโดยเพิ่มพารามิเตอร์ gs_bucket_name ดังนี้

blobstore.create_upload_url('/upload', gs_bucket_name=IMAGE_BUCKET))

เนื่องจากนี่เป็นการอัปเดตที่ไม่บังคับหากคุณต้องการระบุตำแหน่งที่ควรอัปโหลด จึงไม่ได้เป็นส่วนหนึ่งของไฟล์ main.py ในที่เก็บ แต่มีทางเลือกอื่นที่ชื่อ main-gcs.py ให้คุณตรวจสอบในที่เก็บ แทนที่จะใช้ "โฟลเดอร์" บัケットแยกต่างหาก โค้ดใน main-gcs.py จะจัดเก็บการอัปโหลดไว้ในบัケット "รูท" (PROJECT_ID.appspot.com) เช่นเดียวกับ main.py แต่จะให้โครงสร้างที่คุณต้องการหากคุณจะดึงตัวอย่างไปใช้เป็นอย่างอื่นตามที่บอกเป็นนัยในส่วนนี้ ด้านล่างนี้คือภาพแสดง "ความแตกต่าง" ระหว่าง main.py และ main-gcs.py

256e1ea68241a501.png

6. สรุป/ล้างข้อมูล

ส่วนนี้จะสรุป Codelab นี้ด้วยการทำให้แอปใช้งานได้ โดยยืนยันว่าแอปทำงานได้ตามที่ต้องการและในเอาต์พุตที่แสดง หลังจากตรวจสอบแอปแล้ว ให้ทำตามขั้นตอนการล้างข้อมูลและพิจารณาขั้นตอนถัดไป

ทำให้แอปพลิเคชันใช้งานได้และยืนยันแอปพลิเคชัน

ทำให้แอปใช้งานได้อีกครั้งด้วย gcloud app deploy และยืนยันว่าแอปทำงานตามที่โฆษณาไว้ โดยมีประสบการณ์ของผู้ใช้ (UX) แตกต่างจากแอปโมดูล 0 ตอนนี้แอปของคุณมีหน้าจอที่แตกต่างกัน 2 หน้าจอ โดยหน้าจอแรกคือข้อความแจ้งแบบฟอร์มการอัปโหลดไฟล์การเข้าชม

f5b5f9f19d8ae978.pngจากนั้นผู้ใช้ปลายทางจะอัปโหลดไฟล์และคลิก "ส่ง" หรือคลิก "ข้าม" เพื่อไม่อัปโหลดอะไรเลยก็ได้ ไม่ว่าจะในกรณีใด ผลลัพธ์ที่ได้คือหน้าจอการเข้าชมล่าสุด ซึ่งตอนนี้ได้รับการเสริมด้วยลิงก์ "ดู" หรือ "ไม่มี" ระหว่างการประทับเวลาการเข้าชมและข้อมูลผู้เข้าชม ดังนี้

f5ac6b98ee8a34cb.png

ขอแสดงความยินดีที่ทำ Codelab นี้เสร็จสมบูรณ์ ซึ่งเป็นการเพิ่มการใช้ Blobstore ของ App Engine ลงในแอปตัวอย่างโมดูล 0 ตอนนี้โค้ดของคุณควรตรงกับโค้ดในโฟลเดอร์ FINISH (โมดูล 15) main-gcs.py ทางเลือกก็อยู่ในโฟลเดอร์นั้นด้วย

ล้างข้อมูล

ทั่วไป

หากคุณดำเนินการเสร็จแล้ว เราขอแนะนำให้ปิดใช้แอป App Engine เพื่อหลีกเลี่ยงการเรียกเก็บเงิน อย่างไรก็ตาม หากคุณต้องการทดสอบหรือทดลองเพิ่มเติม แพลตฟอร์ม App Engine มีโควต้าฟรี และตราบใดที่คุณไม่ใช้เกินระดับการใช้งานนั้น คุณก็จะไม่ถูกเรียกเก็บเงิน ซึ่งเป็นค่าใช้จ่ายสำหรับการประมวลผล แต่อาจมีค่าใช้จ่ายสำหรับบริการ App Engine ที่เกี่ยวข้องด้วย ดังนั้นโปรดดูข้อมูลเพิ่มเติมในหน้าการกำหนดราคา หากการย้ายข้อมูลนี้เกี่ยวข้องกับบริการอื่นๆ ของ Cloud ระบบจะเรียกเก็บเงินสำหรับบริการเหล่านั้นแยกต่างหาก ไม่ว่าในกรณีใดก็ตาม โปรดดูส่วน "เฉพาะสำหรับ Codelab นี้" ด้านล่าง (หากมี)

เพื่อความโปร่งใส การติดตั้งใช้งานในแพลตฟอร์มการประมวลผลแบบ Serverless ของ Google Cloud เช่น App Engine จะมีค่าใช้จ่ายในการสร้างและพื้นที่เก็บข้อมูลเล็กน้อย Cloud Build มีโควต้าฟรีของตัวเองเช่นเดียวกับ Cloud Storage พื้นที่เก็บข้อมูลของรูปภาพนั้นจะใช้โควต้าบางส่วน อย่างไรก็ตาม คุณอาจอาศัยอยู่ในภูมิภาคที่ไม่มีรุ่นฟรีดังกล่าว ดังนั้นโปรดคำนึงถึงการใช้พื้นที่เก็บข้อมูลเพื่อลดค่าใช้จ่ายที่อาจเกิดขึ้น "โฟลเดอร์" Cloud Storage ที่เฉพาะเจาะจงซึ่งคุณควรตรวจสอบมีดังนี้

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • ลิงก์พื้นที่เก็บข้อมูลด้านบนจะขึ้นอยู่กับ PROJECT_ID และ *LOC*ation เช่น "us" หากแอปโฮสต์อยู่ในสหรัฐอเมริกา

ในทางกลับกัน หากคุณจะไม่ดำเนินการต่อกับแอปพลิเคชันนี้หรือ Codelab การย้ายข้อมูลอื่นๆ ที่เกี่ยวข้อง และต้องการลบทุกอย่างออกทั้งหมด ให้ปิดโปรเจ็กต์

เฉพาะสำหรับ Codelab นี้

บริการที่ระบุไว้ด้านล่างนี้มีไว้สำหรับ Codelab นี้โดยเฉพาะ ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบของผลิตภัณฑ์แต่ละรายการ ดังนี้

ขั้นตอนถัดไป

การย้ายข้อมูลเชิงตรรกะถัดไปที่ควรพิจารณาจะอยู่ในโมดูล 16 ซึ่งจะแสดงวิธีที่นักพัฒนาซอฟต์แวร์ย้ายข้อมูลจากบริการ Blobstore ของ App Engine ไปใช้ไลบรารีของไคลเอ็นต์ Cloud Storage ประโยชน์ของการอัปเกรด ได้แก่ การเข้าถึงฟีเจอร์ Cloud Storage เพิ่มเติม การทำความคุ้นเคยกับไลบรารีของไคลเอ็นต์ที่ทำงานกับแอปนอก App Engine ไม่ว่าจะใน Google Cloud, คลาวด์อื่นๆ หรือแม้แต่ในองค์กร หากรู้สึกว่าไม่จำเป็นต้องใช้ฟีเจอร์ทั้งหมดที่มีใน Cloud Storage หรือกังวลเกี่ยวกับผลกระทบด้านค่าใช้จ่าย คุณสามารถใช้ App Engine Blobstore ต่อไปได้

นอกเหนือจากโมดูล 16 แล้ว ยังมีการย้ายข้อมูลอื่นๆ ที่เป็นไปได้อีกมากมาย เช่น Cloud NDB และ Cloud Datastore, Cloud Tasks หรือ Cloud Memorystore นอกจากนี้ยังมีการย้ายข้อมูลข้ามผลิตภัณฑ์ไปยัง Cloud Run และ Cloud Functions ด้วย ที่เก็บข้อมูลการย้ายข้อมูลมีตัวอย่างโค้ดทั้งหมด ลิงก์ไปยัง Codelab และวิดีโอทั้งหมดที่มี รวมถึงให้คำแนะนำเกี่ยวกับการย้ายข้อมูลที่ควรพิจารณาและ "ลำดับ" การย้ายข้อมูลที่เกี่ยวข้อง

7. แหล่งข้อมูลเพิ่มเติม

ปัญหา/ความคิดเห็นเกี่ยวกับ Codelab

หากพบปัญหาเกี่ยวกับ Codelab นี้ โปรดค้นหาปัญหาก่อนที่จะยื่นเรื่อง ลิงก์เพื่อค้นหาและสร้างปัญหาใหม่

แหล่งข้อมูลเกี่ยวกับการย้ายข้อมูล

ลิงก์ไปยังโฟลเดอร์ที่เก็บสำหรับโมดูล 0 (เริ่มต้น) และโมดูล 15 (เสร็จสิ้น) จะอยู่ในตารางด้านล่าง นอกจากนี้ คุณยังเข้าถึงได้จากที่เก็บสำหรับการย้ายข้อมูล Codelab ของ App Engine ทั้งหมด ซึ่งคุณสามารถโคลนหรือดาวน์โหลดไฟล์ ZIP ได้

Codelab

Python 2

Python 3

โมดูล 0

code

ไม่มี

โมดูล 15 (Codelab นี้)

code

ไม่มี

แหล่งข้อมูลออนไลน์

แหล่งข้อมูลออนไลน์ที่อาจเกี่ยวข้องกับบทแนะนำนี้มีดังนี้

App Engine

Google Cloud

Python

วิดีโอ

ใบอนุญาต

งานนี้ได้รับอนุญาตภายใต้สัญญาอนุญาตครีเอทีฟคอมมอนส์สำหรับยอมรับสิทธิของผู้สร้าง (Creative Commons Attribution License) 2.0 แบบทั่วไป