如何用FireBase实现用户反馈?

Vla*_*hev 8 ios firebase swift firebase-realtime-database swift3

我有一个应用程序,用户可以喜欢照片,评论等.像Instagram这样的功能.

我想实现用户!反馈!,用户可以看到信息,谁喜欢他的照片,谁开始关注等等.我不知道在这种情况下我应该如何组织数据库的结构.

我的用户节点快照:

在此输入图像描述

我的帖子节点快照:

在此输入图像描述

我可以看到,我有下一个选项 - 我应该将所有与用户相关联的操作保存到内部节点中的节点Feedback.但是我该如何保持同步呢?例如,有人可以关注我的用户,我会将其添加到此节点,用户将取消关注,但记录仍然存在.我认为,这是错误的方式.

我实际上没有其他想法,我找不到任何相关的东西.

任何建议和解决方案都非常感谢.

编辑:我需要了解,如何实现这个类似Instagram的应用程序的选项卡:

在此输入图像描述

如何从节点检索数据?

UPD:我的例子中的数据库架构很糟糕(旧问题).小心(10.11.2017).

Ped*_*lho 8

首先,让我们考虑一下如何为此构建数据库:

在为Firebase构建数据时,要遵循两个非常重要的原则:

  1. 您应该以您希望的方式保存数据.
  2. 您应该尽可能保持数据结构平坦 - 避免嵌套.

第1点是因为Firebase不是关系数据库.这意味着我们需要保持查询简单以实现性能.进行复杂查询可能需要向Firebase发出许多请求.

第2点是因为Firebase的查询模型的工作方式:如果你观察一个节点,你也会获得该节点的所有子节点.这意味着,如果您的数据是深度嵌套的,那么您可能会获得许多不需要的数据.

因此,考虑到这些原则,让我们来看看你的情况.我们有用户,他们有照片.这些是数据库的两个主要实体.

我可以看到,目前,您将照片保留为用户的属性.如果您希望能够快速查询用户的照片(请记住第1点),这是一个很好的方法.但是,如果我们希望用户能够"喜欢"照片,那么照片应该不仅仅是指向其Firebase存储位置的链接:它还应该包含其他属性,例如哪些用户已经将其收藏.此属性应为用户ID数组.此外,对于每个用户,您都希望存储哪些照片是该用户的收藏夹.这可能看起来像数据重复,但在使用Firebase时,如果它会导致更简单的查询,则可以复制一些数据.

因此,使用上面示例中的数据索引,您的每个照片应如下所示:

{
  id: /* some ID */,
  location: /* Firebase Storage URL */,
  favorited_by: {
    /* some user ID */: true /* this value doesn't matter */,
    /* another user ID */: true,
  },
  /* other properties... */
}
Run Code Online (Sandbox Code Playgroud)

您的用户应该有一个favorites列出照片ID 的属性.现在,由于每张照片都有一个"拥有"它的用户,我们不需要为每张照片都有唯一的ID,我们只需要确保没有用户有两张具有相同ID的照片.这样,我们可以通过其用户ID和照片ID的组合来引用照片.

当然,请记住第1点:如果您希望能够在不获取用户照片的情况下获取用户信息,则应在根对象上为照片添加不同的属性,而不是将照片与用户关联.但是,对于这个答案,我会尽力坚持你现在的模型.

根据我上面所说的,favorites用户的属性将包含格式值的数组'userId/photoId'.因此,例如,如果用户使用具有ID "3A"的用户的ID 来收集照片"CN7v0A2",则他们的favorites数组将保持该值'CN7v0A2/3A'.我们的收藏结构就此结束.

现在,让我们来看看你提到的一些操作在这个结构下会是什么样子:

  • 用户喜欢的照片:
    • 我们获取照片所有者的用户ID
    • 我们获取了喜欢该照片的用户的用户ID
    • 我们得到了照片的ID
    • 我们将喜欢ID的用户添加到照片的favorited_by阵列中
    • 我们添加photoOwnerID + "/" photoID到收藏用户的favorites数组

如果用户稍后不喜欢这张照片,我们只是反过来:我们photoOwnerID + "/" + photoID从用户favorites那里删除,我们从照片的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()
    }
}
Run Code Online (Sandbox Code Playgroud)

使用此模型,您可以获取用户查询该用户followers属性并.keys()在结果上使用该方法的所有关注者用户ID snapshot,相反,对于给定用户所遵循的用户.

添加内容:我们可以进一步构建此结构,以便添加简单的操作日志记录,这似乎是您希望用户在"反馈"选项卡中可用的内容.假设我们有一系列操作,例如喜欢,收藏和关注,我们希望显示反馈.

我们将再次遵循第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 */
}  
Run Code Online (Sandbox Code Playgroud)

另外,我们需要更改收藏夹/喜欢/关注表.以前,我们只是存储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()
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,我们可以通过阅读带有该用户ID的"反馈"表条目来获得用户的"反馈",并且自从我们使用以来setPriority,它将首先按最近的条目排序,这意味着我们可以使用Firebase queryLimited(toFirst:)仅获取最近的反馈.当用户取消关注时,我们可以轻松删除反馈条目,该反馈条目通知用户他们已被跟踪.您还可以轻松添加额外的字段来存储是否已读取反馈条目等.

即使您之前使用的是其他模型(将"followerId"设置为true),您仍然可以对新条目使用反馈条目,只需检查值为"followerId"是否为我上面所做的字符串:)

您可以使用相同的逻辑,只需使用条目中的不同字段来处理收藏夹和喜欢.当您处理它以向用户显示数据时,只需检查"type"字段中的字符串以了解要显示的反馈类型.最后,应该很容易为每个反馈条目添加额外的字段,以便存储,例如,用户是否已经看到反馈.