SpringBoot整合WebSocket

533 阅读1分钟

SpringBoot整合websocket

群聊案例

创建项目

1620475745793.png

前端库可以自己下载对应需要的js文件放入项目中的static中(前后端分离需要处理跨域),这里为了演示方便,以下我采取单应用webjar模式

maven仓库 mvnrepository.com/ 下载需要的前端库

1620475745793.png

原生的websocket兼容性较差,这里我们在引入一个兼容性更好的stomp-websocket

1620475745793.png

1620475745793.png

定位器依赖(不需要指定版本)

1620475745793.png

完整的依赖配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/sockjs-client -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/stomp-websocket -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/jquery -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/webjars-locator-core -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
    </dependencies>

项目结构

1620475745793.png

@Configuration
@EnableWebSocketMessageBroker // 开启消息代理
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
    /**
     * 注册端点
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 定义一个 前缀为 chat 的端点,开启 socketjs 支持,前端需要连接这个端点
        registry.addEndpoint("/chat").withSockJS();
    }

    /**
     * 配置消息代理
    * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置消息代理前缀,发送消息的前缀如果是 /topic ,就会把消息转发给消息代理(Broker)
        // 代理再把消息广播给所有的客户端
        registry.enableSimpleBroker("/topic");
        // 上一行配置后,消息代理就配置好了,但是如果有些消息不想采取代理处理,想自己处理,可以自定义
        // 下面这行配置的意思是,如果消息的前缀是 /app ,采取特殊处理
        // 这里用不到,注释掉先
        // registry.setApplicationDestinationPrefixes("/app");
    }
}

public class Message {
    private String name;// 消息来源
    private String content;// 消息内容
    //getter,setter...
}
@Controller
public class GreetingController {
    @MessageMapping("/hello") // 接收消息地址
    @SendTo("/topic/greeting") // 转发给广播地址,前端监听这个地址就能收到消息
    public Message greeting(Message message) {
        return message;
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <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>
</head>
<body>
    <!-- 模拟用户名 -->
    <div>
        <input id="username" type="text" placeholder="用户名">
    </div>
    <!--连接断开按钮-->
    <div>
        <button type="button" id="connect">连接</button>
        <button type="button" id="disconnect" disabled="disabled">断开连接</button>
    </div>
    <!--输入聊天内容-->
    <div>
        <input type="text" id="content">
        <button id="send" type="button" disabled="disabled">发送</button>
    </div>
    <!--显示聊天内容-->
    <div id="chat" style="border:5px solid #000"></div>

    <script>
        var stompClient;
        $(function () {
            // 点击连接按钮
            $('#connect').click(function () {
                connect();
            })
            // 点击断开连接按钮
            $('#disconnect').click(function () {
                stompClient.disconnect();
                setConnect(false);
            })
            // 点击发送内容按钮
            $('#send').click(function () {
                // 参数1:发送 消息的地址接口
                // 参数2:优先级 一般空着就行
                // 参数3:消息本身 json格式字符串
                stompClient.send('/hello',{},JSON.stringify({
                    'name':$('#username').val(),
                    'content':$('#content').val()
                }))
            })
        })
        // 建立连接
        function connect() {
            // 判断是否添加用户名
            if (!$('#username').val()) {
                return;
            }
            // 指定端点
            var socketjs = new SockJS("/chat");
            // 创建 Stomp 对象来操作 websocket
            stompClient = Stomp.over(socketjs);
            // 建立连接
            stompClient.connect({},function (frame) {// 连接成功的回调函数
                // 连接成功 连接按钮禁用,断开连接按钮启用
                setConnect(true);
                // 监听 广播地址
                // greeting是返回的消息
                stompClient.subscribe('/topic/greeting',function (greeting) { 
                    var msgBody = JSON.parse(greeting.body);
                    $('#chat').append('<div>'+msgBody.name+' : '+ msgBody.content +'</div>')
                })
            })
        }
        // 按钮状态设置
        function setConnect(connected) {
            $('#connect').prop('disabled',connected);
            $('#disconnect').prop('disabled',!connected);
            $('#send').prop('disabled',!connected);
        }
    </script>
</body>
</html>

启动浏览器测试(启动两个不同的浏览器,Chrome可以采取访客模式模拟)

1620484334567.png

私聊案例

私聊中涉及用户概念,配合security将非常的舒服,如果采取shiro就比较麻烦了,案例采取security来配置用户

依赖配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/sockjs-client -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/stomp-websocket -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/jquery -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.webjars/webjars-locator-core -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

项目结构

1620484382470.png

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("southyin")
                .password("{noop}123")
                .roles("admin")
                .and()
                .withUser("root")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and().formLogin().permitAll();
    }
}
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
    /**
     * 注册端点
     * 新版的 springboot 配合 security 在配置端点时,需要指定允许连接的域
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 定义一个 前缀为 chat 的端点,开启 socketjs 支持
        registry.addEndpoint("/chat").setAllowedOriginPatterns("http://localhost:8080").withSockJS();
    }

    /**
     * 配置消息代理
    * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置消息代理前缀,发送消息的前缀如果是 /topic ,就会把消息转发给消息代理(Broker)
        // 代理再把消息广播给所有的客户端
        // /queue 用来做点对点,不影响群聊
        registry.enableSimpleBroker("/topic","/queue");
    }
}
@Controller
public class GreetingController {
    @MessageMapping("/hello") // 消息地址
    @SendTo("/topic/greeting") // 转发给广播地址,前端监听这个地址就能收到消息
    public Message greeting(Message message) {
        // 作用和 SendTo 注解等效
        // messagingTemplate.convertAndSend("/topic/greeting",message);
        return message;
    }

    // 点对点需要的对象
    @Autowired
    SimpMessagingTemplate messagingTemplate;
    /**
     * @param principal 用户信息
     * @param chat 发过来的具体消息
     */
    @MessageMapping("/online_chat")
    public void chat(Principal principal, Chat chat) {
        // 获取登录者用户名
        String name = principal.getName();
        chat.setFrom(name);
        // 私聊的消息转发到 /queue/chat 前端监控获取信息
        messagingTemplate.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);
    }
}
public class Chat {
    private String to;// 发给谁
    private String from;// 谁发的
    private String content;//内容
    // getter,setter
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <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>
</head>
<body>
    <!-- 模拟用户名 -->
    <div>
        <input id="username" type="text" placeholder="目标用户名">
    </div>
    <!--输入聊天内容-->
    <div>
        <input type="text" id="content">
        <button id="send" type="button">发送</button>
    </div>
    <!--显示聊天内容-->
    <div id="chat" style="border:5px solid #000"></div>

    <script>
        var stompClient;
        $(function () {
            // 建立连接
            connect();
            // 点击发送内容按钮
            $('#send').click(function () {
                // 参数1:发送 消息的地址接口
                // 参数2:优先级 一般空着就行
                // 参数3:消息本身
                stompClient.send('/online_chat',{},JSON.stringify({
                    'to':$('#username').val(),
                    'content':$('#content').val()
                }))
            })
        })
        // 建立连接
        function connect() {
            // 指定端点
            var socketjs = new SockJS("/chat");
            // 创建 Stomp 对象来操作 websocket
            stompClient = Stomp.over(socketjs);
            // 建立连接
            stompClient.connect({},function (frame) {// 连接成功的回调函数
                // 监听 广播地址
                // 私聊需要添加 user 前缀,不可省略!!!!!!
                // greeting是返回的消息
                stompClient.subscribe('/user/queue/chat',function (greeting) { 
                    var msgBody = JSON.parse(greeting.body);
                    $('#chat').append('<div>'+msgBody.from+' : '+ msgBody.content +'</div>')
                })
            })
        }
    </script>
</body>
</html>

测试

1620484382470.png