【问题标题】:Building user database model in Firebase在 Firebase 中构建用户数据库模型
【发布时间】:2016-05-10 21:00:23
【问题描述】:

所以我已经为此完成了所有实际应用程序。我只需要设置后端。我认为 Firebase 是最好的解决方案,因为 Parse 不再是一个东西。我想要的是:

拥有个人资料的用户 - 添加的朋友可以查看这些个人资料,但只能由个人资料的实际所有者编辑(写入)。


所以我通读了 Firebase 文档,但仍然无法真正弄清楚如何做到这一点。他们只有 1 个 Swift 应用程序示例,它没有做任何类似的事情,而一个 Obj C twitter 则甚至不会构建。他们所有的文档仍然有 Swift 的 println,这让我觉得它没有经常更新。


有没有人有这方面的好例子/教程?我一直在尝试寻找东西,但没有什么比我想要的更相似。我更关注如何为每个用户设置数据库并访问它,而不是在 Swift 中实际使用 Firebase。

【问题讨论】:

  • 我使用 Swift + Firebase 编写了整个社交应用程序。我认为您必须更准确地描述您想要实现的目标。看看我的应用程序 (itunes.apple.com/en/app/impether/id1084468692),让我知道这是否是您正在寻找的东西,以及您有什么问题。然后我会用我们在生产中使用的代码 sn-ps 写一个完整的答案,所以它可以是一些关于你想要实现的文档:)
  • 嘿!我下载并试用了它。看起来很棒!您有多个用户,他们有自己的个人资料,只能由他们写入,但可以由朋友查看。这正是我想要做的。如果您写下答案,我将不胜感激。 @pkacprzak

标签: swift xcode firebase firebase-realtime-database backend


【解决方案1】:

正如我在对您的问题的评论中所写,此答案基于我们在真正的社交应用 Impether 中使用 Swift + Firebase 所做的事情。

数据结构

假设您要为单个用户存储以下信息:

  • 电子邮件
  • 用户名
  • 姓名
  • 关注者 - 关注特定用户的人数
  • 关注 - 特定用户关注的人数
  • avatar_url - 头像的网址
  • 简介 - 一些额外的文字

由于在 Firebase 中所有内容都存储为 JSON 对象,因此您可以将上述结构存储在具有类似 users/$userId 的路径的节点下,其中 $userId 是 Firebase 用户 UID,如果您使用简单的电子邮件/密码,它会为每个注册用户创建Firebase 授权。

他们的文档中描述了 Firebase 电子邮件/密码授权: https://www.firebase.com/docs/ios/guide/user-auth.html https://www.firebase.com/docs/ios/guide/login/password.html

请注意,同时存在 Obj-C 和 Swift sn-ps。我发现 Firebase 文档真的很棒,因为它在我构建我们的应用程序时帮助了我很多。

为了回答这个问题,我们假设我们有用户名 jack 和 Firebase 用户 UID 等于 jack_uid 的用户(实际上这将是 Firebase 生成的字符串)。

然后该用户的示例数据将存储在路径users/jack_uid 下,如下所示:

{
  "email" : "jack@example.com",
  "username" : "jack",
  "name" : "Jack",
  "followers" : 8,
  "following" : 11,
  "avatar_url" : "http://yourstoragesystem.com/avatars/jack.jpg",
  "bio" : "Blogger, YouTuber",
}

Firebase 电子邮件/密码授权非常有效,但老实说,如果用户想登录应用程序,使用他的用户名比他在注册帐户时提供的电子邮件要好得多。

为了做到这一点,我们决定存储从用户名到用户 ID 的映射。这个想法是,如果用户在登录表单中输入他的用户名和密码,我们使用该映射来检索他的用户 ID,然后我们尝试使用他的用户 ID 和提供的密码登录。

映射可以存储在例如路径username_to_uid 下,如下所示:

{ 
   "sample_username_1": "firebase_generated_userid_1",
   "sample_username_2": "firebase_generated_userid_2",
   ...
   "jack": "jack_uid",
   "sample_username_123": "firebase_generated_userid_123"
}

然后创建配置文件可能如下所示,并且在新帐户注册成功后立即完成(此 sn-p 非常接近我们在生产中使用的确切代码):

func createProfile(uid: String, email: String,
                   username: String, avatarUrl: String,
                   successBlock: () -> Void, errorBlock: () -> Void) {
   
    //path to user data node
    let userDataPath = "/users/\(uid)"
    
    //path to user's username to uid mapping
    let usernameToUidDataPath = "/username_to_uid/\(username)"
    
    //you want to have JSON object representing user data
    //and we do use our User Swift structures to do that
    //but you can just create a raw JSON object here.
    //name, avatarUrl, bio, followers and following are
    //initialized with default values
    let user = User(uid: uid, username: username, name: "",
                    avatarUrl: avatarUrl, bio: "",
                    followers: 0, following: 0)
    
    //this produces a JSON object from User instance
    var userData = user.serialize()
    //we add email to JSON data, because we don't store
    //it directly in our objects
    userData["email"] = email
    
    //we use fanoutObject to update both user data
    //and username to uid mapping at the same time
    //this is very convinient, because either both
    //write are successful or in case of any error,
    //nothing is written, so you avoid inconsistencies
    //in you database. You can read more about that technique
    //here: https://www.firebase.com/blog/2015-10-07-how-to-keep-your-data-consistent.html
    
    var fanoutObject = [String:AnyObject]()
    fanoutObject[userDataPath] = userData
    fanoutObject[usernameToUidDataPath] = uid
    
    let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
    ref.updateChildValues(fanoutObject, withCompletionBlock: {
        err, snap in
        if err == nil {
            //call success call back if there were no errors
            successBlock()
        } else {
            //handle error here
            errorBlock()
        }
    })
}

