Codelab ในเว็บ Cloud Firestore

1. ภาพรวม

เป้าหมาย

ใน Codelab นี้ คุณจะได้สร้างเว็บแอปแนะนำร้านอาหารซึ่งขับเคลื่อนโดย Cloud Firestore

img5.png

สิ่งที่คุณจะได้เรียนรู้

  • อ่านและเขียนข้อมูลไปยัง Cloud Firestore จากเว็บแอป
  • ฟังการเปลี่ยนแปลงในข้อมูล Cloud Firestore แบบเรียลไทม์
  • ใช้การตรวจสอบสิทธิ์และกฎความปลอดภัย Firebase เพื่อรักษาความปลอดภัยให้ข้อมูล Cloud Firestore
  • เขียนการค้นหา Cloud Firestore ที่ซับซ้อน

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

ก่อนเริ่ม Codelab นี้ โปรดตรวจสอบว่าคุณได้ติดตั้ง:

  • npm ซึ่งโดยทั่วไปจะมาพร้อมกับ Node.js ขอแนะนำให้ใช้โหนด 16+
  • ตัวแก้ไข IDE/ข้อความที่ต้องการ เช่น WebStorm, VS Code หรือ Sublime

2. สร้างและตั้งค่าโปรเจ็กต์ Firebase

สร้างโปรเจ็กต์ Firebase

  1. ในคอนโซล Firebase ให้คลิกเพิ่มโปรเจ็กต์ แล้วตั้งชื่อโปรเจ็กต์ Firebase ว่า friendlyEats

จำรหัสโปรเจ็กต์สําหรับโปรเจ็กต์ Firebase

  1. คลิกสร้างโครงการ

แอปพลิเคชันที่เราจะสร้างจะใช้บริการ Firebase บางอย่างที่มีบนเว็บ

  • การตรวจสอบสิทธิ์ของ Firebase เพื่อให้ระบุผู้ใช้ได้ง่าย
  • Cloud Firestore เพื่อบันทึก Structured Data บนระบบคลาวด์และรับการแจ้งเตือนทันทีเมื่อมีการอัปเดตข้อมูล
  • โฮสติ้งของ Firebase เพื่อโฮสต์และแสดงชิ้นงานแบบคงที่

สำหรับ Codelab ที่เฉพาะเจาะจงนี้ เราได้กำหนดค่าโฮสติ้งของ Firebase ไว้แล้ว อย่างไรก็ตาม สำหรับ Firebase Auth และ Cloud Firestore เราจะแนะนำการกำหนดค่าและการเปิดใช้บริการโดยใช้คอนโซล Firebase

เปิดใช้งานการตรวจสอบสิทธิ์แบบไม่ระบุตัวตน

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

คุณจะต้องเปิดใช้การเข้าสู่ระบบแบบไม่ระบุตัวตน

  1. ในคอนโซล Firebase ให้ค้นหาส่วนสร้างในการนำทางด้านซ้าย
  2. คลิกการตรวจสอบสิทธิ์ แล้วคลิกแท็บวิธีการลงชื่อเข้าใช้ (หรือคลิกที่นี่เพื่อไปยังแท็บนั้นโดยตรง)
  3. เปิดใช้ผู้ให้บริการการลงชื่อเข้าใช้ที่ไม่ระบุชื่อ จากนั้นคลิกบันทึก

img7.png

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

เปิดใช้ Cloud Firestore

แอปใช้ Cloud Firestore เพื่อบันทึกและรับข้อมูลร้านอาหาร รวมถึงคะแนน

คุณจะต้องเปิดใช้ Cloud Firestore ในส่วน Build ของคอนโซล Firebase ให้คลิก Firestore Database คลิกสร้างฐานข้อมูลในแผง Cloud Firestore

สิทธิ์เข้าถึงข้อมูลใน Cloud Firestore จะควบคุมโดยกฎความปลอดภัย เราจะพูดถึงกฎเพิ่มเติมในภายหลังใน Codelab นี้ แต่ก่อนอื่นเราต้องกำหนดกฎพื้นฐานเกี่ยวกับข้อมูลของเราเพื่อเริ่มต้น ในแท็บกฎของคอนโซล Firebase ให้เพิ่มกฎต่อไปนี้แล้วคลิกเผยแพร่

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

