过去几天,我一直在开发一个家庭使用的任务应用程序,需要一个聊天功能。我的技术栈包括用于前端和后端的 Next.js、用于实时通信的 Socket.io、用于推送通知的 Knock 和用于即时通知的 React Toast。虽然我一开始很难找到将 Socket.io 与 MongoDB 集成的资源,但我最终还是想出了办法,所以这篇文章可能会对其他人有所帮助。
本文假定读者熟悉使用 Next.js 和/或 Express 进行全栈开发。许多必要的信息可以在 Socket.io 的文档中找到。
首先,安装 Socket.io 和 Socket 客户端,它允许我们与后端进行交互。我们还将安装 Mongoose 以使用 MongoDB。
npm i Socket.io mongooose mongodb Socket.io-client
现在,在根目录下创建一个新文件夹,命名为 server,然后创建一个文件并命名为 index.js。您可以在模型中创建一个消息文件,它将存储我们的简单聊天消息模式。
root directory/server/index.js
models/Messages.js
构建一个简单的信息模式,看起来像这样,但您可以根据自己的要求随意编辑。
import mongoose from "mongoose"
const {Schema, model,models} = mongoose
const MessageSchema = new Schema({
creator:{
type:string,
required:true
}
message: {
type: String,
required: true,
},
roomId: {
type: String,
required: true,
},
{ timestamps: true }
},
export const Messsages = models.Messages || model("Messages, MessageSchema)
在项目的这一部分,我们为消息设置了一个模式。模式包括创建者 ID(用于识别发送者)、消息正文字段和房间 ID(用于识别群聊)。然后,我们导出模式,并在数据库中创建一个新的消息正文。
接下来,为前端创建客户端。
"use client"
import { useSession } from 'next-auth/react';
const [messages, setMessages] = useState<string[]>([]);
import { useState, useEffect, useRef } from 'react';
import io, { Socket } from "socket.io-client";
const MessageForm = () => {
const [currentMessage, setCurrentMessage] = useState('');
const [users, setUsers]= useState([])
const inputRef = useRef(null)
const { data: session } = useSession()
//@ts-ignore
const userId = session?.user?.id
//@ts-ignore
const role = session?.user?.role
// Create a ref for the socket connection
const socketRef = useRef<Socket | null>(null);
// console.log(socketRef.current?.id);
useEffect(() => {
const fetchUsers=async()=>{
const res = await fetch("apilinktousers")
if(!res){
console.log("failed to fetch users")}
}
const data = await res.json()
setKids(data)
}, [userId, role])
// console.log(state.data)
const groupRoomId = role === "creator" ? userId : users?.[0]?.creator
console.log(groupRoomId);
useEffect(() => {
// Initialize the socket connection once
socketRef.current = io("http://localhost:8080");
// Join the family room
// socketRef.current.emit('join-room', familyRoomId, userId);
// Set up the event listener for receiving messages
socketRef.current.on("receive-message", (message: string,) => {
console.log("message", message);
setMessages((prevMessages) => [...prevMessages, message]);
});
// Cleanup function to remove the event listener and disconnect the socket
return () => {
if (socketRef.current) {
// socketRef.current.off("receive-message");
socketRef.current.disconnect();
}
};
}, []);
useEffect(() => {
const fetchMessages = async () => {
try {
const res = await fetch(`api/messages?userId=${userId}`);
// console.log(res);
if (!res.ok) {
throw new Error("Failed to fetch messages");
}
const data = await res.json();
setMessages(data);
} catch (error) {
console.error("Error fetching messages:", error);
// Handle the error, e.g., show a notification to the user
}
}
fetchMessages()
}, [userId, familyRoomId])
const joinRoomR = () => {
if (userId && groupRoomId) {
socketRef.current.emit("join-room", userId, groupRoomId);
} else {
console.log("User or Room ID is missing");
}
}
// Function to handle sending messages
const sendMessage = async () => {
// Check that there is a nonempty message and socket is present
if (socketRef.current && currentMessage && groupRoomId && userId) {
await socketRef.current.emit("send-message", currentMessage, groupRoomId, userId);
setCurrentMessage("")
}
};
return (
<div>
<div>
{messages?.map((message, index) => (
<p key={index}>{message}</p>
))}
{/* <MessageCard userId={familyRoomId} /> */}
</div>
{/* Input field for sending new messages */}
<input
type="text"
value={currentMessage}
placeholder='messages'
onChange={(e) => setCurrentMessage(e.target.value)}
className='border border-gray-800 '
/>
<input
className='border border-gray-800 '
type="text" ref={inputRef}
placeholder='room name'
//@ts-ignore
value={inputRef.current?.value ? inputRef.current.value : ""}
/>
{/* Button to submit the new message */}
<div className='flex justify-around items-center'>
<button onClick={sendMessage}>Send</button>
<button onClick={joinRoomR}>Join Room</button>
</div>
</div>
);
}
export default MessageForm
接下来,进入 Server.js 文件。首先,从相应的文件夹中导入消息模式。现在,socketRef 将作为套接字连接的引用。使用 NextAuth 中的会话,将获得当前用户的 ID。然后,将 groupRoomId 分配给该组的创建者,以确保不同用户的一致性,因为该字段已附加到他们的模式中。
连接到 Socket.io 后,从 MongoDB 获取所有消息并显示给用户。我们还创建了一个函数,用于向后端发送消息、groupRoomId 和 userId。该函数对于创建房间和识别消息发送者非常必要。
import {Messages} from diretory/models/messages
import {Server} from "socket.io"
import {mongoose} from "mongoose"
//initializing our server
//8080 is our server port
const server = new Server(8080,{
cors: {
origin: ["http://localhost:3000"],
})
//we then map our users to the group
const userRooms = {}
//this will allow any one to join the group chat
server.on("connection", (socket)=>{
socket.on("join-room",(userId, groupRoomId)=>{
//we check if the userId and groupId is present
if( userId && groupId){
socket.join(groupId)
console.log(`User ${userId} joined room ${groupRoomId}`);
// Map the user to the room
userRooms[socket.id] = groupRoomId;
}
})
socket.on("send-message", async(userId, groupRoomId,Message)=>{
//we set the mongoose and connect to dp
//NB, its better to set the connection else where, but im aiming for simplicity
mongoose.set("strictQuery", true);
const mongoUri =process.env.MONGO_URI
if (!mongoUri) {
console.log("Mongo uri isnt define");
}
await mongoose.connect(`${mongoUri}`, {
dbName: "dbname",
bufferCommands: false, // Disable command buffering
socketTimeoutMS: 10000,
// we save the passed message to the message scema
const newMessage = new Messages({
roomId:groupRoomId,
message
creator: userId
}
newMessage.save()
if(roomId && userRooms[socket.id] === groupRoomId) {
//this will send messages to all the group memebers
socket.to(groupRoomId).emit("receive-message", message);
//this will send message to the sender
socket.emit("receive-message", message);
}
}
}
在后端,首先连接到 Socket.io。检查是否提供了 userId 和 groupRoomId;如果提供了,我们会允许用户加入房间,并映射套接字 ID 以方便识别。接下来,我们创建一个异步函数来接收来自客户端的消息,并将其存储到 MongoDB 实例中。
存储消息后,我们将其发送给所有房间成员,包括发送者。
作者:Abusomwan Santos
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/47216.html