Firestore触发器未完成时,应用访问群组聊天节点的权限问题求解
用户创建群组后,应用会生成对应的Firestore记录,随后Firestore触发器会在Realtime Database中创建用于存储成员和聊天信息的节点。
问题在于,应用完成Firestore记录创建后会直接打开新群组页面,该页面尝试为群组的聊天节点添加监听,但此时触发器尚未完成节点创建工作,导致应用收到如下错误:
Unhandled Exception: [firebase_database/permission-denied] Client doesn't have permission to access the desired data.
出现错误的原因是,访问聊天节点的权限依赖于用户UID是否存在于成员节点中。如果成员节点尚未写入完成,就无法成功添加监听。
请问有什么合理的处理方式?添加500毫秒延迟的方案并不规范,且生产环境中触发器耗时可能更长,延迟方案也无法解决根本问题。
监听代码:
void getChatListener() async { final node = 'group/groupID1/chats'; _groupChatRef = FirebaseDatabase.instance.ref(node); _chatsSubscription = _groupChatRef.onValue.listen((DatabaseEvent event) { dataList = []; for (final child in event.snapshot.children) { dataList.add(child.value); } setState(() { chats = dataList; }); }); }
监听Firestore就绪状态再跳转
在Firestore的群组记录里加一个ready字段,初始设为false。触发器完成Realtime Database节点创建后,把这个字段更新为true。应用创建群组后,先监听这个字段的变化,只有当ready变为true时,再跳转到新群组页面。示例逻辑:
void createGroupAndWait() async { final groupDoc = await FirebaseFirestore.instance.collection('groups').add({ 'name': '新群组', 'creator': currentUser.uid, 'ready': false }); groupDoc.snapshots().listen((snapshot) { if (snapshot.data()?['ready'] == true) { Navigator.push(context, MaterialPageRoute(builder: (_) => GroupPage(groupId: groupDoc.id))); } }); }先监听成员节点,就绪后再初始化聊天监听
在群组页面里,先监听当前用户在该群组成员节点的存在状态。只有当成员节点存在(说明触发器已写入),再去初始化聊天节点的监听。修改后的监听代码:
void initGroupListeners() async { final groupId = 'groupID1'; final memberNode = 'group/$groupId/members/${currentUser.uid}'; FirebaseDatabase.instance.ref(memberNode).onValue.listen((memberEvent) { if (memberEvent.snapshot.exists) { getChatListener(groupId); } }); } void getChatListener(String groupId) { final node = 'group/$groupId/chats'; _groupChatRef = FirebaseDatabase.instance.ref(node); _chatsSubscription?.cancel(); _chatsSubscription = _groupChatRef.onValue.listen((DatabaseEvent event) { dataList = []; for (final child in event.snapshot.children) { dataList.add(child.value); } setState(() { chats = dataList; }); }); }权限规则兼容节点未创建场景
调整Realtime Database的权限规则,允许创建者或预期成员在聊天节点未创建时拥有临时读权限(需确保安全,避免漏洞)。示例规则参考:{ "rules": { "group": { "$groupId": { "chats": { ".read": "auth != null && (data.parent().parent().child('members').child(auth.uid).exists() || data.parent().parent().child('creator').val() == auth.uid)", ".write": "auth != null && data.parent().parent().child('members').child(auth.uid).exists()" } } } } }指数退避重试机制
如果以上方式都无法实施,就在聊天监听抛出权限错误时自动重试,采用指数退避策略(比如第1次等1秒,第2次等2秒,最多重试5次),直到监听成功或达到上限。示例代码:
void getChatListener({int retryCount = 0}) async { final node = 'group/groupID1/chats'; _groupChatRef = FirebaseDatabase.instance.ref(node); try { _chatsSubscription?.cancel(); _chatsSubscription = _groupChatRef.onValue.listen((DatabaseEvent event) { dataList = []; for (final child in event.snapshot.children) { dataList.add(child.value); } setState(() { chats = dataList; }); }); } catch (e) { if (e.toString().contains('permission-denied') && retryCount < 5) { await Future.delayed(Duration(seconds: 1 << retryCount)); getChatListener(retryCount: retryCount + 1); } else { print('监听失败,请刷新页面'); } } }
内容的提问来源于stack exchange,提问作者whatwhatwhat