กฎข้างต้นจะจำกัดการเข้าถึงข้อมูลสำหรับผู้ใช้ที่ลงชื่อเข้าใช้อยู่ ซึ่งจะป้องกันไม่ให้ผู้ใช้ที่ไม่ได้รับการตรวจสอบสิทธิ์อ่านหรือเขียนได้ ซึ่งดีกว่าการอนุญาตให้เข้าถึงแบบสาธารณะ แต่ยังคงปลอดภัยอยู่ เราจะปรับปรุงกฎเหล่านี้ภายหลังใน Codelab

3. รับโค้ดตัวอย่าง

โคลนที่เก็บ GitHub จากบรรทัดคำสั่งดังนี้

git clone https://github.com/firebase/friendlyeats-web

ควรโคลนโค้ดตัวอย่างลงในไดเรกทอรี 📁friendlyeats-web แล้ว ตั้งแต่นี้เป็นต้นไป โปรดเรียกใช้คำสั่งทั้งหมดจากไดเรกทอรีนี้

cd friendlyeats-web/vanilla-js

นำเข้าแอปเริ่มต้น

ใช้ IDE (WebStorm, Atom, Sublime, โค้ด Visual Studio...) เพื่อเปิดหรือนำเข้าไดเรกทอรี 📁friendlyeats-web ไดเรกทอรีนี้มีโค้ดเริ่มต้นสำหรับ Codelab ที่ประกอบด้วยแอปแนะนำร้านอาหารที่ยังใช้งานไม่ได้ เราจะทำให้โค้ดนี้ใช้งานได้ทั่วทั้ง Codelab นี้ ดังนั้นคุณจะต้องแก้ไขโค้ดในไดเรกทอรีนั้นในเร็วๆ นี้

4. ติดตั้งอินเทอร์เฟซบรรทัดคำสั่ง Firebase

Firebase Command Line Interface (CLI) ช่วยให้คุณแสดงเว็บแอปภายในเครื่องและทำให้เว็บแอปใช้งานได้กับโฮสติ้งของ Firebase ได้

  1. ติดตั้ง CLI โดยเรียกใช้คำสั่ง npm ต่อไปนี้:
npm -g install firebase-tools
  1. ตรวจสอบว่าได้ติดตั้ง CLI อย่างถูกต้องแล้วโดยเรียกใช้คำสั่งต่อไปนี้
firebase --version

ตรวจสอบว่าเวอร์ชันของ Firebase CLI เป็น v7.4.0 ขึ้นไป

  1. ให้สิทธิ์ Firebase CLI โดยเรียกใช้คำสั่งต่อไปนี้
firebase login

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

  1. ตรวจสอบว่าบรรทัดคำสั่งกำลังเข้าถึงไดเรกทอรีในเครื่องของแอป
  2. เชื่อมโยงแอปกับโปรเจ็กต์ Firebase โดยเรียกใช้คำสั่งต่อไปนี้
firebase use --add
  1. เมื่อมีข้อความแจ้ง ให้เลือกรหัสโปรเจ็กต์ แล้วตั้งชื่ออีเมลแทนให้โปรเจ็กต์ Firebase ของคุณ

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

  1. ทําตามวิธีการที่เหลือในบรรทัดคำสั่ง

5. เรียกใช้เซิร์ฟเวอร์ภายใน

เราพร้อมเริ่มทำงานจริงในแอปแล้ว มาเรียกใช้แอปในเครื่องกัน

  1. เรียกใช้คำสั่ง Firebase CLI ต่อไปนี้
firebase emulators:start --only hosting
  1. บรรทัดคำสั่งของคุณควรแสดงการตอบกลับต่อไปนี้
hosting: Local server: http://localhost:5000

เราใช้โปรแกรมจำลองโฮสติ้งของ Firebase เพื่อให้บริการแอปภายในเครื่อง ขณะนี้เว็บแอปควรมีให้ใช้งานจาก http://localhost:5000 แล้ว

  1. เปิดแอปที่ http://localhost:5000

คุณควรเห็นสำเนา friendlyEats ที่เชื่อมต่อกับโปรเจ็กต์ Firebase แล้ว

แอปได้เชื่อมต่อกับโปรเจ็กต์ Firebase โดยอัตโนมัติและลงชื่อเข้าใช้ในฐานะผู้ใช้แบบไม่ระบุตัวตน

img2.png

6. เขียนข้อมูลไปยัง Cloud Firestore

