Bootstrap

(二十五)Spring Boot 整合 WebSocket【群发消息】

1、创建项目,添加依赖

<!--Web Socket 依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator-core</artifactId>
</dependency>

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>sockjs-client</artifactId>
    <version>1.1.2</version>
</dependency>

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>stomp-websocket</artifactId>
    <version>2.3.3</version>
</dependency>

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.3.1</version>
</dependency>

spring-boot-starter websocket 依赖是 Web Socket 相关依赖,其他的都是前端库,使用 jar 包的形式对这些前端库进行统一管理,使用 webjar 添加到项目中的前端库,在 Spring Boot 项目中已经默认添加了静态资源过滤,因 可以直接使用。

2、配置 WebSocket

Spring 框架提供了基于 WebSocket 的 STOMP 支持, STOMP 是一个简单的可互操作的协议,通常被用于通过中间服务器在客户端之间进行异步消息传递。 WebSocket 配置如下:

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;

/**
 * 功能描述:自定义类配置 WebSocket
 * @author wi-gang
 * @date 2022/2/10 11:01 上午
 */

@Configuration
//开启 WebSocket 消息代理
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {


    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //定义一个前缀为"/chat"的 endpoint,并开启sockjs支持
        //sockjs可以解决浏览器对 WebSocket 的兼容性问题,客户端通过这里配置的URL来建立 WebSocket 连接
        registry.addEndpoint("/chat").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //设置消息代理的前缀,即如果消息的前缀是"/topic",就会将消息转发给消息代理(broker),再由消息代理将消息广播给当前连接的客户端。
        registry.enableSimpleBroker("/topic");
        //配置一个或者多个前缀,通过这些前缀过滤出需要被注解方法处理的消息
        //例如:前缀为"/app"的 destination 可以通过@MessageMapping注解的方法处理,而其他destination(例如"topic","/queue")将被直接交给broker处理。
        registry.setApplicationDestinationPrefixes("/app");
    }
}

3、定义 Controller

@RestController
public class TestController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Message greeting(Message message){
        return message;
    }
}


class Message{
    private String name;
    private String content;
	//省略get/set方法
}

@MessageMapping("/hello")注解的方法将用来接收/app/hello路径发送来的消息,在注解方法中对消息进行处理后,再将消息转发到@SendTo 定义的路径上,而@SendTo路径是一个前缀为/topic的路径,因此该消息将被交给消息代理 broker ,再由 broker 进行广播。

4、构建聊天页面

resources/static 目录下创建chat.html页面作为聊天页面,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>群聊</title>
    <!--引入外部的 JS 库,这些 JS 库在 pom.xml 文件中通过依赖加入进来-->
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <!--自定义js-->
    <script src="/app.js"></script>
</head>
<body>
<div>
    <label for="name">请输入用户名:</label>
    <input type="text" id="name" placeholder="用户名">
</div>
<div>
    <button id="connect" type="button">连接</button>
    <button id="disconnect" type="button" disabled="disabled">断开连接</button>
</div>
<div id="chat" style="display: none;">
    <div>
        <label for="name">请输入聊天内容:</label>
        <input type="text" id="content" placeholder="聊天内容">
    </div>
    <button id="send" type="button">发送</button>
    <div id="greetings">
        <div id="conversation" style="display: none;">群聊进行中....</div>
    </div>
</div>
</body>
</html>

resources/static/目下新建app.js

var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    console.log("--->",connected)
    if (connected) {
        $("#conversation").show();
        $("#chat").show();
    } else {
        $("#conversation").hide();
        $("#chat").hide();
    }
    $("#greetings").html("");
}

//建立一个 WebSocket 连接,在建立 WebSocket 连接时,用户必须先输入用户名,然后才能建立连接
function connect() {
    if (!$("#name").val()) {
        return;
    }
    //使用 SockJS 建立连接,然后创建一个 STOMP 实例发起连接请求 在连接成功的回调方法中,
    // 首先调用 setConnected(true);方法进行页面的设置,然后调用 STOMP 中的 subscribe 方法订阅服务端发送回来的消息,并将服务端发送来的消息展示出来(使用 showGreeting 方法)
    var socket = new SockJS('/chat');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body));
        });
    })
}

// 断开一个 WebSocket 连接
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
}

function sendName() {
    stompClient.send("/app/hello", {},
        JSON.stringify({
            'name': $("#name").val(), 'content': $("#content").val()
        }));
}

function showGreeting(message) {
    console.log("message--->",message)
    $("#greetings").append("<div>" + message.name + ":" + message.content + "</div>");
}

$(function () {
    $("#connect").click(function () {
        connect();
    });
    $("#disconnect").click(function () {
        disconnect();
    });
    $("#send").click(function () {
        sendName();
    });
})

5、测试

接下来启动 Spring Boot 项目进行测试,在浏览器中输入http://localhost:8080/chat.html,显示如下:
在这里插入图片描述
用户首先输入用户名,然后单击 “连接”按钮,结果如图所示
在这里插入图片描述
然后换个浏览器,或者使用 Chrome 浏览器的多用户(注意不是多窗口),重复刚才的步骤,这样就有两个用户连接上了,接下来就可以开始群聊了(当然也可以有更多的用户连接上来),如:
在这里插入图片描述

       @SendTo注解将方法处理过的消息转发到broker ,再由 broker 进行消息广播。除了@SendTo 注解外, Spring 还提供了 SimpMessagingTemplate 类来让开发者更加灵活地发送消息。可以在TestController中引入SimpMessagingTemplate。效果与上面一样。

@RestController
public class TestController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;


//    @MessageMapping("/hello")
//    @SendTo("/topic/greetings")
//    public Message greeting(Message message){
//        return message;
//    }

    @MessageMapping("/hello")
    public void greeting(Message message){
        simpMessagingTemplate . convertAndSend ("/topic/greetings", message) ;
    }
}
;