在 Firebase 中构建用户数据库模型
Building user database model in Firebase
所以我已经完成了所有实际的应用程序。我只需要设置后端。我认为 Firebase 是最好的解决方案,因为 Parse 不再是一回事。我想要的是:
具有个人资料的用户 - 添加的朋友可以查看这些个人资料,但只能由实际个人资料所有者编辑(写入)。
所以我通读了 Firebase 文档,但仍然无法真正弄清楚如何做到这一点。他们只有 1 个 Swift 应用程序示例,没有做任何类似的事情,一个 Obj C 推特,甚至不会构建。他们所有的文档仍然有 Swift 的 println,这让我觉得它没有经常更新。
有人有这方面的好例子/教程吗?我一直在努力寻找东西,但没有什么与我想要的足够相似。我更关注如何为每个用户设置数据库并访问它,而不是在 Swift 中实际使用 Firebase。
正如我在对你的问题的评论中所写,这个答案是基于我们在一个真实的社交应用程序 Impether 中使用 Swift
+ Firebase
.
数据结构
假设您要为单个用户存储以下信息:
- 电子邮件
- 用户名
- 姓名
- 关注者 - 关注特定用户的人数
- 关注 - 特定用户关注的人数
- avatar_url - url他们的头像
- 生物 - 一些额外的文字
由于在 Firebase 中所有内容都存储一个 JSON 对象,您可以将上述结构存储在节点下,路径如 users/$userId
,其中 $userId
是 Firebase 用户 UI如果您使用简单的 email/password Firebase 授权,则为每个注册用户创建 D。
Firebase email/password 授权在他们的文档中有描述:
https://www.firebase.com/docs/ios/guide/user-auth.html
https://www.firebase.com/docs/ios/guide/login/password.html
请注意,同时存在 Obj-C 和 Swift 片段。我发现 Firebase 文档真的很棒,因为它在我构建应用程序时帮了我很多。
为了这个答案的目的,我们假设我们有用户名 jack
和 Firebase 用户 UID 等于 jack_uid
(实际上这将是一个由火力地堡)。
然后该用户的示例数据将存储在路径 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 email/password 授权非常有效,但老实说,如果用户想登录应用程序,对他来说使用用户名比注册时提供的电子邮件要好得多帐号。
为此,我们决定存储从用户名到用户 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"
}
然后创建配置文件可能看起来像这样,并且在新帐户注册成功后立即完成(此代码段非常接近我们在生产中使用的确切代码):
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
}
这是我们在应用程序中存储值集的方式。非常方便,因为更新它并检查是否存在某些值确实是用户。
为了统计粉丝数,我们直接把这个计数器放到用户数据中。这使得读取计数器非常有效。然而,更新这个计数器需要使用事务写入,这个想法几乎和我在这里的回答完全一样:
Read/write 权限
您的部分问题是如何处理您存储的数据的权限。好消息是 Firebase 在这方面非常出色。如果您转到您的 Firebase 仪表板,则会有一个名为 Security&Rules
的选项卡,这是您控制数据权限的地方。
Firebase 规则的优点在于它们是声明性的,这使得它们非常易于使用和维护。然而,在纯 JSON 中编写规则并不是最好的主意,因为当您想将一些原子规则组合成一个更大的规则或者您的应用程序简单增长并且您存储的不同数据越来越多时,很难控制它们在您的 Firebase 数据库中。幸运的是,Firebase 团队编写了 Bolt,这是一种您可以非常轻松地编写所需的所有规则的语言。
首先,我建议阅读有关安全性的 Firebase 文档,尤其是节点权限如何影响其子节点的权限。然后,你可以在这里看看博尔特:
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,然后使用命令行工具或通过将它们粘贴到仪表板中,但命令行效率更高。一个不错的附加功能是您可以使用仪表板 Simulator
选项卡中的工具来测试您的规则。
总结
对我来说,Firebase 是实现您想要的系统的绝佳工具。但是,我建议从简单的功能开始,并首先学习如何使用 Firebase。实现具有 Instagram 等功能的社交应用程序是一个相当大的挑战,特别是如果你想做对的话 :) 很快将所有功能放在那里是非常诱人的,而 Firebase 使它相对容易做到,但我建议耐心等待这里。
此外,慢慢来,投资编写工具。例如,我们有两个独立的 Firebase 数据库,一个用于生产,第二个用于测试,如果您想高效地编写单元和 UI 测试,这一点非常重要。
此外,我建议从一开始就构建权限规则。稍后添加它们可能很诱人,但也相当难以抗拒。
最后但同样重要的是,关注 Firebase 博客。他们 post 定期,您可以了解他们的最新功能和更新 - 这就是我学习如何使用扇出技术使用并发写入的方式。
所以我已经完成了所有实际的应用程序。我只需要设置后端。我认为 Firebase 是最好的解决方案,因为 Parse 不再是一回事。我想要的是:
具有个人资料的用户 - 添加的朋友可以查看这些个人资料,但只能由实际个人资料所有者编辑(写入)。
所以我通读了 Firebase 文档,但仍然无法真正弄清楚如何做到这一点。他们只有 1 个 Swift 应用程序示例,没有做任何类似的事情,一个 Obj C 推特,甚至不会构建。他们所有的文档仍然有 Swift 的 println,这让我觉得它没有经常更新。
有人有这方面的好例子/教程吗?我一直在努力寻找东西,但没有什么与我想要的足够相似。我更关注如何为每个用户设置数据库并访问它,而不是在 Swift 中实际使用 Firebase。
正如我在对你的问题的评论中所写,这个答案是基于我们在一个真实的社交应用程序 Impether 中使用 Swift
+ Firebase
.
数据结构
假设您要为单个用户存储以下信息:
- 电子邮件
- 用户名
- 姓名
- 关注者 - 关注特定用户的人数
- 关注 - 特定用户关注的人数
- avatar_url - url他们的头像
- 生物 - 一些额外的文字
由于在 Firebase 中所有内容都存储一个 JSON 对象,您可以将上述结构存储在节点下,路径如 users/$userId
,其中 $userId
是 Firebase 用户 UI如果您使用简单的 email/password Firebase 授权,则为每个注册用户创建 D。
Firebase email/password 授权在他们的文档中有描述: https://www.firebase.com/docs/ios/guide/user-auth.html https://www.firebase.com/docs/ios/guide/login/password.html
请注意,同时存在 Obj-C 和 Swift 片段。我发现 Firebase 文档真的很棒,因为它在我构建应用程序时帮了我很多。
为了这个答案的目的,我们假设我们有用户名 jack
和 Firebase 用户 UID 等于 jack_uid
(实际上这将是一个由火力地堡)。
然后该用户的示例数据将存储在路径 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 email/password 授权非常有效,但老实说,如果用户想登录应用程序,对他来说使用用户名比注册时提供的电子邮件要好得多帐号。
为此,我们决定存储从用户名到用户 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"
}
然后创建配置文件可能看起来像这样,并且在新帐户注册成功后立即完成(此代码段非常接近我们在生产中使用的确切代码):
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
}
这是我们在应用程序中存储值集的方式。非常方便,因为更新它并检查是否存在某些值确实是用户。
为了统计粉丝数,我们直接把这个计数器放到用户数据中。这使得读取计数器非常有效。然而,更新这个计数器需要使用事务写入,这个想法几乎和我在这里的回答完全一样:
Read/write 权限
您的部分问题是如何处理您存储的数据的权限。好消息是 Firebase 在这方面非常出色。如果您转到您的 Firebase 仪表板,则会有一个名为 Security&Rules
的选项卡,这是您控制数据权限的地方。
Firebase 规则的优点在于它们是声明性的,这使得它们非常易于使用和维护。然而,在纯 JSON 中编写规则并不是最好的主意,因为当您想将一些原子规则组合成一个更大的规则或者您的应用程序简单增长并且您存储的不同数据越来越多时,很难控制它们在您的 Firebase 数据库中。幸运的是,Firebase 团队编写了 Bolt,这是一种您可以非常轻松地编写所需的所有规则的语言。
首先,我建议阅读有关安全性的 Firebase 文档,尤其是节点权限如何影响其子节点的权限。然后,你可以在这里看看博尔特:
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,然后使用命令行工具或通过将它们粘贴到仪表板中,但命令行效率更高。一个不错的附加功能是您可以使用仪表板 Simulator
选项卡中的工具来测试您的规则。
总结
对我来说,Firebase 是实现您想要的系统的绝佳工具。但是,我建议从简单的功能开始,并首先学习如何使用 Firebase。实现具有 Instagram 等功能的社交应用程序是一个相当大的挑战,特别是如果你想做对的话 :) 很快将所有功能放在那里是非常诱人的,而 Firebase 使它相对容易做到,但我建议耐心等待这里。
此外,慢慢来,投资编写工具。例如,我们有两个独立的 Firebase 数据库,一个用于生产,第二个用于测试,如果您想高效地编写单元和 UI 测试,这一点非常重要。
此外,我建议从一开始就构建权限规则。稍后添加它们可能很诱人,但也相当难以抗拒。
最后但同样重要的是,关注 Firebase 博客。他们 post 定期,您可以了解他们的最新功能和更新 - 这就是我学习如何使用扇出技术使用并发写入的方式。