You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin+Firestore图书推荐应用好友读书链路查找实现求助

Kotlin+Firestore 图书推荐关联链路查找实现方案

核心需求

  • 支持用户查询自身直接好友是否读过指定图书
  • 支持用户查询好友的好友是否读过指定图书,需建立当前用户到目标读者的完整关联链路(例:用户A的好友B的好友C读过目标书时,需留存A→B→C的关联路径),同时支持存储多条有效链路

原有代码问题

  • 嵌套回调层级过深,代码可读性差,异常无法统一捕获处理
  • 链路存储使用局部变量,仅在判断用户读过书时临时创建,无法归集所有有效链路
  • 未做访问去重,遇到双向好友关系(A在B的好友列表、B也在A的好友列表)时会产生重复查询
  • 逻辑缺失:未覆盖直接好友的读书状态判断,仅实现了二层好友的检索逻辑
  • 函数缺陷:userReadBook方法未传入目标书籍ID参数,无法判断用户是否读的是当前查询的指定图书

具体实现方案

前置准备

首先修正原有数据类的泛型缺失问题,引入协程依赖简化Firestore异步回调:

  1. 在模块级build.gradle添加协程依赖,用于将Firestore的Task回调转为同步挂起调用:
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3"
    
  2. 定义全局存储结构与修正后的数据类:
    // 修正原好友关系数据类的泛型声明
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 00:09:39