Firebase数据分组问题:如何将同标题自动ID节点归为同一帖子?
问题背景
我当前的Firebase数据库结构能正常使用,但存在一个核心问题:两个带自动ID的节点实际属于同一帖子,却被拆分成了独立节点。比如下面这两个标题相同的节点本应属于同一帖:
Planit -LETR-XJvQsZCOpG-T1N Uid: "ZjtJdkjzuxc0mZn4u9TfWsXa9jh2" date: "Jun 9, 2018 at 10:00 AM" image: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.c.." plan: "Bike Rodeo, Safety Presentation and Riding Tour o" title: "This weekend Plans?" -LETR-XKqXf7NY1gh6Jm Uid: "ZjtJdkjzuxc0mZn4u9TfWsXa9jh2" date: "Jun 11, 2018 at 10:00 AM" image: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.c.." plan: "Fun night out at the Parlor" title: "This Weekend Plans?"
之前我用数组结构存储数据(虽然不符合Firebase规范,但功能完美),每个帖子包含多张图片、多个日期和计划,仅一个标题,读取后能正确分组加载到表格视图:
Plant: -LETYTJh1WEHGgGcVCEp dates 0: " " 1: " " images 0: " " 1: " " plans 0: " " 1: " " senderId 0: " " title: close
现在我改用推荐的结构,但推送数据的逻辑有问题。推送函数是循环数组,把图片、日期、计划、标题作为一组推送:
for planits in chosenPlanArray { DataService.instance.uploadPlanitData(withTitleOfPlan: [planits.nameOfEvent!], withDate: [planits.eventStartsAt!], withImage: [planits.imageForPlan!], withTitle: nameOfPlanit.text!, forUID: (Auth.auth().currentUser?.uid)!, sendComplete: { (isGood) in if isGood { } }) }
DataService类的实现:
func uploadPlanitData(withTitleOfPlan plan: [String], withDate: [String], withImage: [String], withTitle: String, forUID uid: String, sendComplete: @escaping (_ status: Bool) -> ()) { _REF_PLANITS.childByAutoId().updateChildValues(["plan" : plan, "date" : withDate, "image": withImage, "title": withTitle, "Uid": uid]) sendComplete(true) }
现在的问题是:如何确保从Firebase读取数据时,能将同属一帖的自动ID节点正确分组?
解决方案:调整数据上传逻辑,让同一帖子的所有数据归为一个节点
嘿,我来帮你搞定这个问题!核心问题出在你循环上传的逻辑上——现在你每遍历一个chosenPlanArray元素就创建一个新的Firebase节点,直接把同一帖子的内容拆成了多个独立节点。要解决这个,你需要先把同一帖子的所有计划、日期、图片打包好,再一次性上传到一个自动ID节点下,这样读取时自然就是完整的一组。
1. 重构推送逻辑:先收集数据,再一次性上传
别在循环里调用上传函数了,先把同一帖子的所有数据整合到数组里,再调用一次上传方法:
// 先收集当前帖子的所有计划、日期、图片 var collectedPlans = [String]() var collectedDates = [String]() var collectedImages = [String]() let postTitle = nameOfPlanit.text! guard let currentUid = Auth.auth().currentUser?.uid else { return } for planItem in chosenPlanArray { // 可选绑定避免强制解包,更安全 if let plan = planItem.nameOfEvent { collectedPlans.append(plan) } if let date = planItem.eventStartsAt { collectedDates.append(date) } if let image = planItem.imageForPlan { collectedImages.append(image) } } // 一次性上传整帖数据 DataService.instance.uploadPlanitData(withTitleOfPlan: collectedPlans, withDate: collectedDates, withImage: collectedImages, withTitle: postTitle, forUID: currentUid) { isSuccess in if isSuccess { // 这里可以加上传成功后的逻辑,比如弹窗提示或者返回上一页 } }
2. 你的DataService上传逻辑不需要改
其实你的uploadPlanitData方法已经完全符合Firebase的推荐规范了——它会创建一个唯一的自动ID节点,然后把包含多个计划、日期、图片的数组和标题、UID一起存入。上传后的Firebase结构会变成这样,完美匹配你的需求:
Planit -LETR-Xxxxxxxxxx (自动生成的唯一ID) Uid: "ZjtJdkjzuxc0mZn4u9TfWsXa9jh2" date: ["Jun 9, 2018 at 10:00 AM", "Jun 11, 2018 at 10:00 AM"] image: ["https://img.evbuc.com/xxx...", "https://img.evbuc.com/yyy..."] plan: ["Bike Rodeo, Safety Presentation...", "Fun night out at the Parlor"] title: "This Weekend Plans?"
3. 读取数据时直接按节点解析成完整帖子
现在每个自动ID节点就是一个完整的帖子,你可以创建一个PlanitPost模型类,然后把每个节点解析成模型对象,直接加载到表格视图:
// 先定义一个模型类 struct PlanitPost { let id: String let uid: String let title: String let plans: [String] let dates: [String] let images: [String] } // 读取数据的示例代码 _REF_PLANITS.observe(.value) { snapshot in var posts = [PlanitPost]() for childSnapshot in snapshot.children { guard let snap = childSnapshot as? DataSnapshot, let postData = snap.value as? [String: Any], let uid = postData["Uid"] as? String, let title = postData["title"] as? String, let plans = postData["plan"] as? [String], let dates = postData["date"] as? [String], let images = postData["image"] as? [String] else { continue } let post = PlanitPost(id: snap.key, uid: uid, title: title, plans: plans, dates: dates, images: images) posts.append(post) } // 更新表格视图数据 self.planitPosts = posts self.tableView.reloadData() }
补充:如果已经有拆分的旧数据怎么办?
如果你的Firebase里已经存在那些拆分的旧节点,可以写一个一次性的迁移脚本:
- 遍历所有旧节点,按
title+Uid分组(同一帖子的标题和发布者UID肯定一致) - 把同一组的计划、日期、图片合并成数组
- 创建新的节点存储合并后的数据,然后删除旧节点
不过这一步是可选的,核心还是先修复新数据的上传逻辑,避免再产生拆分的节点。
内容的提问来源于stack exchange,提问作者Coding while Loading

