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

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 方法

javascript 复制代码
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

javascript 复制代码
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 引入

javascript 复制代码
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);

}

}

}))

效果如下

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

相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax