Vue+Firebase多用户一对一聊天的消息过滤实现咨询
多用户一对一聊天实现问题(Firestore + Vue)
需求背景
需要实现普通用户间的一对一聊天功能,仅对话双方可查看聊天内容。此前仅实现过管理员与用户的聊天,对普通用户间的聊天逻辑不清晰。
当前尝试代码
发送消息的Vue组件代码
<script setup> const { ChatMess, error } = getChatMess(firebaseAuth.currentUser.uid); const route = useRoute() const { user } = getUser() const message = ref('') const Activate = ref(false) const NewMessage = ref(true) const NewMessage2 = ref(false) const handleSubmit = async () => { const NewChat = { name: user.value.displayName, message: message.value, createdAt: timestamp(), } const NewMessages = { NewMessage: NewMessage.value, } const NewMessages2 = { NewMessage: NewMessage2.value, } message.value = '' const res = await projectFirestore.collection('Subscribed').doc(firebaseAuth.currentUser.uid).collection('messages').doc(route.params.id).set({ name: user.value.displayName, message: message.value, createdAt: timestamp(), }, { merge: true }) const messages = ref(null) return { handleSubmit, message } } </script>
获取消息的工具函数
import { projectFirestore } from "../Firebase/Config"; import { ref, watchEffect } from "vue" const getChatMess = (id) => { const ChatMess = ref(null) const error = ref(null) let collectionRef = projectFirestore.collection('Subscribed').doc(id).collection('messages').orderBy('createdAt') const unsub = collectionRef.onSnapshot(snap => { console.log(snap) let results = [] snap.docs.forEach(doc => { doc.data().createdAt && results.push({...doc.data(), id: doc.id}) }); ChatMess.value = results error.value = null console.log(ChatMess) }, err => { console.log(err.message) ChatMess.value = null error.value = 'could not fetch the data' }) watchEffect((onInvalidate) => { onInvalidate(() => unsub()); }); return { ChatMess, error } } export default getChatMess
当前数据库结构
Subscribed集合下以用户UID为文档ID,每个用户文档内包含messages子集合,子集合中以对话方UID为文档ID存储消息内容。
遇到的问题
- 发送新消息时会覆盖旧消息;
- 使用
set({NewChat}, {merge:true})时,消息字段会被存入Map类型,无法通过v-for遍历展示。
考虑过的方案
- 为每个对话方创建独立集合,每条消息作为新文档存入,但不知如何通过
route.params.id过滤消息; - 编写Firestore规则,仅允许包含双方UID的文档被读取,但不会编写此类规则。
可行实现方案
一、优化数据库结构
一对一聊天的标准做法是为每对用户生成唯一对话ID(按字典序拼接双方UID,确保无论谁发起对话,ID一致),然后创建独立的chats集合:
chats集合:每个文档对应一个对话,文档ID为生成的唯一对话ID,文档字段包含participants(存储双方UID数组)、updatedAt(对话更新时间);- 每个对话文档下的
messages子集合:每条消息作为独立文档存储,字段包含senderId(发送者UID)、senderName(发送者昵称)、content(消息内容)、createdAt(发送时间)。
二、Firestore安全规则
限制用户仅能访问包含自己UID的对话及消息:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 对话集合访问限制 match /chats/{chatId} { allow read, write: if request.auth != null && request.auth.uid in resource.data.participants; // 消息子集合访问限制 match /messages/{messageId} { allow read, write: if request.auth != null && exists(/databases/$(database)/documents/chats/$(chatId)) && request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.participants; } } } }
三、代码调整
1. 生成唯一对话ID的工具函数
function generateChatId(uid1, uid2) { return uid1 < uid2 ? `${uid1}_${uid2}` : `${uid2}_${uid1}`; }
2. 修改发送消息的逻辑
<script setup> import { projectFirestore, timestamp } from "../Firebase/Config"; import { ref } from "vue"; import { useRoute } from "vue-router"; import { firebaseAuth } from "../Firebase/Config"; import { getUser } from "./your-user-composable"; const route = useRoute(); const { user } = getUser(); const message = ref(''); const generateChatId = (uid1, uid2) => { return uid1 < uid2 ? `${uid1}_${uid2}` : `${uid2}_${uid1}`; }; const handleSubmit = async () => { if (!message.value.trim()) return; const currentUserId = firebaseAuth.currentUser.uid; const recipientUserId = route.params.id; const chatId = generateChatId(currentUserId, recipientUserId); // 确保对话文档存在(不存在则创建) await projectFirestore.collection('chats').doc(chatId).set({ participants: [currentUserId, recipientUserId], updatedAt: timestamp() }, { merge: true }); // 添加新消息到子集合 await projectFirestore.collection('chats').doc(chatId).collection('messages').add({ senderId: currentUserId, senderName: user.value.displayName, content: message.value, createdAt: timestamp() }); message.value = ''; }; return { handleSubmit, message }; </script>
3. 修改获取消息的逻辑
import { projectFirestore } from "../Firebase/Config"; import { ref, watchEffect } from "vue"; const getChatMessages = (chatId) => { const chatMessages = ref(null); const error = ref(null); let collectionRef = projectFirestore .collection('chats') .doc(chatId) .collection('messages') .orderBy('createdAt'); const unsub = collectionRef.onSnapshot(snap => { let results = []; snap.docs.forEach(doc => { doc.data().createdAt && results.push({ ...doc.data(), id: doc.id }); }); chatMessages.value = results; error.value = null; }, err => { console.log(err.message); chatMessages.value = null; error.value = '无法获取消息'; }); watchEffect((onInvalidate) => { onInvalidate(() => unsub()); }); return { chatMessages, error }; }; export default getChatMessages;
4. 在组件中调用获取消息
<script setup> import { firebaseAuth } from "../Firebase/Config"; import { useRoute } from "vue-router"; import getChatMessages from "./getChatMessages"; const route = useRoute(); const currentUserId = firebaseAuth.currentUser.uid; const recipientUserId = route.params.id; const chatId = generateChatId(currentUserId, recipientUserId); const { chatMessages, error } = getChatMessages(chatId); </script>
四、扩展:获取用户所有对话列表
const getUserChats = (userId) => { const chats = ref(null); const error = ref(null); let collectionRef = projectFirestore .collection('chats') .where('participants', 'array-contains', userId) .orderBy('updatedAt', 'desc'); const unsub = collectionRef.onSnapshot(snap => { let results = []; snap.docs.forEach(doc => { results.push({ ...doc.data(), id: doc.id }); }); chats.value = results; error.value = null; }, err => { console.log(err.message); chats.value = null; error.value = '无法获取对话列表'; }); watchEffect((onInvalidate) => { onInvalidate(() => unsub()); }); return { chats, error }; };
内容的提问来源于stack exchange,提问作者Epimetheus14
相关产品推荐
相关产品推荐

