SpringBoot 集成 STOMP 实现一对一聊天的两种方法
前言
之前写过一篇SpringBoot 配置基于 wss 和 STOMP 的 WebSocket,而本文则将介绍两种实现单点聊天的方法,如果对配置基于 STOMP
和 wss
的 WebSocket
不太熟悉,建议先回看一下,本文的完整代码同样也已上传到GitHub。
效果
在介绍最终的实现之前,先看一下效果,为了方便展示,使用了 iframe
,以便可以同时展示四个窗口:
实现
为了实现能够将信息发给特定的用户,本文主要借用了 spring-messaging
的 SimpMessagingTemplate
消息模板来实现,而下面的两种方法也是基于该消息模板的 convertAndSend
和 convertAndSendToUser
方法来实现。
基于 convertAndSendToUser
方法的实现
为了使用 convertAndSendToUser
方法能指定发送信息给特定用户,首先需要添加一个自定义的处理器,用于生成用户唯一的标识:
public class CustomHandshakeHandler extends DefaultHandshakeHandler { @Override protected Principal determineUser( ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { // 获取例如 wss://localhost/websocket/1 订阅地址 // 中的最后一个用户 id 参数作为用户的标识, // 为实现发送信息给指定用户做准备 String uri = request.getURI().toString(); String uid = uri.substring(uri.lastIndexOf("/") + 1); return () -> uid; } }
以上自定义处理器用于设置用户唯一的标识为用户的 uid
,用户只要在连接 websocket
时,在订阅地址 wss://localhost/websocket/
后加上用户的 id
,即可作为用户的唯一标识。
然后就是启用 websocket
消息代理的设置:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 开启一个简单的基于内存的消息代理 // 将消息返回到订阅了带 /chat 前缀的目的客户端 config.enableSimpleBroker("/chat"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册一个 /websocket/{id} 的 WebSocket 终端 // {id} 用于让用户连接终端时都可以有自己的路径 // 作为 Principal 的标识,以便实现向指定用户发送信息 registry.addEndpoint("/websocket/{id}") .setHandshakeHandler(new CustomHandshakeHandler()); } }
完成以上的配置后就已经有了一个 websocket
终端,下面就介绍对消息的处理,为了便于处理消息,所有的消息都封装成了以下实体:
@Data @NoArgsConstructor @AllArgsConstructor public class MessageEntity { private Long from; private Long to; private String message; private Date time; }
其中 from
为发送者的 id
,to
为接收者的 id
,message
为具体的消息,time
为消息的发送时间。
然后再介绍发送消息的接口:
@RestController public class ChatController { private final MessageService messageService; @Autowired public ChatController(MessageService messageService) { this.messageService = messageService; } // 这里的 @MessageMapping 可以当成 @RequestMapping, // 当有信息 (sendMsg 方法中的 messageEntity 参数即为客服端发送的信息实体) // 发送到 /sendMsg 时会在这里进行处理 @MessageMapping("/sendMsg") public void sendMsg(MessageEntity messageEntity) { messageService.sendToUser(messageEntity); } }
最后是消息模板发送信息方法:
@Service public class MessageService { private final SimpMessagingTemplate simpMessagingTemplate; @Autowired public MessageService(SimpMessagingTemplate simpMessagingTemplate) { this.simpMessagingTemplate = simpMessagingTemplate; } public void sendToUser(MessageEntity messageEntity) { // convertAndSendToUser 方法可以发送信给给指定用户, // 底层会自动将第二个参数目的地址 /chat/contact 拼接为 // /user/username/chat/contact,其中第二个参数 username 即为这里的第一个参数 // username 也是前文中配置的 Principal 用户识别标志 simpMessagingTemplate.convertAndSendToUser( String.valueOf(messageEntity.getTo()), "/chat/contact", messageEntity ); } }
进行以上后端的后端配置(省略了 wss
的配置,如果不清楚,可以参考前言里的文章),即完成了后端代码的编写,下面再来介绍前端界面的编写:
首先是单个聊天界面的代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> <script src="/webjars/stomp-websocket/stomp.min.js"></script> </head> <body> <label><input id="uid"/></label> <button onclick="login()" id="login">登录</button> <label><input id="msg" placeholder="信息后加 -id,发给指定人"/></label> <button onclick="sendMsg()">发送</button> <div id="user"></div> <div id="greet"></div> <script> let stompClient function login() { // 根据输入的 id 号模拟不同用户的订阅 let socket = new WebSocket(`wss://localhost/websocket/${document.getElementById('uid').value}`) stompClient = Stomp.over(socket) stompClient.connect({}, function () { // 所有想要接收给指定用户发送的信息的订阅地址都必须加上/user前缀 // 这里是为了配合后台的 convertAndSendToUser 方法,如果使用 // convertAndSend,就不需要 /user 前缀了,下面会再介绍 stompClient.subscribe(`/user/chat/contact`, function (frame) { let entity = JSON.parse(frame.body) showGreeting(`收到用户${entity.from}的信息: ${entity.message}`) }) }) document.getElementById('user').innerText = `当前用户为:${document.getElementById('uid').value}` function showGreeting(clientMessage) { document.getElementById("greet").innerText += `${clientMessage}\n` } } function sendMsg() { const msg = document.getElementById('msg').value stompClient.send("/sendMsg", {}, JSON.stringify({ from: document.getElementById('uid').value, to: msg.substring(msg.lastIndexOf('-') + 1), message: msg.substring(0, msg.lastIndexOf('-')), time: new Date() })) } </script> </body> </html>
然后是为了有四个聊天界面的代码:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>聊天</title> <style> html, body, #app { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; } iframe { width: 49%; height: 49%; border: aquamarine 1px solid; } </style> </head> <body> <div id="app"> <iframe src="https://localhost/index"></iframe> <iframe src="https://localhost/index"></iframe> <iframe src="https://localhost/index"></iframe> <iframe src="https://localhost/index"></iframe> </div> </body> </html>
进行了以上的配置后,就可以模拟实现简单的单点聊天了,下面再介绍使用消息模板的 convertAndSend
方法来实现单点聊天。
基于convertAndSend
方法的实现
基于convertAndSend
方法的实现不同于基于 convertAndSendToUser
时主要是通过后端进行配置,如果使用 convertAndSend
就只需要在前端订阅时进行控制即可,这么说可能不太清晰,下面就具体展示:
基于convertAndSend
就不再需要自定义处理器了,终端也不再需要/{id}
了:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig2 implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/chat"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/websocket"); } }
消息接口处理的代码还是一样:
@RestController public class ChatController2 { private final MessageService2 messageService; @Autowired public ChatController2(MessageService2 messageService) { this.messageService = messageService; } @MessageMapping("/sendMsg2") public void sendMsg(MessageEntity messageEntity) { messageService.sendToUser(messageEntity); } }
不过消息模板的方法实现就有所不同了:
@Service public class MessageService2 { private final SimpMessagingTemplate simpMessagingTemplate; @Autowired public MessageService2(SimpMessagingTemplate simpMessagingTemplate) { this.simpMessagingTemplate = simpMessagingTemplate; } public void sendToUser(MessageEntity messageEntity) { simpMessagingTemplate.convertAndSend("/chat/contact/" + messageEntity.getTo(), messageEntity); } }
这里通过在发送地址后拼接目的用户的 id
,然后再配合前端订阅时的处理即可实现发送发送信息给特定用户,下面是前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> <script src="/webjars/stomp-websocket/stomp.min.js"></script> </head> <body> <label><input id="uid"/></label> <button onclick="login()" id="login">登录</button> <label><input id="msg" placeholder="信息后加 -id,发给指定人"/></label> <button onclick="sendMsg()">发送</button> <div id="user"></div> <div id="greet"></div> <script> let stompClient function login() { let socket = new WebSocket(`wss://localhost/websocket`) stompClient = Stomp.over(socket) stompClient.connect({}, function () { // 由于使用了 convertAndSend, 这里就不再需要加 /user 前缀了 // 只要在订阅地址后加上自己的 id 即可发送给自己的信息 stompClient.subscribe(`/chat/contact/${document.getElementById('uid').value}`, function (frame) { let entity = JSON.parse(frame.body) showGreeting(`收到用户${entity.from}的信息: ${entity.message}`) }) }) document.getElementById('user').innerText = `当前用户为:${document.getElementById('uid').value}` function showGreeting(clientMessage) { document.getElementById("greet").innerText += `${clientMessage}\n` } } function sendMsg() { const msg = document.getElementById('msg').value stompClient.send("/sendMsg2", {}, JSON.stringify({ from: document.getElementById('uid').value, to: msg.substring(msg.lastIndexOf('-') + 1), message: msg.substring(0, msg.lastIndexOf('-')), time: new Date() })) } </script> </body> </html>
以上便是基于 STOMP
实现单点聊天的两种方法,如果有不清楚的地方也可以留言反馈。
总结
本文通过一个简单的例子介绍了两种实现单点聊天的方法,在下一篇文章将会通过一个基于 SpringBoot + Vue
的比较完善的例子来展示单点聊天的实现,不过下一篇的例子并未使用本文的 STOMP
,而只是用了基本的 WebSocket
通信,不过只要按照本文的例子,也很容易对其进行改造,希望本文能够对你有所帮助。
本文作者:庄周de蝴蝶
本文链接:https://www.cnblogs.com/butterfly-fish/p/17762295.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步