我有一个应用程序,用户可以喜欢照片,评论等。像Instagram这样的功能。
我想要实现用户! 反馈 !,用户可以看到信息,谁喜欢他的照片,谁开始关注等等。我实际上并不知道在这种情况下,我应该如何组织我的数据库的结构。
我的用户节点快照:
我的帖子节点快照:
正如我所看到的,我有下一个选项 - 我应该将所有与用户相关联的操作保存到内部节点Feedback
中的节点。但是我该如何保持同步呢?例如,有人可以关注我的用户,我会将其添加到此节点,用户将取消关注,但记录仍然存在。我认为,这是错误的方式。
我实际上没有其他想法,我找不到任何相关内容。
非常感谢任何建议和解决方案。
编辑:我需要了解,如何实现这个类似Instagram的应用的标签:
如何从节点检索数据?
UPD:我的示例中的数据库架构很糟糕(旧问题)。小心(10.11.2017)。
答案 0 :(得分:8)
首先,让我们考虑如何为此构建数据库:
构建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... */
}
您的用户应该有一个favorites
属性列出照片ID。现在,因为每张照片都有一个拥有"拥有"它,我们不需要为每张照片都有一个唯一的ID,我们只需要确保没有用户有两张具有相同ID的照片。这样,我们就可以通过用户ID和照片ID的组合来引用照片。
当然,请记住第1点:如果您希望在不获取用户照片的情况下获取用户信息,则应在根对象上使用不同的属性来替换照片将照片与用户关联。但是,对于这个答案,我会尝试坚持你现在的模式。
根据我上面所说的,用户的favorites
属性将包含格式'userId/photoId'
的值数组。因此,例如,如果用户收到标识为"3A"
的用户ID为"CN7v0A2"
的照片,则其favorites
数组将保留值'CN7v0A2/3A'
。我们的收藏结构就此结束。
现在,让我们来看看你提到的一些操作在这种结构下会是什么样的:
favorited_by
阵列photoOwnerID + "/" photoID
添加到收藏用户的favorites
数组如果用户稍后不喜欢这张照片,我们就是反过来:我们从用户的photoOwnerID + "/" + photoID
中删除了favorites
,我们从照片中移除了收藏用户的ID #39; s favorited_by
属性。
这种逻辑足以实现喜欢,收藏和跟随。跟随者/ liker / favoriter和followee / likee / favoritee都应该引用另一方的ID,你应该封装"喜欢/喜欢/关注"和"不像/喜欢/取消关注"操作,以便他们每次都保持该数据库状态一致(这样,您不会遇到任何问题,例如您提到的情况,用户取消关注用户但数据库仍然保持"以下&# 34;记录)。
最后,这里有一些关于你如何做"收藏"的代码。和#34;不喜欢"操作,假设您有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()
}
}
使用此模型,您可以获取用户查询该用户的followers
属性并在生成的.keys()
上使用snapshot
方法获取所有关注者用户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 */
}
此外,我们还需要更改收藏夹/喜欢/关注表格。在此之前,我们只是存储true
,以表示有人喜欢或喜欢照片或关注用户。但由于我们使用的价值无关紧要,因为我们只检查密钥以找到用户喜欢或喜欢的内容以及他们关注的对象,我们可以开始使用类似/喜欢/关注的条目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
),您仍然可以使用反馈条目进行新条目,只需检查值是否为&#34 ; followerId"就像我上面所做的那样是一个字符串:)
您可以使用相同的逻辑,只需使用条目中的不同字段来处理收藏夹和喜欢。当您处理它以向用户显示数据时,只需检查"type"
字段中的字符串以了解要显示的反馈类型。最后,应该很容易为每个反馈条目添加额外的字段,以便存储,例如,用户是否已经看到反馈。
答案 1 :(得分:1)
您可以使用Firebase功能实现所需的功能。这里大概是我将如何实现它:
/Feedback/userID/
中。eventStream
的子节点。每当动作发生时,都可以直接添加到用户eventStream
,按时间排序。
此操作可以采用以下形式:pushID: { actionType:"liked", post:"somePostID", byUser:"someUserId" }
还包括anti-action
子节点(在/Feedback/userID/
下)。每当其中一个'反动作'发生事件(例如:不同,取消关注等),将其存储在相应用户的anti-action
节点下。该节点基本上充当我们的函数读取缓冲区。
这种反对行动的形式几乎相同:pushID: { actionType:"unliked", post:"somePostID", byUser:"someUserId" }
现在有了这个功能。
每当向anti-action
节点添加反操作时,函数会从anti-action
节点中删除此操作,在action
中找到相应的eventStream
,然后删除这个。这可以通过先"actionType"
然后"someUserId"
然后"somePostID"
查询来轻松实现。
这将确保用户eventStream
始终与最新活动保持同步。
希望这有帮助! :)