ในส่วนนี้ เราจะเขียนข้อมูลบางอย่างใน Cloud Firestore เพื่อสร้าง UI ของแอป ซึ่งทำได้ด้วยตนเองผ่านคอนโซล Firebase แต่เราจะทำในแอปเองเพื่อสาธิตการเขียนใน Cloud Firestore พื้นฐาน

โมเดลข้อมูล

ข้อมูล Firestore จะแบ่งออกเป็นคอลเล็กชัน เอกสาร ช่อง และคอลเล็กชันย่อย เราจะจัดเก็บร้านอาหารแต่ละแห่งเป็นเอกสารในคอลเล็กชันระดับบนสุดที่เรียกว่า restaurants

img3.png

หลังจากนั้น เราจะจัดเก็บรีวิวแต่ละรายการไว้ในคอลเล็กชันย่อยที่ชื่อว่า ratings ใต้ร้านอาหารแต่ละแห่ง

img4.png

เพิ่มร้านอาหารใน Firestore

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

  1. เปิด scripts/FriendlyEats.Data.js จากไฟล์ที่ดาวน์โหลด
  2. หาฟังก์ชัน FriendlyEats.prototype.addRestaurant
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

รหัสด้านบนจะเพิ่มเอกสารใหม่ในคอลเล็กชัน restaurants ข้อมูลเอกสารมาจากออบเจ็กต์ JavaScript ธรรมดา โดยก่อนอื่นให้อ้างอิงคอลเล็กชัน Cloud Firestore restaurants จากนั้น add "ทำการใส่ข้อมูล"

มาเพิ่มร้านอาหารกัน

  1. กลับไปที่แอป friendlyEats ในเบราว์เซอร์แล้วรีเฟรช
  2. คลิกเพิ่มข้อมูลจำลอง

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

หากไปที่แท็บ Cloud Firestore ในคอนโซล Firebase คุณจะเห็นเอกสารใหม่ในคอลเล็กชัน restaurants

img6.png

ยินดีด้วย คุณเพิ่งเขียนข้อมูลจากเว็บแอปลงใน Cloud Firestore

ในส่วนถัดไป คุณจะได้ดูวิธีเรียกข้อมูลจาก Cloud Firestore และแสดงในแอป

7. แสดงข้อมูลจาก Cloud Firestore

ในส่วนนี้ คุณจะได้ดูวิธีเรียกข้อมูลจาก Cloud Firestore และแสดงในแอป ขั้นตอนสำคัญ 2 ขั้นตอนคือการสร้างการค้นหาและการเพิ่ม Listener ของสแนปชอต ผู้ฟังรายนี้จะได้รับแจ้งข้อมูลที่มีอยู่ทั้งหมดที่ตรงกับคำค้นหา และจะได้รับข้อมูลอัปเดตแบบเรียลไทม์

ก่อนอื่น มาสร้างคำค้นหาที่จะแสดงรายชื่อร้านอาหารตามค่าเริ่มต้นที่ไม่มีการกรอง

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. หาฟังก์ชัน FriendlyEats.prototype.getAllRestaurants
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

ในโค้ดข้างต้น เราสร้างข้อความค้นหาที่จะดึงข้อมูลร้านอาหารสูงสุด 50 ร้านจากคอลเล็กชันระดับบนสุดที่ชื่อ restaurants ซึ่งเรียงลำดับตามคะแนนเฉลี่ย (ปัจจุบันทั้งหมดเป็น 0) หลังจากประกาศคำค้นหานี้ เราจะส่งคำค้นหาดังกล่าวไปยังเมธอด getDocumentsInQuery() ซึ่งมีหน้าที่โหลดและแสดงผลข้อมูล

โดยเราจะเพิ่ม Listener ของสแนปชอต

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. หาฟังก์ชัน FriendlyEats.prototype.getDocumentsInQuery
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

ในโค้ดข้างต้น query.onSnapshot จะเรียกใช้ Callback ทุกครั้งที่ผลการค้นหามีการเปลี่ยนแปลง

  • ครั้งแรก ระบบจะทริกเกอร์ Callback พร้อมกับชุดผลการค้นหาทั้งชุด ซึ่งหมายถึงคอลเล็กชัน restaurants ทั้งหมดจาก Cloud Firestore และจะส่งต่อเอกสารทั้งหมดไปยังฟังก์ชัน renderer.display
  • เมื่อลบเอกสาร change.type จะเท่ากับ removed ดังนั้นในกรณีนี้ เราจะเรียกฟังก์ชันที่นำร้านอาหารออกจาก UI

