做二手车那个网站会员性价比高,旅游扁平化设计网站模板,网站移动适配怎么做,云南省玉溪市建设局官方网站目录 后端pom.xmlConfig配置类Controller类DTO 前端安装相关依赖websocketService.js接口javascripthtmlCSS 效果展示简单测试连接#xff1a; 报错解决方法1、vue3 使用SockJS报错 ReferenceError: global is not defined 功能补充拓展1. 安全性和身份验证2. 异常处理3. 消息… 目录 后端pom.xmlConfig配置类Controller类DTO 前端安装相关依赖websocketService.js接口javascripthtmlCSS 效果展示简单测试连接 报错解决方法1、vue3 使用SockJS报错 ReferenceError: global is not defined 功能补充拓展1. 安全性和身份验证2. 异常处理3. 消息广播的功能4. 配置 WebSocket 消息缓存和负载均衡5. 客户端连接管理6. WebSocket 消息格式和编码总结 后面将继续完善待更新... 后端
pom.xml !-- Spring Boot WebSocket--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependency!-- Spring Boot 数据库支持 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- MySQL 驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependency!-- Thymeleaf如果你使用了模板 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependency!-- Spring Boot Web 支持 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependencyConfig配置类 注意允许源根据自己项目修改 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 {Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker(/queue, /topic,/user);config.setApplicationDestinationPrefixes(/app);}Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint(/ws)
// 在 WebSocket 握手时我们可以通过 URL 参数或者 HTTP headers 传递用户身份信息。
// .addInterceptors(new MyHandshakeInterceptor())// 添加拦截器.setAllowedOrigins(http://127.0.0.1:8889, http://localhost:8889, http://localhost:8888, http://127.0.0.1:8888, http://localhost:8000,http://localhost:8890,http://127.0.0.1:8890).withSockJS(); // 添加 SockJS 支持}
}Controller类
import com.tianwen.mapper.UserMessagesMapper;
import com.tianwen.user.dtos.MessageDTO;
import com.tianwen.user.pojos.Messages;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.time.LocalDateTime;Controller
public class ChatController {Autowiredprivate UserMessagesMapper userMessagesMapper;Autowiredprivate SimpMessagingTemplate messagingTemplate; // 注入消息模板用于发送消息到指定目的地MessageMapping(/chat.sendMessage)//这个注解用来监听来自前端的 WebSocket 消息路径是 /app/chat.sendMessage当前端发送消息到这个路径时sendMessage 方法会被触发。
// SendTo(/topic/messages)//这个注解表明处理完消息后返回的消息将广播给订阅了 /topic/messages 路径的所有客户端。public MessageDTO sendMessage(MessageDTO messageDTO) throws Exception {System.out.println(接收到的messagemessageDTO);// 1. 可以在这里进行私信存储到数据库操作Messages messages new Messages();messages.setSenderId(messageDTO.getSenderId());messages.setReceiverId(messageDTO.getReceiverId());messages.setContent(messageDTO.getContent());messages.setCreateTime(LocalDateTime.now());// 2. 保存私信消息插入操作if (userMessagesMapper.insert(messages) 0) {// 如果插入失败可以返回错误或做其他处理return null;}// 3. 实时将消息转发给接收者String receiverIdStr String.valueOf(messageDTO.getReceiverId()); // 将 receiverId 转换为 StringString receiverDestination /user/ receiverIdStr /queue/messages;//通过 SimpMessagingTemplate 的 convertAndSendToUser 方法将消息实时推送给接收者。//推送的目标是 /user/{receiverId}/queue/messages该路径是给特定用户的私有消息队列。messagingTemplate.convertAndSendToUser(receiverIdStr, receiverDestination, messageDTO);return messageDTO;}
}DTO
import lombok.Data;
Data
public class MessageDTO {private Integer senderId;private Integer receiverId;private String content;
}前端
安装相关依赖
npm install sockjs-clientlatest
npm install stomp/stompjs sockjs-client
npm install global
npm i --save-dev types/sockjs-client websocketService.js接口 注意服务器地址根据自己的修改application.yml // websocketService.js
// Stomp.js用于处理 STOMP 协议它在 WebSocket 基础上实现了消息订阅、发送等功能。
import { Stomp } from stomp/stompjs;
//SockJS是一个用于实现 WebSocket 的库它为 WebSocket 提供了回退机制例如 HTTP 长轮询等确保在不同浏览器和网络环境下的兼容性。
import SockJS from sockjs-client/dist/sockjs.min.js;
export default {connect(onMessageReceived) {//使用 SockJS 和 Stomp 创建一个 WebSocket 客户端连接到后端的 WebSocket 服务 http://localhost:8000/ws。const socket new SockJS(http://localhost:8000/ws); // 使用 SockJS 连接const stompClient Stomp.over(socket);const userId JSON.parse(localStorage.getItem(userId));stompClient.connect({}, () {console.log(本人消息队列ID, userId);//在连接成功后通过 stompClient.subscribe 订阅 /topic/messages接收从后端广播过来的消息。// stompClient.subscribe(/topic/messages, (messageOutput) {// onMessageReceived(JSON.parse(messageOutput.body));// });// 订阅当前用户的私有消息队列stompClient.subscribe(/user/ userId /queue/messages,(messageOutput) {onMessageReceived(JSON.parse(messageOutput.body)); // 处理接收到的私聊消息});// 订阅当前用户的私有消息队列;// stompClient.subscribe(// /user/ userId /queue/messages,// function (messageOutput) {// const message JSON.parse(messageOutput.body);// console.log(接收到私信, message);// }// );});// 连接到 WebSocket 后订阅用户消息// stompClient.connect({}, function (frame) {// // 获取当前用户ID// const userId getCurrentUserId(); // 假设这个方法能够获取当前用户的ID// // 订阅接收者的消息队列// stompClient.subscribe(// /user/ userId /queue/messages,// function (messageOutput) {// const message JSON.parse(messageOutput.body);// console.log(接收到私信, message);// }// );// });},sendMessage(message) {console.log(发送消息接口1, message);const socket new SockJS(http://localhost:8000/ws); // 使用 SockJS 连接const stompClient Stomp.over(socket);stompClient.connect({}, () {//当用户输入消息时通过 stompClient.send 方法将消息发送到 /app/chat.sendMessage这个路径会将消息推送到后端进行处理。stompClient.send(/app/chat.sendMessage, {}, JSON.stringify(message));});},
};
javascript
script setup langts
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 refMessage[]([]); // 明确指定消息数组的类型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(), // 使用当前时间戳作为唯一 IDsenderId: userInfo.value.id, // Example senderreceiverId: receiverIdAnswer.value, // Example receivercontent: newMessage.value,};websocketService.sendMessage(message);newMessage.value ;}
};
const editorConfig: PartialIEditorConfig {placeholder: 请输入...,MENU_CONF: {},
};
const handleCreated (editor) {editorRef.value editor;
};
// 排除富文本的菜单项
const toolbarConfigPrivateMessages: PartialIToolbarConfig {// 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,],
};
/scripthtml !-- 私信聊天框 --el-dialog v-modeldialogVisibletemplate #titlediv styletext-align: center; font-weight: bold{{ privateMessagesUser.username }}/divhr classline //templatediv classchat-containerdiv classmessages refmessagesContainerdivv-formessage in messages:keymessage.id:class{my-message: message.senderId userInfo.id,other-message: message.senderId ! userInfo.id,}div styledisplay: flex; flex-direction: rowdivel-image:srcuserInfo.avatarUrlstylewidth: 45px; border-radius: 50%/el-image/divdiv stylemargin-top: 5px; margin-left: 10pxdivstrong{{ userInfo.username }}/strong/divdivclassmessage-bubble message-greenv-htmlmessage.content/div/div/div/div/div/divdiv styleborder: 1px solid #cccToolbarstyleborder-bottom: 1px solid #ccc:editoreditorRef:defaultConfigtoolbarConfigPrivateMessagesmodedefault/Editorstyleheight: 200px; overflow-y: hiddenv-modelnewMessagekeyup.entersendMessage:defaultConfigeditorConfigmodedefaultonCreatedhandleCreated//divtemplate #footerel-button clickdialogVisible false取消/el-buttonel-button typeprimary clicksendMessage发送/el-button/template/el-dialogCSS
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 功能补充拓展
以下是一些可能的补充和优化确保 WebSocket 能够顺利运行并且高效处理消息。
1. 安全性和身份验证
如果你的 WebSocket 服务需要进行身份验证如用户登录你可以考虑在 WebSocket 握手时验证用户身份。你可以在 WebSocketConfig 中添加一个 HandshakeInterceptor 来拦截握手请求获取 HTTP header 或 URL 参数中的用户信息确保只有经过身份验证的用户能够连接。
例如
Override
public void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint(/ws).addInterceptors(new MyHandshakeInterceptor()) // 添加拦截器.setAllowedOrigins(http://localhost:8889).withSockJS();
}其中 MyHandshakeInterceptor 可以用来在 WebSocket 握手期间传递用户信息。
2. 异常处理
在 WebSocket 消息处理过程中你可能会遇到一些异常如数据库操作失败或消息传输失败等。在控制器中你可以捕获这些异常并返回相应的错误消息确保系统更加健壮。
例如
MessageMapping(/chat.sendMessage)
public MessageDTO sendMessage(MessageDTO messageDTO) {try {// 消息处理逻辑} catch (Exception e) {log.error(发送消息失败, e);return new MessageDTO(error, 消息发送失败);}
}3. 消息广播的功能
目前你的代码实现了将消息发送到指定用户的功能私信。如果你希望实现群聊功能或全局广播可以进一步扩展 SendTo 注解。这个注解的使用使得你可以将处理后的消息发送给所有订阅某个特定主题的客户端。
例如广播消息给所有订阅 /topic/messages 的客户端
MessageMapping(/chat.sendMessage)
SendTo(/topic/messages)
public MessageDTO sendMessage(MessageDTO messageDTO) {// 处理消息逻辑return messageDTO; // 返回的消息会广播给所有订阅 /topic/messages 的客户端
}4. 配置 WebSocket 消息缓存和负载均衡
如果你的系统需要处理大量的 WebSocket 连接可能会面临性能和可扩展性的问题。在这种情况下考虑使用 Redis 等消息队列作为消息代理可以通过 EnableWebSocketMessageBroker 配置远程消息代理。
例如通过 Redis 实现消息广播
Configuration
EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker(/queue, /topic, /user);config.setApplicationDestinationPrefixes(/app);// 使用 Redis 消息代理config.setBrokerRegistry().setApplicationDestinationPrefixes(/app).enableStompBrokerRelay(/topic, /queue).setRelayHost(localhost).setRelayPort(61613); // 配置Redis如ActiveMQ等消息中间件}
}5. 客户端连接管理
如果你希望在某个用户断开连接时进行一些清理工作例如清理会话、推送通知等你可以使用 OnDisconnect 注解来捕获断开连接事件。
MessageMapping(/chat.disconnect)
public void handleDisconnect(SessionDisconnectEvent event) {// 用户断开连接时执行的逻辑log.info(用户断开连接sessionId: event.getSessionId());
}6. WebSocket 消息格式和编码
确保你的客户端和服务端使用相同的消息格式。你可能需要为 WebSocket 消息提供适当的序列化和反序列化器以确保消息能够正确地从客户端传输到服务端以及从服务端传输回客户端。
例如使用 Jackson 或其他库将 Java 对象序列化为 JSON 格式
MessageMapping(/chat.sendMessage)
SendTo(/topic/messages)
public MessageDTO sendMessage(MessageDTO messageDTO) throws Exception {// 消息传输过程中确保使用适当的 JSON 格式return messageDTO;
}总结
大体上已经完成了 WebSocket 的配置和处理消息的核心部分剩下的步骤主要是根据具体业务需求做扩展如身份验证、消息缓存、广播支持等。如果你的应用规模较大可能还需要考虑负载均衡和消息队列等高可用性的设计。
后面将继续完善待更新…