【问题标题】:Firebase -how to run a calculation using a Cloud FunctionFirebase - 如何使用云函数运行计算
【发布时间】:2021-05-06 16:41:54
【问题描述】:

我有一个带有星级系统的美食应用。每次用户选择评分/星级时,我都会将他们的评分发送到数据库,然后运行 ​​firebase transaction 来更新留下评分的用户总数。

let theirRating: Double = // whatEverStarRatingTheySelected
let dict = [currentUserId: theirRating]

let reviewsRef = Database.database().reference().child("reviews").child(postId)
reviewsRef.updateChildValues(dict) { (error, ref) in

    let postsRef = Database.database().reference().child("posts").child(postId).child("totalUsersCount")
    postsRef.runTransactionBlock({ (mutableData: MutableData) -> TransactionResult in
        // ...
    })
}

在显示实际评分时,我拉出具有 totalUsersCount 作为属性的帖子,我提取所有用户的实际评分,将它们加在一起,然后将两个数字输入算法以吐出实际评分。我在客户端上完成所有这些。我怎样才能使用类似于this answer 的云函数来执行相同的操作“我将所有用户的实际评分,将它们加在一起”?

Database.database().reference().child("posts").child(postId).observeSingleEvent(of: .value, with: { (snapshot) in
 
     guard let dict = snapshot.value as? [String:Any] else { return }

     let post = Post(dict: dict)
 
     let totalUsers = post.totalUsersCount

     Database.database().reference().child("reviews").child(postId).observeSingleEvent(of: .value, with: { (snapshot) in
      
          var ratingSum = 0.0
      
          // *** how can I do this part using a Cloud Function so that way I can just read this number as a property on the post instead of doing all of this on the client ***
          for review in (snapshot.children.allObjects as! [DataSnapshot]) {
              guard let rating = review.value as? Double else { continue }
              ratingSum += rating
          }
      
          let starRatingToDisplay = self.algoToComputeStarRating(totalUsersCount: totalUsersCount, ratingsSum: ratingSum)
     })
 })

这不是关于 algoToComputeStarRating 的问题,我可以在客户端上进行。我想使用云函数将所有用户的评分加在一起,然后将该结果作为属性添加到帖子中。这样,当我拉出帖子时,我所要做的就是:

Database.database().reference().child("posts").child(postId).observeSingleEvent(of: .value, with: { (snapshot) in

    guard let dict = snapshot.value as? [String:Any] else { return }

    let post = Post(dict: dict)
         
    let totalUsersCount = post.totalUsersCount
    let ratingsSum = post.ratingsSum
    
    let starRatingToDisplay = self.algoToComputeStarRating(totalUsersCount: totalUsersCount, ratingsSum: ratingSum)
})

数据库结构:

@posts
   @postId
      -postId: "..."
      -userId: "..."
      -totalUsersCount: 22
      -ratingsSum: 75 // this should be the result from the cloud function

@reviews
    @postId
       -uid_1: theirRating
       -uid_2: theirRating
       // ...
       -uid_22: theirRating

这是我迄今为止尝试过的:

exports.calculateTotalRating = functions.https.onRequest((data, response) => {

    const postId = data.postId;
    
    const totalUsersCtRef = admin.database().ref('/posts/' + postId + '/' + 'totalUsersCt');
    const postsRef = admin.database().ref('/posts/' + postId);

    admin.database().ref('reviews').child(postId).once('value', snapshot => {

        if (snapshot.exists()) {

            var ratingsSum = 0.0;

            snapshot.forEach(function(child) {

                ratingsSum += child().val()
            })
            .then(() => { 
            
                return postsRef.set({ "ratingsSum": ratingsSum})          
            })
            .then(() => { 
            
                return totalUsersCtRef.set(admin.database.ServerValue.increment(1));                
            }) 
            .catch((error) => {
                console.log('ERROR - calculateTotalRating() Failed: ', error);
            });
        }
    });
});

【问题讨论】:

  • 这种情况太常见了,firebase 文档甚至有一本手册:firebase.google.com/docs/firestore/solutions/counters
  • 我不是原生 Javascript/Node 开发人员。如果这是 Swift,我可以轻而易举地解决这个问题,但我必须做大量研究才能在 Javascript 中解决这个问题,这就是我问这个问题的原因。

