一尘不染

Firebase:如何以事务方式更新多个节点?迅捷3

swift

我正在开发一个用于清洁服务的应用程序。在此应用程序中,员工(清洁工)可以读取由多个客户(用户)完成的作业(预订)列表。

所有清洁工都可以在“用户”节点中读取所有预订。最初,当用户将预订保存在数据库中时,该键claimed:的值为
“false”,这意味着清洁工尚未声明该键。

每当清洁工想要索取列表中存在的工作时,他将必须触摸按钮,该按钮将向Firebase数据库发出请求以将key的值修改claimedtrueat路径。
/Users/UID/bookings/bookingNumber

一次只能允许一名清洁工修改claimed钥匙的价值。如果允许多个清理者修改claimed密钥的值,其他清理者最终将要求相同的工作。我们不希望这种情况发生。

此外,在清理者将claimedkey
的值修改为之后true,我们将需要再次请求路径CLeaners/UID/bookings/bookingNumber,以保存他刚刚在清理者节点中声明的预订。
-根据firebase的文档,每当我们希望一次仅由一个请求修改一个资源时,便会使用事务,如果有多个并发请求试图写入同一资源,则其中一个会成功。但是使用事务的问题在于,它 只能 写入 一个路径 ,而不能写入 多个路径

我如何确保即使有多个用户可以读取此路径
/Users/UID/bookings/bookingNumber,一次也只能更新一个用户?如果写入成功,则进一步写入第二条路径
Cleaners/UID/bookings/bookingNumber

我们需要考虑到客户端的互联网连接可能断开,用户可以退出该应用程序,或者仅仅是电话在写入上述指定路径之间的任何时间意外关闭。

数据库结构如下

Root
  Cleaners
    UID
     bookings
       bookingNumber
         amount: “10”
         claimed: “true”


   Users
     UID
      otherID
        bookingNumber
          amount: “10”
          claimed: “true”

         bookingNumber
          amount: “50”
          claimed: “false”

