我想实现用户!反馈!,用户可以看到信息,谁喜欢他的照片,谁开始关注等等.我实际上不知道在这种情况下我应该如何组织数据库的结构.
我的用户节点快照:
我的帖子节点快照:
我可以看到,我有下一个选项 – 我应该将所有与用户相关联的操作保存到内部节点反馈中的节点.但是我怎样才能保持同步呢?例如,有人可以关注我的用户,我会将其添加到此节点,用户将取消关注,但记录仍然存在.我认为,这是错误的方式.
我实际上没有其他想法,我找不到任何相关的东西.
任何建议和解决方案都非常感谢.
编辑:我需要了解,如何实现这个类似Instagram的应用程序选项卡:
如何从节点中检索数据?
UPD:我的例子中的数据库架构很糟糕(旧问题).小心(10.11.2017).
解决方法
在为Firebase构建数据时,要遵循两个非常重要的原则:
>您应该以您想要的方式保存数据.
>您应该尽可能保持数据结构平坦 – 避免嵌套.
第1点是因为Firebase不是关系数据库.这意味着我们需要保持查询简单以实现性能.进行复杂查询可能需要向Firebase发出许多请求.
第2点是because of the way Firebase’s query model works:如果您观察一个节点,您也会获得该节点的所有子节点.这意味着,如果您的数据是深度嵌套的,那么您可能会获得许多不需要的数据.
因此,考虑到这些原则,让我们来看看你的情况.我们有用户,他们有照片.这些是数据库的两个主要实体.
我可以看到,目前,您将照片保留为用户的属性.如果您希望能够快速查询用户的照片(请记住第1点),这是一个很好的方法.但是,如果我们希望用户能够“喜欢”照片,那么照片应该不仅仅是指向其Firebase存储位置的链接:它还应该包含其他属性,例如哪些用户已经将其收藏.此属性应为用户ID数组.此外,对于每个用户,您都希望存储哪些照片是该用户的收藏夹.这可能看起来像数据重复,但在使用Firebase时,it’s OK to duplicate some data if it’ll lead to simpler queries.
因此,使用上面示例中的数据索引,您的每个照片应如下所示:
{ id: /* some ID */,location: /* Firebase Storage URL */,favorited_by: { /* some user ID */: true /* this value doesn't matter */,/* another user ID */: true,},/* other properties... */ }
您的用户应该有一个列出照片ID的收藏夹属性.现在,由于每张照片都有一个“拥有”它的用户,我们不需要为每张照片都有唯一的ID,我们只需要确保没有用户有两张具有相同ID的照片.这样,我们可以通过其用户ID和照片ID的组合来参考照片.
当然,请记住第1点:如果您希望能够在不获取用户照片的情况下获取用户信息,则应在根对象上为照片添加不同的属性,而不是将照片与用户关联.但是,对于这个答案,我会尽力坚持你现在的模型.
根据我上面所说的,用户的收藏夹属性将包含格式为’userId / photoId’的值数组.因此,例如,如果用户喜欢具有ID“CN7v0A2”的用户的ID为“3A”的照片,则他们的收藏夹阵列将保持值“CN7v0A2 / 3A”.这结束了我们的收藏结构.
现在,让我们看看你提到的一些操作在这种结构下会是什么样子:
>用户收藏照片:
>我们获取照片所有者的用户ID
>我们获得了偏好照片的用户的用户ID
>我们得到照片的ID
>我们将喜欢ID的用户添加到照片的favorited_by数组中
>我们将photoOwnerID“/”photoID添加到收藏用户的收藏夹阵列中
如果用户稍后不喜欢这张照片,我们就是反过来:我们从用户的收藏中删除了photoOwnerID“/”photoID,我们从照片的favorited_by属性中删除了收藏用户的ID.
这种逻辑足以实现喜欢,收藏和跟随. follower / liker / favoriter和followee / likee / favoritee都应该保持对另一方ID的引用,你应该封装“喜欢/喜欢/关注”和“不喜欢/喜欢/取消关注”操作,以便他们保留该数据库每次状态都是一致的(这样,您就不会遇到任何问题,例如您提到的情况,用户取消关注用户但数据库仍保留“跟踪”记录).
最后,假设您有一个User模型类,这里有一些关于如何进行“收藏”和“不喜欢”操作的代码:
extension User { func follow(_ otherUser: User) { let ref = FIRDatabase.database().reference() ref.child("users/\(otherUser.userId)/followers/") .child(self.userId).setValue(true) ref.child("user/\(self.userId)/following/") .child(otherUser.userId).setValue(true) } func unfollow(_ otherUser: User) { let ref = FIRDatabase.database().reference() ref.child("users/\(otherUser.userId)/followers/") .child(self.userId).remove() ref.child("user/\(self.userId)/following/") .child(otherUser.userId).remove() } }
使用此模型,您可以获取查询该用户的关注者属性并在生成的快照上使用.keys()方法的用户的所有关注者用户ID,反之对于给定用户关注的用户.
添加内容:我们可以进一步构建此结构,以便添加简单的操作日志记录,这似乎是您希望用户在“反馈”选项卡中可用的内容.假设我们有一系列操作,例如喜欢,收藏和关注,我们希望显示反馈.
我们将再次遵循第1点:为了构建反馈数据,最好以我们想要检索它的方式存储这些数据.在这种情况下,我们通常会向用户显示他们自己的反馈数据.这意味着我们应该按用户ID存储反馈数据.此外,在第2点之后,我们应该将反馈数据存储为自己的表,而不是将其添加到用户记录中.所以我们应该在我们的根对象上创建一个新表,对于每个用户ID,我们存储一个反馈条目列表.
它应该看起来像这样:
{ Feedback: { userId1: /* this would be an actual user ID */ { autoId1: /* generated using Firebase childByAutoId */ { type: 'follow',from: /* follower ID */,timestamp: /* Unix time */,autoId2: { type: 'favorite',from: /* ID of the user who favorited the photo */ on: /* photo ID */ timestamp: /* Unix time */ },/* ...other Feedback items */ },userId2: { /* ...Feedback items for other user */ },/* ...other user's entries */ },/* other top-level tables */ }
另外,我们需要更改收藏夹/喜欢/关注表.之前,我们只是存储了真实信号,以表示有人喜欢或喜欢照片或跟随用户.但由于我们使用的价值无关紧要,因为我们只检查密钥以找到用户喜欢或喜欢的内容以及他们关注的对象,我们可以开始使用类似/喜欢/关注的条目ID.所以我们会改变我们的“跟随”逻辑:
extension User { func makeFollowFeedbackEntry() -> [String: Any] { return [ "type": "follow","from": self.userId,"timestamp": UInt64(Date().timeIntervalSince1970) ] } func follow(_ otherUser: User) { let otherId = otherUser.userId let ref = FIRDatabase.database().reference() let FeedbackRef = ref.child("Feedback/\(otherId)").childByAutoId() let FeedbackEntry = makeFollowFeedbackEntry(for: otherId) FeedbackRef.setValue(FeedbackEntry) FeedbackRef.setPriority(UInt64.max - FeedbackEntry["timestamp"]) let FeedbackKey = FeedbackRef.key ref.child("users/\(otherUser.userId)/followers/") .child(self.userId).setValue(FeedbackKey) ref.child("user/\(self.userId)/following/") .child(otherUser.userId).setValue(FeedbackKey) } func unfollow(_ otherUser: User,completionHandler: () -> ()) { let ref = FIRDatabase.database().reference() let followerRef = ref.child("users/\(otherUser.userId)/followers/") .child(self.userId) let followingRef = ref.child("user/\(self.userId)/following/") .child(otherUser.userId) followerRef.observeSingleEvent(of: .value,with: { snapshot in if let followFeedbackKey = snapshot.value! as? String { // we have an associated follow entry,delete it ref.child("Feedback").child(otherUser.userId + "/" + followFeedbackKey).remove() } // if the key wasn't a string,there is no follow entry followerRef.remove() followingRef.remove() completionHandler() }) } }
这样,我们只需通过阅读带有该用户ID的“反馈”表条目就可以获得用户的“反馈”,并且由于我们使用了setPriority,它将首先按最近的条目排序,这意味着我们可以使用Firebase的queryLimited(toFirst) :)只获得最新的反馈.当用户取消关注时,我们可以轻松删除反馈条目,该反馈条目通知用户他们已被跟踪.您还可以轻松添加额外的字段来存储是否已读取反馈条目等.
即使您之前使用其他模型(将“followerId”设置为true),您仍然可以对新条目使用反馈条目,只需检查值为“followerId”是否为字符串,如上所述:)
您可以使用相同的逻辑,只需使用条目中的不同字段来处理收藏夹和喜欢.当您处理它以向用户显示数据时,只需检查“类型”字段中的字符串以了解要显示的反馈类型.最后,应该很容易为每个反馈条目添加额外的字段,以便存储,用户是否已经看到反馈.