除此之外,您可能还想为每个用户存储他的关注者列表和他关注的用户的单独列表。这可以通过将用户 ID 存储在 followers/jack_uid 之类的路径中来完成,例如它可以如下所示:

{
   "firebase_generated_userid_4": true,
   "firebase_generated_userid_14": true
}

这是我们在应用中存储值集的方式。这很方便,因为它是真正的用户来更新它并检查是否有一些值。

为了统计关注者的数量,我们将这个计数器直接放入用户的数据中。这使得读取计数器非常有效。但是,更新这个计数器需要使用事务性写入,这个想法几乎与我在此处的回答完全相同:Upvote/Downvote system within Swift via Firebase

读/写权限

您的部分问题是如何处理对您存储的数据的权限。好消息是 Firebase 在这里非常出色。如果您转到 Firebase 仪表板,则会有一个名为 Security&Rules 的选项卡,您可以在这里控制对数据的权限。

Firebase 规则的优点在于它们是声明性的,这使得它们非常易于使用和维护。但是,用纯 JSON 编写规则并不是最好的主意,因为当您想将一些原子规则组合成一个更大的规则或​​者您的应用程序简单增长并且您在 Firebase 数据库中存储的不同数据越来越多时,很难控制它们.幸运的是,Firebase 团队编写了 Bolt,这是一种您可以非常轻松地编写所有需要的规则的语言。

首先,我建议阅读有关安全性的 Firebase 文档,尤其是对节点的权限如何影响对其子节点的权限。然后,您可以在此处查看 Bolt:

https://www.firebase.com/docs/security/bolt/guide.html https://www.firebase.com/blog/2015-11-09-introducing-the-bolt-compiler.html https://github.com/firebase/bolt/blob/master/docs/guide.md

例如,我们使用类似这样的规则来管理用户数据:

//global helpers
isCurrentUser(userId) {
    auth != null && auth.uid == userId;
}

isLogged() {
    auth != null;
}

//custom types, you can extend them
//if you want to
type UserId extends String;
type Username extends String;
type AvatarUrl extends String;
type Email extends String;

type User {
    avatar_url: AvatarUrl,
    bio: String,
    email: Email,
    followers: Number,
    following: Number,
    name: String,
    username: Username,
}

//user data rules
path /users/{$userId} is User {
    write() { isCurrentUser($userId) }
    read() { isLogged() }
}

//user's followers rules
//rules for users a particular
//user follows are similar
path /followers/{$userId} {
    read() { isLogged() }
}

path /followers/{$userId}/{$followerId} is Boolean {
    create() { isCurrentUser($followerId) && this == true }
    delete() { isCurrentUser($followerId) }
}

//username to uid rules
path /username_to_uid {
    read() { true }
}

path /username_to_uid/{$username} is UserId {
    create() { isCurrentUser(this) }
}

底线是您使用 Bolt 编写您想要的规则,然后使用 Bolt 编译器将它们编译为 JSON,然后使用命令行工具或将它们粘贴到仪表板中将它们部署到 Firebase 中,但命令行效率更高。一个不错的附加功能是您可以使用仪表板中Simulator 选项卡中的工具来测试您的规则。

总结

对我来说,Firebase 是实现所需系统的绝佳工具。但是,我建议从简单的功能开始,并首先学习如何使用 Firebase。使用 Instagram 之类的功能实现社交应用程序是一个很大的挑战,尤其是如果你想把它做好的话:) 将所有功能都放在那里非常有诱惑力,而 Firebase 让它相对容易做到,但我建议耐心等待在这里。

此外,花点时间投资写作工具。例如,我们有两个独立的 Firebase 数据库,一个用于生产,第二个用于测试,如果您想高效地编写单元和 UI 测试,这一点非常重要。

另外,我建议从一开始就建立权限规则。稍后再添加它们可能很诱人,但也很让人难以抗拒。

最后但同样重要的是,请关注 Firebase 博客。他们定期发布,您可以了解他们的最新功能和更新 - 这就是我学习如何使用扇出技术使用并发写入的方式。

【讨论】:

  • @pkacprxak 你也可以显示你的结构吗?顺便说一句,你的回答很棒,我学到了很多。
  • @TarvoMäesepp 当然,你想看哪一个?顺便说一句,您之前的评论(以某种方式删除)登陆 Twitter,看起来 Joel Spolsky 喜欢它
  • 是的.. 有人不喜欢我之前发自内心的深刻评论 :) 但我的意思是用户 Struct。你认为做这样一个项目比一些 SQL 有什么优势?
  • @TarvoMäesepp 用户结构非常简单。它仅用于传递公共用户数据,因此它的实例是从除电子邮件之外的用户类型中的所有字段创建的(因为此字段的工作方式类似于私有字段,并且只能由其所有者读取和更新)。那里没有使用魔法。如果您有任何具体问题,请随时询问。
猜你喜欢
  • 2018-01-02
  • 1970-01-01
  • 2020-10-26
  • 2020-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-21
相关资源
最近更新 更多