เมื่อเราเริ่มใช้ทั้ง 2 วิธีแล้ว ให้รีเฟรชแอปและยืนยันว่าร้านอาหารที่เราเห็นก่อนหน้านี้ในคอนโซล Firebase จะปรากฏในแอป หากคุณกรอกข้อมูลในส่วนนี้สำเร็จ แสดงว่าแอปกำลังอ่านและเขียนข้อมูลด้วย Cloud Firestore แล้ว

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

img5.png

8. ข้อมูล Get()

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

เราต้องการใช้เมธอดที่ทริกเกอร์เมื่อผู้ใช้คลิกร้านอาหารหนึ่งๆ ในแอปของคุณ

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. หาฟังก์ชัน FriendlyEats.prototype.getRestaurant
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

หลังจากคุณใช้วิธีการนี้แล้ว คุณจะสามารถดูหน้าของร้านอาหารแต่ละแห่งได้ เพียงคลิกที่ร้านอาหารในรายการ แล้วคุณจะเห็นหน้ารายละเอียดของร้านอาหารนั้น ดังนี้

img1.png

ขณะนี้คุณยังเพิ่มการให้คะแนนไม่ได้เนื่องจากเรายังต้องใช้การเพิ่มการให้คะแนนใน Codelab อีกในภายหลัง

9. จัดเรียงและกรองข้อมูล

ปัจจุบันแอปของเราแสดงรายการร้านอาหาร แต่ไม่มีวิธีให้ผู้ใช้กรองตามความต้องการได้ ในส่วนนี้ คุณจะได้ใช้การค้นหาขั้นสูงของ Cloud Firestore เพื่อเปิดใช้การกรอง

ต่อไปนี้คือตัวอย่างคำค้นหาง่ายๆ ในการดึงข้อมูลร้านอาหาร Dim Sum ทั้งหมด

var filteredQuery = query.where('category', '==', 'Dim Sum')

ตามที่ชื่อบอกไว้ เมธอด where() จะทำให้การค้นหาของเราดาวน์โหลดเฉพาะสมาชิกของคอลเล็กชันซึ่งมีฟิลด์ที่เป็นไปตามข้อจำกัดที่เราตั้งไว้ ในกรณีนี้ ระบบจะดาวน์โหลดเฉพาะร้านอาหารที่มีcategoryเป็นDim Sum

ในแอปของเรา ผู้ใช้สามารถเชื่อมโยงตัวกรองหลายรายการเพื่อสร้างข้อความค้นหาที่เฉพาะเจาะจง เช่น "พิซซ่าในเชียงใหม่" หรือ "อาหารทะเลในเชียงใหม่เรียงลำดับตามความนิยม"

เราจะสร้างเมธอดขึ้นมาจากคำค้นหา ซึ่งจะกรองร้านอาหารของเราตามเกณฑ์ต่างๆ ที่ผู้ใช้ของเราเลือก

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. หาฟังก์ชัน FriendlyEats.prototype.getFilteredRestaurants
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

โค้ดด้านบนจะเพิ่มตัวกรอง where หลายรายการและวลี orderBy รายการเดียวเพื่อสร้างการค้นหาแบบผสมตามข้อมูลจากผู้ใช้ ตอนนี้ข้อความค้นหาของเราจะแสดงร้านอาหารที่ตรงกับความต้องการของผู้ใช้เท่านั้น

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

The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...

ข้อผิดพลาดเหล่านี้เกิดขึ้นเนื่องจาก Cloud Firestore ต้องใช้ดัชนีสำหรับการค้นหาแบบผสมส่วนใหญ่ การกำหนดให้ดัชนีในการค้นหาช่วยให้ Cloud Firestore ทำงานได้อย่างรวดเร็วในระดับใหญ่

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

10. ทำให้ดัชนีใช้งานได้

หากเราไม่ต้องการสำรวจทุกเส้นทางในแอปและไปตามลิงก์การสร้างดัชนีแต่ละรายการ เราสามารถทำให้ดัชนีหลายรายการใช้งานได้พร้อมกันได้ง่ายๆ โดยใช้ Firebase CLI

  1. คุณจะเห็นไฟล์ firestore.indexes.json ในไดเรกทอรีในเครื่องที่ดาวน์โหลดมา

