Firebase非规范化数据一致性问题
我目前正在将Ionic CLI 3.19与Cordova CLI 7.1.0配合使用(@Ionic app script 3.1.4) 我目前面临的问题是,每次从其他地方更改相关数据时,我都应该同时更新friends节点值。我想用一些截图来澄清我的目标,让它更清楚 如下图所示,每个子节点都由一个用户数组组成,该用户数组的用户id作为好友节点的密钥。我之所以存储为数组,是因为每个用户都可能有很多朋友。 在这个例子中,Jeff Kim有一个朋友John Doe,反之亦然 当用户节点中的数据由于某种原因被更改时,我希望朋友节点中的相关数据也被更新 例如,当Jeff Kim更改其个人资料照片或statusMessage时,需要根据用户更改的内容更新驻留在friends节点中与Jeff Kim的uid匹配的所有相同uid 用户服务.tsFirebase非规范化数据一致性问题,firebase,firebase-realtime-database,ionic2,ionic3,angularfire2,Firebase,Firebase Realtime Database,Ionic2,Ionic3,Angularfire2,我目前正在将Ionic CLI 3.19与Cordova CLI 7.1.0配合使用(@Ionic app script 3.1.4) 我目前面临的问题是,每次从其他地方更改相关数据时,我都应该同时更新friends节点值。我想用一些截图来澄清我的目标,让它更清楚 如下图所示,每个子节点都由一个用户数组组成,该用户数组的用户id作为好友节点的密钥。我之所以存储为数组,是因为每个用户都可能有很多朋友。 在这个例子中,Jeff Kim有一个朋友John Doe,反之亦然 当用户节点中的数据由于某种
constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
this.afAuth.authState.do(user => {
this.authState = user;
if (user) {
this.updateOnConnect();
this.updateOnDisconnect();
}
}).subscribe();
}
sendFriendRequest(recipient: string, sender: User) {
let senderInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email,
timestamp: Date.now(),
message: 'wants to be friend with you.'
}
return new Promise((resolve, reject) => {
this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
resolve({'status': true, 'message': 'Friend request has sent.'});
}, error => reject({'status': false, 'message': error}));
});
}
fetchFriendRequest() {
return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
}
acceptFriendRequest(sender: User, user: User) {
let acceptedUserInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email
}
this.afDB.list(`friends/${sender.uid}`).push(user);
this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
this.removeCompletedFriendRequest(sender.uid);
}
getFriendList() {
const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
users.map(u => {
this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
});
this.friends = users;
console.log("FRIEND LIST@", users);
});
this.subscription.add(subscription);
}
testMultiPathStatusMessageUpdate({uid, statusMessage}) {
if (uid === null || uid === undefined)
return;
const rootRef = firebase.database().ref();
const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
console.log("key:", key);
key.forEach(key => {
console.log("checking..", key);
updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
});
updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
return rootRef.update(updates);
});
}
根据我刚才看到的这一点,看起来我做了一件叫做反规范化的事情
,解决方案可能是使用多路径更新
来更改数据的一致性。然而,完全理解并开始编写一些代码有点棘手
我做了一些练习,以确保在不调用.update方法两次的情况下在多个位置更新数据
// I have changed updateUsername method from the code A to code B
// Code A
updateUsername(username: string) {
let data = {};
data[username] = this.currentUserId;
this.afDB.object(`users/${this.currentUserId}`).update({'username': username});
this.afDB.object(`usernames`).update(data);
}
// Code B
updateUsername(username: string) {
const ref = firebase.database().ref();
let updateUsername = {};
updateUsername[`usernames/${username}`] = this.currentUserId;
updateUsername[`users/${this.currentUserId}/username`] = username;
ref.update(updateUsername);
}
我并不是说这是一个完美的代码。但我一直试图自己解决这个问题,以下是我到目前为止所做的
假设我当前以Jeff的身份登录
当我运行这段代码时,朋友节点中与Jeff关联的所有数据都会更改,同时用户节点中Jeff的数据也会更新
该代码需要其他firebase专家进行改进,并且还应该在真实的测试代码上进行测试
根据以下内容,once(
(通常,这对于Firebase的最佳性能来说是一个坏主意)。我应该找出这不好的原因。
朋友。ts
constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
this.afAuth.authState.do(user => {
this.authState = user;
if (user) {
this.updateOnConnect();
this.updateOnDisconnect();
}
}).subscribe();
}
sendFriendRequest(recipient: string, sender: User) {
let senderInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email,
timestamp: Date.now(),
message: 'wants to be friend with you.'
}
return new Promise((resolve, reject) => {
this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
resolve({'status': true, 'message': 'Friend request has sent.'});
}, error => reject({'status': false, 'message': error}));
});
}
fetchFriendRequest() {
return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
}
acceptFriendRequest(sender: User, user: User) {
let acceptedUserInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email
}
this.afDB.list(`friends/${sender.uid}`).push(user);
this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
this.removeCompletedFriendRequest(sender.uid);
}
getFriendList() {
const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
users.map(u => {
this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
});
this.friends = users;
console.log("FRIEND LIST@", users);
});
this.subscription.add(subscription);
}
testMultiPathStatusMessageUpdate({uid, statusMessage}) {
if (uid === null || uid === undefined)
return;
const rootRef = firebase.database().ref();
const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
console.log("key:", key);
key.forEach(key => {
console.log("checking..", key);
updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
});
updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
return rootRef.update(updates);
});
}
用户服务.ts
constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
this.afAuth.authState.do(user => {
this.authState = user;
if (user) {
this.updateOnConnect();
this.updateOnDisconnect();
}
}).subscribe();
}
sendFriendRequest(recipient: string, sender: User) {
let senderInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email,
timestamp: Date.now(),
message: 'wants to be friend with you.'
}
return new Promise((resolve, reject) => {
this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
resolve({'status': true, 'message': 'Friend request has sent.'});
}, error => reject({'status': false, 'message': error}));
});
}
fetchFriendRequest() {
return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
}
acceptFriendRequest(sender: User, user: User) {
let acceptedUserInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email
}
this.afDB.list(`friends/${sender.uid}`).push(user);
this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
this.removeCompletedFriendRequest(sender.uid);
}
getFriendList() {
const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
users.map(u => {
this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
});
this.friends = users;
console.log("FRIEND LIST@", users);
});
this.subscription.add(subscription);
}
testMultiPathStatusMessageUpdate({uid, statusMessage}) {
if (uid === null || uid === undefined)
return;
const rootRef = firebase.database().ref();
const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
console.log("key:", key);
key.forEach(key => {
console.log("checking..", key);
updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
});
updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
return rootRef.update(updates);
});
}
以下代码在将状态更新为联机而非脱机时工作正常
我认为这不是正确的方法
updateOnConnect() {
return this.afDB.object('.info/connected').valueChanges()
.do(connected => {
let status = connected ? 'online' : 'offline'
this.updateCurrentUserActiveStatusTo(status)
this.testMultiPathStatusUpdate(status)
})
.subscribe()
}
updateOnDisconnect() {
firebase.database().ref().child(`users/${this.currentUserId}`)
.onDisconnect()
.update({currentActiveStatus: 'offline'});
this.testMultiPathStatusUpdate('offline');
}
private statusUpdate(uid, status) {
if (uid === null || uid === undefined)
return;
let rootRef = firebase.database().ref();
let query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
key.forEach(key => {
console.log("checking..", key);
console.log("STATUS:", status);
updates[`friends/${uid}/${key}/currentActiveStatus`] = status;
});
return rootRef.update(updates);
});
}
testMultiPathStatusUpdate(status: string) {
this.afDB.list(`friends/${this.currentUserId}`).valueChanges()
.subscribe((users: any) => {
users.map(u => {
console.log("service U", u.uid);
this.statusUpdate(u.uid, status);
})
})
}
它确实会在控制台中显示offline
,但更改不会显示在Firebase数据库中
有人能帮我吗?:(我认为你做的非规范化是对的,你的多路径更新方向正确。但是假设几个用户可以有几个朋友,我错过了朋友表中的一个循环 您应该有表
users
、friends
和一个userfriends
。最后一个表就像是在friends
中查找用户的快捷方式,您需要迭代每个朋友以找到需要更新的用户
在我的第一个应用程序示例[angular 4+firebase]中,我采用了不同的方法。我从客户端删除了该进程,并通过云函数中的onUpdate()将其添加到服务器中
在when user change his name中,云函数在用户已经编写的每个评论中执行并更新name。在我的例子中,客户端不知道非规范化
//Executed when user.name changes
exports.changeUserNameEvent = functions.database.ref('/users/{userID}/name').onUpdate(event =>{
let eventSnapshot = event.data;
let userID = event.params.userID;
let newValue = eventSnapshot.val();
let previousValue = eventSnapshot.previous.exists() ? eventSnapshot.previous.val() : '';
console.log(`[changeUserNameEvent] ${userID} |from: ${previousValue} to: ${newValue}`);
let userReviews = eventSnapshot.ref.root.child(`/users/${userID}/reviews/`);
let updateTask = userReviews.once('value', snap => {
let reviewIDs = Object.keys(snap.val());
let updates = {};
reviewIDs.forEach(key => { // <---- note that I loop in review. You should loop in your userFriend table
updates[`/reviews/${key}/ownerName`] = newValue;
});
return eventSnapshot.ref.root.update(updates);
});
return updateTask;
});
问:你的意思是我应该创建一个查找表
在你的问题中提到的,David East给了我们一个如何去规范化的例子。最初,他有用户
和事件
。在去规范化过程中,他创建了一个类似vlookup的EventAttenders(就像你所说的)
问:你能给我举个例子吗
当然可以。我删除了一些用户信息并添加了一个额外字段friendshipTypes
users
xxsxaxacdadID1
currentActiveStatus: online
email: zinzzkak@gmail.com
gender: Male
displayName: Jeff Kim
photoURL: https://firebase....
...
trteretteteeID2
currentActiveStatus: online
email: hahehahaheha@gmail.com
gender: Male
displayName: Joeh Doe
photoURL: https://firebase....
...
friends
xxsxaxacdadID1
trteretteteeID2
friendshipTypes: bestFriend //<--- extra information
displayName: Jeff Kim
photoURL: https://firebase....
trteretteteeID2
xxsxaxacdadID1
friendshipTypes: justAfriend //<--- extra information
displayName: John Doe
photoURL: https://firebase....
userfriends
xxsxaxacdadID1
trteretteteeID2: true
hgjkhgkhgjhgID3: true
trteretteteeID2
trteretteteeID2: true
用户
xxsxaxacdadID1
当前活动状态:联机
电邮:zinzzkak@gmail.com
性别:男
显示名称:杰夫·金
照片网址:https://firebase....
...
trteretteeid2
当前活动状态:联机
电邮:hahehahaheha@gmail.com
性别:男
显示名称:Joeh Doe
照片网址:https://firebase....
...
朋友
xxsxaxacdadID1
trteretteeid2
Friendstypes:bestFriend//这是一个针对“新”用户的格式非常好的书面问题:)我希望有人能帮助你!欢迎使用堆栈溢出。@cramopy感谢你的评论。我真的希望如此。目前,我正在阅读此文章,但我的问题有点棘手。不确定该线程是否能帮助我。谢谢你给我提出建议。我不确定我是否正确构造了friends节点。我是否理解你的话是的,你的意思是我应该创建一个查找表?如果是的话,我应该把查找表放在哪里?你能给我一个例子吗?谢谢你的深入回答。因为一个问题,用户的活动状态永远不会变成脱机状态,我想也许我需要修复数据结构。而不是复制用户数据在friends节点中,只保留uid并使用这些ID从users节点获取用户数据。但是,问题是我需要将friends数据作为数组而不是对象获取。您认为这是解决问题的更好方法吗?如果您不需要在friends中添加字段(如Friendstypes),我认为您可以从两个表开始。users
具有用户信息和userFriends
具有指针(如我的示例中所示)…因此,在您的代码中,您首先获取当前用户拥有的所有朋友,然后获取所有朋友(或仅在线朋友)。我刚刚编辑了您的答案。如果我错了,请修复我:(问题是,它不能将所有用户作为一个包数组列表获取。。。