为了避免任何覆盖,我决定使用Firebase事务。我可以将单个节点作为事务写入,但是在完成处理程序中写入第二个节点不是解决方案,因为在从服务器接收到响应之前,清洁程序的Internet连接可能会断开,或者应用程序可能会退出,因此,完成处理程序{(error, committed,snapshot) in....将不会被评估,并且第二次写入将不会成功。

另一种情况是:执行第一次写入,
1.在客户端应用程序中收到响应
2. 在客户端应用程序中尚未收到响应
,用户立即退出了该应用程序。在这种情况下,将永远不会执行第二次写入,因为在完成处理程序中接收到(或未接收到)响应之后,尚未评估任何代码,因此我的数据库处于不一致状态。


从Firebase文档:

事务不会在应用程序重启后持续存在

即使启用了持久性,事务也不会在应用程序重启后持久化。因此,您不能依赖脱机完成的事务提交到Firebase实时数据库。

  • 是否可以使用Swift中的Firebase事务写入Firebase数据库中的多个节点?

如果是这样,我该怎么做?我在Google https://firebase.googleblog.com/2015/09/introducing-
multi-location-updates-
and_86.html的博客中没有看到任何示例
。我确实知道您可以原子地写多个节点,但是我想以事务方式编写。我试图在else子句中写入两个节点,但在此行收到警告let updated = updateInUsersAndCleaners as? FIRMutableData

从“ [FIRDatabaseReference:FIRMutableData]”强制转换为不相关的类型“ FIRMutableData”始终失败

    class ClaimDetail: UIViewController,UITableViewDelegate,UITableViewDataSource {

 var valueRetrieved = [String:AnyObject]()
 var uid:String?

   @IBAction func claimJob(_ sender: Any) {

      dbRef.runTransactionBlock({ (_ currentData:FIRMutableData) -> FIRTransactionResult in

//if valueRetrieved is nil abort
  guard let val = currentData.value as? [String : AnyObject] else {
    return FIRTransactionResult.abort()
    }
          self.valueRetrieved = val

  guard let uid = FIRAuth.auth()?.currentUser?.uid else {
         return FIRTransactionResult.abort()
        }
              self.uid = uid

    for key in self.valueRetrieved.keys {
         print("key is \(key)")

   //unwrap value of 'Claimed' key
  guard let keyValue = self.valueRetrieved["Claimed"] as? String else {
              return FIRTransactionResult.abort()
        }

            //check if key value is true
               if keyValue == "true"{

                //booking already assigned, abort
                  return FIRTransactionResult.abort()

            } else {
              //write the new values to firebase
               let newData =  self.createDictionary()
                  currentData.value = newData

             let usersRef = self.dbRef.child("Users").child(FullData.finalFirebaseUserID).child(FullData.finalStripeCustomerID).child(FullData.finalBookingNumber)
            let cleanersRef = self.dbRef.child("Cleaners").child(self.uid!).child("bookings").child(FullData.finalBookingNumber)

  //Create data we want to update for both nodes
    let updateInUsersAndCleaners = [usersRef:currentData,cleanersRef:currentData]
                let updated = updateInUsersAndCleaners as? FIRMutableData
                  return FIRTransactionResult.success(withValue: updated!)

      }//end of else
}//end of for key in self

          return FIRTransactionResult.abort()
    }) {(error, committed,snapshot) in

        if let error = error {
            //display an alert with the error, ask user to try again

         self.alertText = "Booking could not be claimed, please try again."
             self.alertActionTitle = "OK"
               self.segueIdentifier = "unwindfromClaimDetailToClaim"
                  self.showAlert()

        } else if committed == true {

        self.alertText = "Booking claimed.Please check your calendar"
            self.alertActionTitle = "OK"
            self.segueIdentifier = "unwindfromClaimDetailToClaim"
               self.showAlert()
        }
    }

 }//end of claimJob button

}//end of class


  extension ClaimDetail {

//show alert to user and segue to Claim tableView
   func showAlert() {
      let alertMessage = UIAlertController(title: "", message: self.alertText, preferredStyle: .alert)
     alertMessage.addAction(UIAlertAction(title: self.alertActionTitle, style: .default, handler: { (action:UIAlertAction) in
          self.performSegue(withIdentifier: self.segueIdentifier, sender: self)
    }))
         self.present(alertMessage, animated: true,completion: nil)
 }


    //create dictionary with data received from completion handler and the new data
  func createDictionary() -> AnyObject {
     let timeStamp = Int(Date().timeIntervalSince1970)
       self.valueRetrieved["CleanerUID"] = uid as AnyObject?
         self.valueRetrieved["TimeStampBookingClaimed"] = timeStamp as AnyObject?
         self.valueRetrieved["Claimed"] = "true" as AnyObject?
          print("line 89 extension CLaim Detail")
            return self.valueRetrieved as AnyObject
      }
   } // end of extension ClaimDetail

阅读 247

收藏
2020-07-07

共1个答案

一尘不染

在Firebase文档的“
启用脱机功能 ”部分中,指定了:

事务不会在应用程序重新启动之间
持久化即使启用了持久性,事务也不会在应用程序重新启动之间持久化。
因此,您不能依赖脱机完成的事务提交到Firebase实时数据库。

因此:
1.无法在客户端使用Firebase事务来更新两个或更多路径上的值。
2.使用完成回调执行第二次写入是不可行的,因为客户端可以在从Firebase服务器接收到完成处理程序中的响应之前重新启动应用程序,从而使数据库处于不一致状态。

我假设我唯一的选择是通过Firebase文档中指定的条件在REST上使用条件请求,以在第一路径上进行事务性更新数据,并进一步使用已在Firebase数据库中的第一路径上写入的数据来更新第二路径 。
这将实现Firebase框架为IOS客户端提供的相同功能。

    1. 客户端将通过Alamofire向我的服务器发出请求(我将使用Vapor框架以利用Swift语言),一旦在Vapor服务器上收到请求,GET请求就会发送到root/users/bookings/4875383我在其中的Firebase数据库服务器将请求一个 ETAG_VALUE

什么是ETAG值?
:(唯一标识符,每次在发出GET请求的路径上数据发生更改时,标识符都会不同。即,如果另一个用户在我的写请求成功之前在同一路径上写资源,我的写操作将被拒绝因为该路径上的ETAG值将已经被其他用户的写操作修改过。这将使用户可以通过事务方式将数据写入路径)

    1. 从Firebase服务器收到一个包含ETAG_VALUE的响应。
    1. 向Firebase服务器发出PUT请求,并在标头中指定 ETag: 从先前的GET请求收到的 [ETAG_VALUE] 。如果发布到服务器的ETAG值与Firebase服务器上的值匹配,则写入将成功。如果该位置不再与ETag匹配(如果另一个用户向数据库中写入新值,则可能会发生这种情况),则请求将失败,而无需写入该位置。返回响应包括新值和ETag。
    1. 此外,现在我们可以更新at的值root/Cleaners/bookings/4875383以反映清洁工声称的工作。
2020-07-07