WebSocket实现私聊私信功能

目录

后端

pom.xml

java 复制代码
		<!-- Spring Boot WebSocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!-- Spring Boot 数据库支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- Thymeleaf(如果你使用了模板) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Spring Boot Web 支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
       <dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>

Config配置类

注意:允许源根据自己项目修改

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue", "/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOrigins("http://127.0.0.1:8889", "http://localhost:8889", "http://localhost:8888", "http://127.0.0.1:8888", "http://localhost:8000")
                .withSockJS();  // 添加 SockJS 支持
    }
}

Controller类

java 复制代码
import com.tianwen.user.dtos.MessageDTO;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/messages")
    public MessageDTO sendMessage(MessageDTO messageDTO) throws Exception {
        System.out.println("接收到的message:"+messageDTO);
        // 可以在这里进行私信存储到数据库操作
        return messageDTO;
    }
}

DTO

java 复制代码
import lombok.Data;
@Data
public class MessageDTO {
    private Integer senderId;
    private Integer receiverId;
    private String content;
}

前端

安装相关依赖

javascript 复制代码
npm install sockjs-client@latest
npm install @stomp/stompjs sockjs-client
npm install global    
npm i --save-dev @types/sockjs-client 

websocketService.js接口

注意:服务器地址根据自己的修改(application.yml)

javascript 复制代码
// websocketService.js
import { Stomp } from "@stomp/stompjs";
import SockJS from "sockjs-client/dist/sockjs.min.js";
export default {
  connect(onMessageReceived) {
    const socket = new SockJS("http://localhost:8000/ws"); // 使用 SockJS 连接
    const stompClient = Stomp.over(socket);

    stompClient.connect({}, () => {
      stompClient.subscribe("/topic/messages", (messageOutput) => {
        onMessageReceived(JSON.parse(messageOutput.body));
      });
    });
  },

  sendMessage(message) {
    const socket = new SockJS("http://localhost:8000/ws"); // 使用 SockJS 连接
    const stompClient = Stomp.over(socket);

    stompClient.connect({}, () => {
      stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(message));
    });
  },
};

javascript

javascript 复制代码
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import type { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
const editorRef = shallowRef();
import websocketService from "@/api/websocketService.js";
import {
  ref,
  onMounted,
} from "vue";
import websocketService from "@/api/websocketService.js";

const receiverIdAnswer = ref();
const sendPrivateMessage = () => {
  dialogVisible.value = true;
};
const sendPrivateMessage = async (userId) => {
  receiverIdAnswer.value = userId;
  dialogVisible.value = true;
  const response = await getAuthorDetailsByUserId(userId);
  console.log("response", userId);
  privateMessagesUser.value = response.data;
  console.log("privateMessagesUser", privateMessagesUser.value);
};

const dialogVisible = ref(false);

interface Message {
  id: string;
  senderId: string;
  receiverId: string;
  content: string;
}

const messages = ref<Message[]>([]); // 明确指定消息数组的类型

const newMessage = ref("");

onMounted(() => {
  websocketService.connect((message: Message) => {
    // 明确指定回调函数的参数类型
    messages.value.push(message);
  });
});

const sendMessage = () => {
  if (newMessage.value.trim()) {
    const message: Message = {
      // 明确声明消息类型
      id: Date.now().toString(), // 使用当前时间戳作为唯一 ID
      senderId: userInfo.value.id, // Example sender
      receiverId: receiverIdAnswer.value, // Example receiver
      content: newMessage.value,
    };
    websocketService.sendMessage(message);
    newMessage.value = "";
  }
};
const editorConfig: Partial<IEditorConfig> = {
  placeholder: "请输入...",
  MENU_CONF: {},
};
const handleCreated = (editor) => {
  editorRef.value = editor;
};
// 排除富文本的菜单项
const toolbarConfigPrivateMessages: Partial<IToolbarConfig> = {
  // toolbar 配置
  excludeKeys: [
    "headerSelect",
    "blockquote",
    "|",
    "bold",
    "underline",
    "italic",
    "group-more-style", // 排除菜单组,写菜单组 key 的值即可
    "color",
    "bgColor",
    "|",
    "fontSize",
    "fontFamily",
    "lineHeight",
    "bulletedList",
    "numberedList",
    "todo",
    "group-justify",
    "group-indent",
    "insertLink",
    "group-video",
    "insertTable",
    "codeBlock",
    "divider",
    "undo",
    "redo",
    "fullScreen",
  ],
};
</script>

html

html 复制代码
 <!-- 私信聊天框 -->
  <el-dialog v-model="dialogVisible">
    <template #title>
      <div style="text-align: center; font-weight: bold">
        {{ privateMessagesUser.username }}
      </div>
      <hr class="line" />
    </template>
    <div class="chat-container">
      <div class="messages" ref="messagesContainer">
        <div
          v-for="message in messages"
          :key="message.id"
          :class="{
            'my-message': message.senderId === userInfo.id,
            'other-message': message.senderId !== userInfo.id,
          }"
        >
          <div style="display: flex; flex-direction: row">
            <div>
              <el-image
                :src="userInfo.avatarUrl"
                style="width: 45px; border-radius: 50%"
              ></el-image>
            </div>
            <div style="margin-top: 5px; margin-left: 10px">
              <div>
                <strong>{{ userInfo.username }}</strong>
              </div>
            <div
                class="message-bubble message-green"
                v-html="message.content"
              ></div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div style="border: 1px solid #ccc">
      <Toolbar
        style="border-bottom: 1px solid #ccc"
        :editor="editorRef"
        :defaultConfig="toolbarConfigPrivateMessages"
        mode="default"
      />
      <Editor
        style="height: 200px; overflow-y: hidden"
        v-model="newMessage"
        @keyup.enter="sendMessage"
        :defaultConfig="editorConfig"
        mode="default"
        @onCreated="handleCreated"
      />
    </div>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="sendMessage">发送</el-button>
    </template>
  </el-dialog>

