SpringBoot 集成 STOMP 实现一对一聊天的两种方法

导读:本篇文章讲解 SpringBoot 集成 STOMP 实现一对一聊天的两种方法,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

前言

之前写过一篇SpringBoot 配置基于 wss 和 STOMP 的 WebSocket,而本文则将介绍两种实现单点聊天的方法,如果对配置基于 STOMPwssWebSocket 不太熟悉,建议先回看一下,本文的完整代码同样也已上传到GitHub

效果

在介绍最终的实现之前,先看一下效果,为了方便展示,使用了 iframe,以便可以同时展示四个窗口:

demo1

实现

为了实现能够将信息发给特定的用户,本文主要借用了 spring-messagingSimpMessagingTemplate 消息模板来实现,而下面的两种方法也是基于该消息模板的 convertAndSendconvertAndSendToUser 方法来实现。

基于 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 为发送者的 idto 为接收者的 idmessage 为具体的消息,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 通信,不过只要按照本文的例子,也很容易对其进行改造,希望本文能够对你有所帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/5374.html

(0)
小半的头像小半

相关推荐

极客之家——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!