ไฟล์นี้อธิบายถึงดัชนีทั้งหมดที่จำเป็นสำหรับการใช้ตัวกรองต่างๆ ร่วมกัน

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. ทำให้ดัชนีเหล่านี้ใช้งานได้ด้วยคำสั่งต่อไปนี้
firebase deploy --only firestore:indexes

หลังจากผ่านไป 2-3 นาที ดัชนีของคุณจะเผยแพร่และข้อความแสดงข้อผิดพลาดจะหายไป

11. เขียนข้อมูลในธุรกรรม

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

แอปของเราจะมีผู้ใช้จำนวนมากที่ต้องการเพิ่มคะแนนให้ร้านอาหาร เราจึงต้องประสานงานการอ่านและเขียนหลายครั้ง คุณต้องส่งรีวิวก่อน จากนั้นก็อัปเดตคะแนน count และ average rating ของร้านอาหาร หากข้อมูลใดชุดหนึ่งล้มเหลว แต่อย่างใดอย่างหนึ่ง เราจะอยู่ในสถานะที่ไม่สอดคล้องกัน ข้อมูลในฐานข้อมูลส่วนหนึ่งไม่ตรงกับข้อมูลอีกส่วนหนึ่ง

โชคดีที่ Cloud Firestore มีฟังก์ชันการทำธุรกรรมที่ทำให้เราสามารถอ่านและเขียนหลายครั้งในการทำงานแบบอะตอมเดียวเพื่อให้แน่ใจว่าข้อมูลของเรามีความสอดคล้องกัน

  1. กลับไปที่ไฟล์ scripts/FriendlyEats.Data.js
  2. หาฟังก์ชัน FriendlyEats.prototype.addRating
  3. แทนที่ฟังก์ชันทั้งหมดด้วยโค้ดต่อไปนี้

friendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

ในบล็อกด้านบน เราจะเรียกให้ธุรกรรมอัปเดตค่าที่เป็นตัวเลขของ avgRating และ numRatings ในเอกสารร้านอาหาร ในขณะเดียวกัน เราก็เพิ่ม rating ใหม่ลงในคอลเล็กชันย่อย ratings

12. รักษาความปลอดภัยให้ข้อมูลของคุณ

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

  1. ในส่วน Build ของคอนโซล Firebase ให้คลิก Firestore Database
  2. คลิกแท็บกฎในส่วน Cloud Firestore (หรือคลิกที่นี่เพื่อไปที่นั่นโดยตรง)
  3. แทนที่ค่าเริ่มต้นด้วยกฎต่อไปนี้ แล้วคลิกเผยแพร่

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data) 
      && (key in request.resource.data) 
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys()) 
                    && unchanged("name");
      
      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

กฎเหล่านี้จะจำกัดการเข้าถึงเพื่อให้มั่นใจว่าไคลเอ็นต์จะทำการเปลี่ยนแปลงที่ปลอดภัยเท่านั้น เช่น

  • การอัปเดตเอกสารร้านอาหารจะเปลี่ยนแปลงการให้คะแนนเท่านั้น ไม่ได้เปลี่ยนแปลงชื่อหรือข้อมูลอื่นๆ ที่เปลี่ยนแปลงไม่ได้
  • คุณสร้างการจัดประเภทได้ก็ต่อเมื่อรหัสผู้ใช้ตรงกับผู้ใช้ที่ลงชื่อเข้าใช้ ซึ่งเป็นการป้องกันการปลอมแปลง

อีกวิธีคือการใช้คอนโซล Firebase คุณสามารถใช้ Firebase CLI เพื่อทำให้กฎใช้งานได้ในโปรเจ็กต์ Firebase ไฟล์ firestore.rules ในไดเรกทอรีการทำงานมีกฎจากด้านบนอยู่แล้ว หากต้องการปรับใช้กฎเหล่านี้จากระบบไฟล์ในเครื่อง (แทนการใช้คอนโซล Firebase) คุณจะต้องเรียกใช้คำสั่งต่อไปนี้

firebase deploy --only firestore:rules

13. บทสรุป