CSS

css 复制代码
<style scoped>
/* 私信样式 */
/* 标题居中 */
/* .private-message-dialog {
} */
.line {
  border-top: 1px solid #ccc; /* 直线的样式,可以修改颜色 */
}

/* 聊天框滚动 */
.chat-container {
  display: flex;
  flex-direction: column;
  height: 300px;
  overflow-y: auto;
  box-sizing: border-box; /* 让 padding 和 border 包含在宽度和高度内 */
}

.chat-container > * {
  width: 100%; /* 确保所有子元素不会超出容器宽度 */
  box-sizing: border-box; /* 确保子元素的宽度计算不受 padding 和 border 影响 */
}

/* 消息容器 */
.messages {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px;
  max-height: 250px;
  overflow-y: auto;
}

/* 发送方和接收方的消息样式 */
.my-message {
  /* text-align: right; */
  border-radius: 10px;
  height: auto;
  /* padding: 5px 10px; */
}

.other-message {
  /* text-align: left; */
  border-radius: 10px;
  /* padding: 5px 10px; */
}

.message-bubble {
  /* max-width: 70%; */
  padding: 0px 10px;
  border-radius: 10px;
  /* height: 30px; */
  /* margin: 0px 0px 0px 0px; */
  /* word-wrap: break-word; */
  /* line-height: 1.4; */
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.message-green {
  background-color: #57c457; /* 微信消息绿色 */
  color: white;
  align-self: flex-end; /* 让气泡靠右显示 */
}
</style>

效果展示

简单测试连接:



报错解决方法

1、vue3 使用SockJS报错 ReferenceError: global is not defined

解:

import SockJS from "sockjs-client";

修改为:

import SockJS from "sockjs-client/dist/sockjs.min.js";

并安装依赖

npm i --save-dev @types/sockjs-client

后面将继续完善,待更新...

相关推荐
幽兰的天空1 小时前
介绍 HTTP 请求如何实现跨域
网络·网络协议·http
lisenustc1 小时前
HTTP post请求工具类
网络·网络协议·http
心平气和️1 小时前
HTTP 配置与应用(不同网段)
网络·网络协议·计算机网络·http
心平气和️1 小时前
HTTP 配置与应用(局域网)
网络·计算机网络·http·智能路由器
Gworg1 小时前
网站HTTP改成HTTPS
网络协议·http·https
Mbblovey2 小时前
Picsart美易照片编辑器和视频编辑器
网络·windows·软件构建·需求分析·软件需求
北顾南栀倾寒3 小时前
[Qt]系统相关-网络编程-TCP、UDP、HTTP协议
开发语言·网络·c++·qt·tcp/ip·http·udp
GZ_TOGOGO3 小时前
PIM原理与配置
网络·华为·智能路由器
7ACE3 小时前
Wireshark TS | 虚假的 TCP Spurious Retransmission
网络·网络协议·tcp/ip·wireshark·tcpdump
大丈夫立于天地间4 小时前
ISIS基础知识
网络·网络协议·学习·智能路由器·信息与通信