从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(九) 消息接口

发布于:2025-03-04 ⋅ 阅读:(18) ⋅ 点赞:(0)

1.后端

 首先在models下创建 message.model.js

import mongoose from "mongoose";

const messageSchema = new mongoose.Schema(

    {  

        // 发送人Id

        senderId: {

            type: mongoose.Schema.Types.ObjectId, //在 Mongoose 模型中定义 _id 字段

            required: true,

            ref: "User"

        },

        // 接收人

        receiverId: {

            type: mongoose.Schema.Types.ObjectId, //在 Mongoose 模型中定义 _id 字段

            required: true,

            ref: "User"

        },

        // 文本

        text: {

            type: String

        },

        // 图片

        image: {

            type: String

        }

    },

    {timestamps: true});

const Message = mongoose.model('Message', messageSchema);

完善message.controller.js中 getMessages 和  sendMessages 方法

import User from '../models/user.model.js';
import Message from '../models/message.model.js';
import cloudinary from '../lib/cloudinary.js';

export const getUsersForSidebar = async (req, res) => {
    try {
        // 获取当前登录用户的id
        const loggedInUserId = req.user._id;
        // 过滤用户 所有不等于当前用户id 的用户 .select 查询时排除password
        const filteredUsers = await User.find({ _id: { $ne: loggedInUserId } }).select("-password");
        res.status(200).json(filteredUsers);
    } catch (error) {
        console.log("error in getSidebarUsers: ");
        res.status(500).json({ message: error.message });
    }
}
// 点击左侧用户时,获取该用户与当前用户之间的聊天记录
export const getMessages = async (req, res) => {
    try {
        const {id: userToChatId} = req.params;
        // 发送人id 是当前用户id
        const myId = req.user._id;
        
        // 获取当前用户与点击的用户之间的聊天记录
        const messages = await Message.find({
           $or: [
             {senderId: myId, receiverId: userToChatId},
             {senderId: userToChatId, receiverId: myId}
           ]
        })
        res.status(200).json(messages);
    } catch (error) {
        console.log("error in getMessages controller: ");
        res.status(500).json({ message: error.message });
    }
}

// 发送消息
export const sendMessage = async (req, res) => {
    try {   
        const {text,image} = req.body;
        const {id: receiverId} = req.params;
        const senderId = req.user._id;
        let imageUrl;
        if(image) {
            // 上传base64图片到cloudinary
            const uploadResonse = await cloudinary.uploader.upload(image)      
            imageUrl = uploadResonse.secure_url;
        }
        // 构建消息对象
        const newMessage = new Message({
            senderId,
            receiverId,
            text,
            image: imageUrl
        })

        await newMessage.save();


        res.status(201).json(newMessage);
    } catch (error) {
        console.log("error in sendMessage controller: ");
        res.status(500).json({ message: error.message });
    }
}

2.前端

在components 下 新建一个ChatHeader.jsx

import { useChatStore } from "../store/useChatStore";
import { useAuthStore } from "../store/useAuthStore";
import { X } from "lucide-react";

const ChatHeader = () => {
    const {selectedUser,setSelectedUser} = useChatStore();
  return (
    <div className="p-2.5 border-b border-base-300">
       <div className="flex items-center justify-between">
          <div className="flex items-center">
            {/* 头像 */}
            <div className="avatar">
                <div className="size-10 rounded-full relative">
                    <img src={selectedUser.profilePic || "https://picsum.photos/200"} alt={selectedUser.userName} />
                </div>
            </div>

            {/* 用户信息 */}
            <div>
                <h3 className="font-medium">{selectedUser.userName}</h3>
                
            </div>
          </div>

          {/* 关闭按钮 */}
          <button onClick={()=>setSelectedUser(null)}>
            <X/>
          </button>
       </div>
    </div>
  )
}

export default ChatHeader

然后再ChatBox.jsx 引入

 


import {useEffect} from "react"
import { useChatStore } from "../store/useChatStore"
import { useAuthStore } from "../store/useAuthStore"
import {formatMessageTime} from "@/lib/util"
import ChatHeader from "./ChatHeader"

const ChatBox = () => {
  const {messages, getMessages, isMessagesLoading, selectedUser} = useChatStore()
  const {authUser} = useAuthStore()
  useEffect(()=>{
    getMessages(selectedUser._id)

  },[selectedUser._id, getMessages])

  if(isMessagesLoading) return <div>Loading...</div>
  return (
    <div className="flex-1 flex flex-col overflow-auto">
      {/* 聊天框头部 */}
      <ChatHeader/>
      
      {/* 聊天消息 */}
      <div className="flex-1 overflow-auto p-4 space-y-4">
          {messages.map((message)=> (
             <div
              key={message._id}
              // 消息的发送者id和当前用户id一致,则显示在右侧,否则显示在左侧
              className={`chat ${message.senderId===authUser._id ? 'chat-end' : 'chat-start'}`}
             >
              <div className="chat-image avatar">
                 <div className="size-10 rounded-full border">
                    <img
                      src={message.senderId === authUser._id ? authUser.profilePic || 'http://via.placeholder.com/150' : selectedUser.profilePic}
                      alt=""
                    />
                 </div>
              </div>

              <div className="chat-header mb-1">
                 <time className="text-xs opacity-50 ml-1">{formatMessageTime(message.createdAt)}</time>
              </div>
              {/* flex-col 图片和文字上下排列 */}
              <div className="chat-bubble flex flex-col"> 
                 {message.image && (
                   <img 
                    src={message.image}
                    alt=""
                    className="sm:max-w-[200px] rounded-md mb-2"
                   />
                 )}
                 {message.text && <p>{message.text}</p>}
              </div>
             </div>
          ))}

      </div>

      {/* 消息输入 */}
      
    </div>
  )
}

export default ChatBox

完善useChatStore.js

import {create} from "zustand";

import toast from "react-hot-toast"

import axiosInstance from "../lib/axios";

export const useChatStore = create((set, get) => ({

    messages: [],

    users: [],

    selectedUser: null,

    isUserLoading:false,

    isMessageLoading:false,

     // 选择一个联系人

     setSelectedUser: (user) => {

          set({selectedUser: user});

     },

    getUsers: async () => {

        set({isUserLoading: true});

       try {

            const res = await axiosInstance.get("/messages/users");

            set({users: res.data});

       } catch{

            toast.error("Error while fetching users");

       } finally{

            set({isUserLoading: false});

       }

    },

    getMessages: async (userId) => {

        set({isMessageLoading: true});

        try {

            const res = await axiosInstance.get(`/messages/${userId}`);

            set({messages: res.data});

        } catch {

            toast.error("Error while fetching messages");

        } finally {

            set({isMessageLoading: false});

        }

    },

    sendMessages: async (messageData) => {

        const {selectedUser, messages} = get();

        try {

            const res = await axiosInstance.post(`/messages/send/${selectedUser._id}`, messageData);

            set({messages: [...messages, res.data]});

        } catch(error) {

            toast.error("Error while sending message:" + error.message);

        }

    }

}))

效果如下

好这篇就到这 下一篇 实现聊天功能