标签: ios swift firebase-realtime-database google-cloud-functions


【解决方案1】:

我使用这个answer

exports.calculateTotalRating = functions.https.onRequest((data, response) => {

    const postId = data.postId;
    
    const postsRef = admin.database().ref('/posts/' + postId);
    const totalUsersCtRef = admin.database().ref('/posts/' + postId + '/' + 'totalUsersCt');

    var ratingsSum = 0.0;

    admin.database().ref('reviews').child(postId).once('value', snapshot => {

        if (snapshot.exists()) {

            snapshot.forEach((child) => {

                ratingsSum += child().val()
            })
            console.log('ratingsSum: ', ratingsSum);

            return postsRef.update({ "ratingsSum": ratingsSum }).then(() => { 
                return totalUsersCtRef.set(admin.database.ServerValue.increment(1));                
            })
            .catch((error) => {
                console.log('ERROR - calculateTotalRating() Failed: ', error);
            });   
        }
    });
});

【讨论】:

    【解决方案2】:

    我认为这应该为您提供一个良好的开端。我使用了触发器,因此所有更新都会自动发生,您的快速听众将立即获得新的评级。 :) 虽然我不确定您的 let post = Post(dict: dict) 将如何解析 [String: Any] 字典。

    
    //We are adding a event listner that will be triggered when any child under the reviews key is modified. Since the key for updated review can be anything we use wildcards `{autoId}`.
    exports.calculateTotalRating = functions.database
      .ref("reviews/{autoId}")
      .onUpdate((snapshot, context) => {
    
    
        if (snapshot.exists()) { //For safety check that the snapshot has a value
          const ratingsDict = snapshot.val(); //The updated reviews object you set using `reviewsRef.updateChildValues(dict)`
    
          const autoId = context.params.autoId; //Getting the wildcard postId so we can use it later 
    
          //Added due to error in understanding the structure of review object. Can be ignored. 
          //delete ratingsDict("totalUserCount");
          //delete ratingsDict("ratingsSum");
    
          const totalUsers = Object.keys(ratingsDict).length; //Counting how many reviews are there in the ratingsDict 
    
          //Sum over all the values in ratingsDict. This approach is supposedly slower, but was oneliner so used it. Feel free to modify this with a more efficient way to sum values once you are more comfortable with cloud functions.
          const ratingsSum = Object.keys(ratingsDict).reduce(
            (sum, key) => sum + parseFloat(ratingsDict[key] || 0),
            0
          );  
    
          //Saving the computed properties.
          ratingsDict["totalUserCount"] = totalUsers;
          ratingsDict["ratingsSum"] = ratingsSum;
    
          //Updating posts with new reviews and calculated properties
          return functions.database.ref("posts").child(autoId).set(ratingsDict);
        }
      });
    

    我觉得你是新手,所以你可以按照这个指南to create cloud functions。在您应用的顶级目录中,只需运行firebase init functions 并从Use an existing project 中选择您的项目。这应该为您创建一个带有 index.js 文件的函数文件夹,您可以在其中添加此代码。然后运行firebase deploy --only functions:calculateTotalRating

    【讨论】:

    • 谢谢,让我将该帖子添加到问题中。给我一秒钟
    • 我认为不需要。你遵循逻辑吗?我可能稍微误解了您的评论对象。并且觉得这些行是不需要的,` delete ratingsDict("totalUserCount");删除 ratingsDict("ratingsSum");`.但其余的应该可以工作。
    • 感谢您的帮助。我添加了到目前为止我想出的exports。这会让你更清楚吗?我不是原生 Javascript,所以我需要花一点时间来回答您的问题。
    • 我绝对是 Cloud Functions 的新手,我之前只使用过一次。我问了一个问题,然后添加了一个关于如何设置它们的答案stackoverflow.com/a/65875956/4833705。关于如何遍历评论然后将结果更新到 posts/postId/ratingsSum 属性的 Javascript 部分是我迷路的地方
    • 当用户发帖时,我需要知道是哪个用户发的。 userID 指的是该用户。我会在几个小时内尝试你的代码。突然出现了一些对我来说非常重要的事情。尽管我想将实际评级保留在单独的参考文献中,但您对 2 个字段是正确的。 ratingSum 只是所有实际评分加在一起的总和。
    猜你喜欢
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    • 2021-10-09
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多