从零开始用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);

}

}

}))

效果如下

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

相关推荐
剑走偏锋o.O7 分钟前
Vue 3 新特性:对比 Vue 2 的重大升级
前端·javascript·vue.js
勘察加熊人13 分钟前
angular实现nodejs增删改查
前端·javascript·angular.js
陈无左耳、27 分钟前
解锁Egg.js:从Node.js小白到Web开发高手的进阶之路
javascript·node.js
努力的小好1 小时前
【html期末作业网页设计】
前端·css·html·js
_小郑有点困了1 小时前
vue使用html实现的一个项目进度图
javascript·css·vue.js·html
拉玛干1 小时前
Run ‘conda init‘ before ‘conda activate‘
linux·前端·conda
呵呵,不解释8681 小时前
词向量(Word Embedding)
前端·javascript·easyui
xiao芝麻2 小时前
React Native 实现滑一点点内容区块指示器也滑一点点
javascript·react native·react.js
银迢迢3 小时前
javaweb自用笔记:Vue
javascript·vue.js·笔记
月伤593 小时前
el-select的下拉选择框插入el-checkbox
前端·javascript·vue.js