Kotlin+Firestore图书推荐应用好友读书链路查找实现求助
Kotlin+Firestore 图书推荐关联链路查找实现方案
核心需求
- 支持用户查询自身直接好友是否读过指定图书
- 支持用户查询好友的好友是否读过指定图书,需建立当前用户到目标读者的完整关联链路(例:用户A的好友B的好友C读过目标书时,需留存
A→B→C的关联路径),同时支持存储多条有效链路
原有代码问题
- 嵌套回调层级过深,代码可读性差,异常无法统一捕获处理
- 链路存储使用局部变量,仅在判断用户读过书时临时创建,无法归集所有有效链路
- 未做访问去重,遇到双向好友关系(A在B的好友列表、B也在A的好友列表)时会产生重复查询
- 逻辑缺失:未覆盖直接好友的读书状态判断,仅实现了二层好友的检索逻辑
- 函数缺陷:
userReadBook方法未传入目标书籍ID参数,无法判断用户是否读的是当前查询的指定图书
具体实现方案
前置准备
首先修正原有数据类的泛型缺失问题,引入协程依赖简化Firestore异步回调:
- 在模块级
build.gradle添加协程依赖,用于将Firestore的Task回调转为同步挂起调用:implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3" - 定义全局存储结构与修正后的数据类:
// 修正原好友关系数据类的泛型声明 data class FriendRelation( val userID: String = "", val friendsList: ArrayList<String> = ArrayList() ) // 全局存储所有有效链路,单条链路为按顺序排列的用户ID列表(起点为当前用户,终点为读过目标书的用户) private val allValidLinks = mutableListOf<List<String>>() // 已访问用户ID集合,避免循环好友关系导致的重复查询 private val visitedUserIds = mutableSetOf<String>()
核心代码实现
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await // 链路查询入口函数 fun findAllValidLinks(targetBookId: String) { CoroutineScope(Dispatchers.IO).launch { // 清空上一次查询的历史缓存 allValidLinks.clear() visitedUserIds.clear() val currentUserId = getCurrentUserID() visitedUserIds.add(currentUserId) try { // 查询当前用户的好友关系文档 val currentUserFriendData = mFireStore.collection(Constants.FRIENDS) .whereEqualTo(Constants.USER_ID, currentUserId) .get() .await() .documents .firstOrNull() ?.toObject(FriendRelation::class.java) ?: return@launch // 遍历所有直接好友 for (directFriendId in currentUserFriendData.friendsList) { visitedUserIds.add(directFriendId) // 先判断直接好友是否读过目标书 if (checkUserReadBook(directFriendId, targetBookId)) { // 存入一层有效链路:当前用户→直接好友 allValidLinks.add(listOf(currentUserId, directFriendId)) continue } // 直接好友未读过,查询该好友的好友列表 val directFriendData = mFireStore.collection(Constants.FRIENDS) .whereEqualTo(Constants.USER_ID, directFriendId) .get() .await() .documents .firstOrNull() ?.toObject(FriendRelation::class.java) ?: continue // 遍历二层好友 for (secondLevelFriendId in directFriendData.friendsList) { // 跳过已访问过的用户(当前用户、已检索的直接好友),避免循环查询 if (visitedUserIds.contains(secondLevelFriendId)) continue visitedUserIds.add(secondLevelFriendId) // 判断二层好友是否读过目标书 if (checkUserReadBook(secondLevelFriendId, targetBookId)) { // 存入二层有效链路:当前用户→直接好友→二层好友 allValidLinks.add(listOf(currentUserId, directFriendId, secondLevelFriendId)) } } } // 查询完成,切到主线程回调结果用于UI更新 launch(Dispatchers.Main) { handleLinkQueryResult(allValidLinks, targetBookId) } } catch (e: Exception) { Log.e(activity.javaClass.simpleName, "Error while finding valid links.", e) } } } // 补全用户读书状态判断逻辑,传入目标书籍ID做匹配 private suspend fun checkUserReadBook(userId: String, targetBookId: String): Boolean { return try { val userDoc = mFireStore.collection(Constants.USERS) .document(userId) .get() .await() // 假设用户集合中存储已读书籍的字段名为readBookIds,为字符串数组类型 val readBookList = userDoc.get("readBookIds") as? List<String> ?: emptyList() readBookList.contains(targetBookId) } catch (e: Exception) { Log.e("ReadCheck", "Check user $userId read status failed", e) false } } // 查询结果处理函数,可在此处渲染推荐列表 private fun handleLinkQueryResult(validLinks: List<List<String>>, bookId: String) { // 示例:日志打印所有有效链路 validLinks.forEachIndexed { index, link -> Log.d("BookLink", "有效链路${index + 1}: ${link.joinToString(" → ")}") } // 此处添加UI更新逻辑,将链路与推荐用户展示到页面 }
关键优化说明
- 链路存储:使用
MutableList<List<String>>作为顶层存储结构,每找到一条有效链路就追加到列表中,查询完成后可拿到所有符合条件的关联路径 - 异步优化:用Kotlin协程替代多层嵌套回调,代码逻辑线性可读,所有请求异常可统一在
try-catch块中处理 - 性能优化:如果直接好友已经读过目标书,直接存入链路后跳过后续该好友的好友列表查询,减少不必要的Firestore读操作,降低成本
- 去重逻辑:通过
visitedUserIds记录所有已检索过的用户ID,避免好友关系双向绑定导致的重复查询、重复链路问题
扩展方向
如果后续需要支持超过二层的好友链路检索(如三层、四层好友关系),可以将现有遍历逻辑改为*广度优先搜索(BFS)*的图遍历实现,遍历过程中记录每个节点的前置用户ID,遍历到读过目标书的用户时回溯前置节点即可生成完整链路,不需要手动编写多层嵌套循环。
内容的提问来源于stack exchange,提问作者Jazim
相关产品推荐
相关产品推荐

