如何在Firestore搜索中排除已浏览用户数据,降低读取次数提升加载速度
解决方案
首先明确一个核心误区:Firebase安全规则是权限控制工具,不是查询过滤工具,你找到的示例仅用于控制单条数据的可读权限,无法实现「自动从查询结果中剔除指定数据」的效果——如果硬写规则禁止读取已浏览用户数据,当你拉取的用户列表包含已浏览用户时,整个查询请求会直接被安全规则拒绝,完全达不到你要的效果。
结合你现有的数据库结构,推荐按以下步骤实现需求:
1. 优化数据结构,新增已滑动用户索引
你当前的swipes节点是按滑动记录ID存储,查询单个用户的已滑动ID需要全表遍历,效率极低。我们新增一个userSwipedIds节点,专门存储每个用户的已滑动ID索引,结构如下:
{ "userSwipedIds": { "当前用户ID": { "已滑动用户1ID": true, "已滑动用户2ID": true } } }
每次用户滑动其他用户时,同时执行两个写入操作:
- 向
swipes节点写入完整的滑动记录(用于后续浏览历史查询) - 向
userSwipedIds/当前用户ID/被滑动用户ID写入值true
这个结构可以让你毫秒级拿到单个用户的所有已滑动ID列表,没有遍历开销。
2. 实现服务端过滤,避免无效读取
这里提供两种成熟方案,你可以根据自己的技术栈选择:
方案一:使用Firebase云函数做中间层(推荐)
所有推荐请求走云函数处理,客户端不需要拉取冗余数据,完全消除无效读取:
云函数示例代码
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); exports.getRecommendations = functions.https.onCall(async (data, context) => { // 校验登录状态 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', '请先登录'); const currentUid = context.auth.uid; const needCount = data.count || 10; // 客户端指定需要的推荐数量,默认10条 // 1. 拉取当前用户已滑动的ID列表 const swipedSnap = await admin.database().ref(`userSwipedIds/${currentUid}`).once('value'); const swipedIds = swipedSnap.exists() ? Object.keys(swipedSnap.val()) : []; swipedIds.push(currentUid); // 排除用户自己 // 2. 分页拉取用户,过滤已滑动ID,直到凑够需要的数量 const recommendations = []; let lastUserId = null; while (recommendations.length < needCount) { // 每次拉20条,预留足够的过滤空间 let query = admin.database().ref('users').orderByKey().limitToFirst(20); if (lastUserId) query = query.startAfter(lastUserId); const userSnap = await query.once('value'); if (!userSnap.exists()) break; // 没有更多可推荐用户 const userList = userSnap.val(); const userIds = Object.keys(userList); lastUserId = userIds[userIds.length - 1]; // 过滤已滑动用户 userIds.forEach(uid => { if (!swipedIds.includes(uid) && recommendations.length < needCount) { recommendations.push(userList[uid]); } }); } return recommendations; });
客户端调用方式
客户端直接调用这个云函数,就能拿到已经过滤好的推荐列表,不需要做任何额外处理,读取次数仅等于实际返回的推荐用户数量,没有浪费。
方案二:客户端优化(不想用云函数可选)
如果暂时不打算用云函数,可以优化现有客户端逻辑,大幅降低无效读取:
- 首次打开应用时拉取当前用户的
userSwipedIds列表,存在本地缓存,每次滑动新用户时同时更新本地缓存,不需要每次请求推荐都重新拉取 - 查询用户列表时按
orderByKey分页查询,记录上一页最后一个用户的ID,下次拉取直接从这个ID之后开始,不会重复拉取之前已经判断过的用户 - 拉取到一页用户后,直接用本地缓存的已滑动ID列表过滤,不够数再拉取下一页
3. 浏览历史功能实现
该功能和推荐逻辑完全独立,不需要做修改:
- 先给
swipes节点加索引,提升查询效率,安全规则配置如下:
{ "rules": { "swipes": { ".read": "auth != null", ".write": "auth != null && newData.child('whoSwiped').val() === auth.uid", ".indexOn": ["whoSwiped", "createdAt"] // 加索引 }, "userSwipedIds": { "$uid": { ".read": "auth.uid === $uid", ".write": "auth.uid === $uid" } }, "users": { ".read": "auth != null", "$userId": { ".write": "auth.uid === $userId" } } } }
- 查询浏览历史时,用
orderByChild('whoSwiped').equalTo(当前用户ID)查询swipes节点,拿到所有滑动记录,再根据记录里的whoIsSwiped字段拉取对应用户的资料即可。
你找到的安全规则示例解释
你提到的示例是典型的权限校验规则,逻辑如下:
{ "rules": { "Children": { "$child_id": { ".read": "auth != null && root.child('Family').child( root.child('User').child( data.child('parentId').val() ).val() ).child('members').child(auth.uid).exists() } } } }
- 首先校验请求用户处于登录状态
- 拿到当前请求的孩子节点的
parentId字段值,去User节点查询这个家长ID对应的家庭ID - 再去
Family节点下对应家庭ID的members列表里,判断当前登录用户是否在这个家庭的成员列表中 - 只有是家庭成员的情况下,才允许读取这条孩子的数据
这个规则完全是用来做权限控制的,和你要的查询过滤没有任何关系,不要用这个思路实现你的需求。
内容的提问来源于stack exchange,提问作者Muhammad Owais
相关产品推荐
相关产品推荐

