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

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

相关推荐
23zhgjx-hyh1 小时前
配置NBMA和P2MP网络类型
网络
诺亚凹凸曼2 小时前
我与DeepSeek读《大型网站技术架构》(8)- 网站应用攻击与防御
网络·架构
始终奔跑在路上3 小时前
全栈网络安全|渗透测试-1
网络·安全·web安全·网络安全
SRC_BLUE_174 小时前
[网络爬虫] 动态网页抓取 — Selenium 入门操作
网络·爬虫·selenium·测试工具
SuperW4 小时前
EPS8266远端固定UDP传输
网络·网络协议·udp
nihuhui6665 小时前
关于静态IP的总结
网络·tcp/ip
长安11086 小时前
计算机网络----主要内容简介
网络·计算机网络·智能路由器
鹿屿二向箔6 小时前
72MHz的MCU能支持多大频率的传感器数据采样率?
服务器·网络·单片机
东隆科技7 小时前
从芯片到光网络:平面光波导技术(PLC)的核心优势与应用前景
网络·平面
AORO_BEIDOU8 小时前
科普|卫星电话有哪些应用场景?
网络·人工智能·安全·智能手机·信息与通信