0

[Series Thực Chiến E-commerce] Bài 20: "Chốt sổ" Điểm số - Thuật toán tính Tổng sao trung bình (Total Ratings)

Chào anh em!

Tiếp nối nhịp đập của Bài 19, chúng ta đã giải quyết êm xuôi việc cho phép khách hàng sửa hoặc thêm mới bình luận vào mảng ratings. Nhưng mình đã để ngỏ một "quả bom nổ chậm": Cái trường totalRatings (Điểm đánh giá trung bình) của sản phẩm vẫn chưa hề nhúc nhích!

Nếu anh em đẩy việc tính toán này xuống cho Frontend thì thật sự là ác mộng. Tưởng tượng sản phẩm có 10.000 lượt đánh giá, Frontend phải gọi API tải nguyên cục data khổng lồ đó về máy khách hàng chỉ để dùng hàm reduce tính ra con số 4.5 sao? Điện thoại khách nóng ran, giật lag, app thì chậm rì.

Tất cả những logic tính toán nặng nhọc này bắt buộc phải do Backend gánh. Hôm nay, mình sẽ ghép nốt mảnh ghép cuối cùng vào hàm Đánh giá hôm trước nhé!

1. Thuật toán "Chốt sổ" (Controller)

Logic ở đây cực kỳ đơn giản: Điểm trung bình = Tổng số sao / Tổng số lượt đánh giá. Tuy nhiên, để con số hiển thị đẹp mắt (ví dụ: 4.5 thay vì 4.533333333), chúng ta sẽ dùng một mẹo Toán học nhỏ với hàm Math.round().

Anh em mở lại file controllers/product.js của Bài 19, lướt xuống dưới cùng của hàm ratings (ngay trước lúc return res.status(200)...) và dán đoạn code này vào nối tiếp nhé:

// ... (Đoạn code if/else xử lý push hoặc update mảng ratings của bài 19)

  // BƯỚC 4: TÍNH TOÁN LẠI ĐIỂM TRUNG BÌNH (TOTAL RATINGS) LÚC RUNTIME
  
  // Lấy lại dữ liệu sản phẩm mới nhất từ DB
  const updatedProduct = await Product.findById(pid); 
  
  // Đếm xem hiện tại có tổng cộng bao nhiêu người đã đánh giá
  const ratingCount = updatedProduct.ratings.length; 
  
  // Dùng hàm reduce của Javascript để cộng dồn tất cả các 'star' trong mảng lại
  const sumRatings = updatedProduct.ratings.reduce((sum, el) => sum + el.star, 0); 
  
  // Tính điểm đánh giá trung bình và LÀM TRÒN 1 CHỮ SỐ THẬP PHÂN
  // 💡 Mẹo Toán học: Nhân 10 -> Làm tròn số nguyên -> Chia lại cho 10
  // Ví dụ: (4.3333 * 10) = 43.333 -> round() thành 43 -> chia 10 = 4.3 
  updatedProduct.totalRatings = Math.round((sumRatings * 10) / ratingCount) / 10; 
  
  // Lưu kết quả chốt sổ vào Database
  await updatedProduct.save(); 

  // Trả về kết quả cho Frontend
  return res.status(200).json({
    status: true,
    message: 'Ghi nhận đánh giá thành công và đã cập nhật điểm số!',
    updatedProduct // Trả về luôn để Frontend update giao diện tức thì
  });

🔥 Góc nhìn Thực chiến (Optimization Tip): Đoạn code trên chạy rất chuẩn, nhưng nếu anh em muốn tối ưu hệ thống (giảm tải cho Database), anh em có thể để ý dòng await Product.findById(pid);. Thực tế, ở phần đầu của Bài 19, anh em đã findById để lấy biến ratingProduct lên, rồi tiến hành push/update.save() nó rồi. Vì vậy, mảng ratings trong biến ratingProduct lúc đó đã là bản cập nhật mới nhất. Anh em hoàn toàn có thể lôi luôn ratingProduct ra tính reduce tiếp mà không cần tốn thêm một nhịp query DB gọi updatedProduct nữa. Hãy thử vọc vạch tối ưu lại chỗ này xem sao nhé, cảm giác tối ưu được vài chục mili-giây nó "phê" lắm!

2. Về phần Router

Phần Router thì không có gì thay đổi, vì đoạn code này chỉ là logic chạy ngầm bên trong API Đánh giá mà chúng ta đã khai báo từ bài trước:

router.put('/ratings', [verifyAccessToken], ctrls.ratings);

Lời kết cho Module Sản Phẩm (Phần 1) Đến đây, việc đăng bán sản phẩm, cho khách xem, tìm kiếm, đánh giá và tính điểm đã hoàn thiện thành một khối cực kỳ vững chắc.

Thế nhưng, nếu vào một shop mà Điện thoại, Quần áo, Nước hoa, Bỉm sữa... vứt lộn xộn vào chung một chỗ thì khách biết đường nào mà lần? Hàng hóa cần phải được phân loại rõ ràng. Chúng ta cần một danh mục để gom nhóm chúng lại.

Đó chính là nhiệm vụ của bài tiếp theo: Lession 21: Add Categories (Thêm Danh mục Sản phẩm).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí