我正在开发一个用于清洁服务的应用程序。在此应用程序中,员工(清洁工)可以读取由多个客户(用户)完成的作业(预订)列表。
所有清洁工都可以在“用户”节点中读取所有预订。最初,当用户将预订保存在数据库中时,该键claimed:的值为 “false”,这意味着清洁工尚未声明该键。
claimed:
“false”
每当清洁工想要索取列表中存在的工作时,他将必须触摸按钮,该按钮将向Firebase数据库发出请求以将key的值修改claimed为trueat路径。 /Users/UID/bookings/bookingNumber
claimed
true
/Users/UID/bookings/bookingNumber
一次只能允许一名清洁工修改claimed钥匙的价值。如果允许多个清理者修改claimed密钥的值,其他清理者最终将要求相同的工作。我们不希望这种情况发生。
此外,在清理者将claimedkey 的值修改为之后true,我们将需要再次请求路径CLeaners/UID/bookings/bookingNumber,以保存他刚刚在清理者节点中声明的预订。 -根据firebase的文档,每当我们希望一次仅由一个请求修改一个资源时,便会使用事务,如果有多个并发请求试图写入同一资源,则其中一个会成功。但是使用事务的问题在于,它 只能 写入 一个路径 ,而不能写入 多个路径 。
CLeaners/UID/bookings/bookingNumber
我如何确保即使有多个用户可以读取此路径 /Users/UID/bookings/bookingNumber,一次也只能更新一个用户?如果写入成功,则进一步写入第二条路径 Cleaners/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....将不会被评估,并且第二次写入将不会成功。
{(error, committed,snapshot) in....
另一种情况是:执行第一次写入, 1.在客户端应用程序中收到响应 2. 在客户端应用程序中尚未收到响应 ,用户立即退出了该应用程序。在这种情况下,将永远不会执行第二次写入,因为在完成处理程序中接收到(或未接收到)响应之后,尚未评估任何代码,因此我的数据库处于不一致状态。
从Firebase文档:
事务不会在应用程序重启后持续存在 即使启用了持久性,事务也不会在应用程序重启后持久化。因此,您不能依赖脱机完成的事务提交到Firebase实时数据库。
事务不会在应用程序重启后持续存在
即使启用了持久性,事务也不会在应用程序重启后持久化。因此,您不能依赖脱机完成的事务提交到Firebase实时数据库。
如果是这样,我该怎么做?我在Google https://firebase.googleblog.com/2015/09/introducing- multi-location-updates- and_86.html的博客中没有看到任何示例 。我确实知道您可以原子地写多个节点,但是我想以事务方式编写。我试图在else子句中写入两个节点,但在此行收到警告let updated = updateInUsersAndCleaners as? FIRMutableData
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
在Firebase文档的“ 启用脱机功能 ”部分中,指定了:
事务不会在应用程序重新启动之间 持久化即使启用了持久性,事务也不会在应用程序重新启动之间持久化。 因此,您不能依赖脱机完成的事务提交到Firebase实时数据库。
因此: 1.无法在客户端使用Firebase事务来更新两个或更多路径上的值。 2.使用完成回调执行第二次写入是不可行的,因为客户端可以在从Firebase服务器接收到完成处理程序中的响应之前重新启动应用程序,从而使数据库处于不一致状态。
我假设我唯一的选择是通过Firebase文档中指定的条件在REST上使用条件请求,以在第一路径上进行事务性更新数据,并进一步使用已在Firebase数据库中的第一路径上写入的数据来更新第二路径 。 这将实现Firebase框架为IOS客户端提供的相同功能。
root/users/bookings/4875383
什么是ETAG值? :(唯一标识符,每次在发出GET请求的路径上数据发生更改时,标识符都会不同。即,如果另一个用户在我的写请求成功之前在同一路径上写资源,我的写操作将被拒绝因为该路径上的ETAG值将已经被其他用户的写操作修改过。这将使用户可以通过事务方式将数据写入路径)
root/Cleaners/bookings/4875383