ใน Codelab นี้ คุณได้เรียนรู้วิธีอ่านและเขียนขั้นพื้นฐานและขั้นสูงด้วย Cloud Firestore ตลอดจนวิธีรักษาความปลอดภัยการเข้าถึงข้อมูลด้วยกฎความปลอดภัย คุณสามารถค้นหาโซลูชันแบบเต็มได้ในที่เก็บ Quickstarts-js

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Cloud Firestore ได้ที่แหล่งข้อมูลต่อไปนี้

14. [ไม่บังคับ] บังคับใช้ด้วย App Check

App Check ของ Firebase ให้การปกป้องด้วยการช่วยตรวจสอบและป้องกันการเข้าชมแอปที่ไม่ต้องการ ในขั้นตอนนี้ คุณจะรักษาความปลอดภัยในการเข้าถึงบริการได้ด้วยการเพิ่ม App Check ด้วย reCAPTCHA Enterprise

ก่อนอื่น คุณจะต้องเปิดใช้ App Check และ reCAPTCHA

การเปิดใช้ reCaptcha Enterprise

  1. ใน Cloud Console ให้ค้นหาและเลือก reCaptcha Enterprise ในส่วนความปลอดภัย
  2. เปิดใช้บริการตามข้อความแจ้ง แล้วคลิกสร้างคีย์
  3. ป้อนชื่อที่แสดงตามข้อความแจ้ง แล้วเลือกเว็บไซต์เป็นประเภทแพลตฟอร์ม
  4. เพิ่ม URL ที่ทำให้ใช้งานได้แล้วลงในรายการโดเมน และตรวจสอบว่า "ใช้คำถามในช่องทำเครื่องหมาย" มีการไม่ได้เลือกอยู่
  5. คลิกสร้างคีย์และจัดเก็บคีย์ที่สร้างขึ้นไว้ที่ใดก็ได้เพื่อเก็บรักษาคีย์เอาไว้ ซึ่งคุณจะต้องใช้ภายหลังในขั้นตอนนี้

การเปิดใช้ App Check

  1. ในคอนโซล Firebase ให้ค้นหาส่วนสร้างในแผงด้านซ้าย
  2. คลิกการตรวจสอบแอป แล้วคลิกปุ่มเริ่มต้นใช้งาน (หรือเปลี่ยนเส้นทางไปยัง คอนโซลโดยตรง)
  3. คลิกลงทะเบียน แล้วป้อนคีย์ reCAPTCHA Enterprise เมื่อระบบแจ้ง แล้วคลิกบันทึก
  4. ในมุมมอง API ให้เลือกพื้นที่เก็บข้อมูล แล้วคลิกบังคับใช้ ให้ทำแบบเดียวกันนี้กับ Cloud Firestore

ควรบังคับใช้ App Check แล้ว รีเฟรชแอปและลองสร้าง/ดูร้านอาหาร คุณควรได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

ซึ่งหมายความว่า App Check จะบล็อกคำขอที่ยังไม่ได้รับการตรวจสอบโดยค่าเริ่มต้น ตอนนี้มาเพิ่มการตรวจสอบลงในแอปกัน

ไปที่ไฟล์ friendlyEats.View.js และอัปเดตฟังก์ชัน initAppCheck และเพิ่มคีย์ reCAPTCHA เพื่อเริ่มต้น App Check

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

อินสแตนซ์ appCheck เริ่มต้นด้วย ReCaptchaEnterpriseProvider ด้วยคีย์ของคุณ และ isTokenAutoRefreshEnabled จะอนุญาตให้โทเค็นรีเฟรชอัตโนมัติในแอป

หากต้องการเปิดใช้การทดสอบในเครื่อง ให้หาส่วนที่แอปเริ่มต้นในไฟล์ friendlyEats.js แล้วเพิ่มบรรทัดต่อไปนี้ลงในฟังก์ชัน FriendlyEats.prototype.initAppCheck

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

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

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

จากนั้นไปที่มุมมองแอปของ App Check ในคอนโซล Firebase

คลิกเมนูรายการเพิ่มเติม แล้วเลือกจัดการโทเค็นการแก้ไขข้อบกพร่อง

จากนั้นคลิกเพิ่มโทเค็นการแก้ไขข้อบกพร่อง และวางโทเค็นการแก้ไขข้อบกพร่องจากคอนโซลตามข้อความแจ้ง

ยินดีด้วย ตอนนี้ App Check ควรใช้งานได้ในแอปของคุณแล้ว