美文网首页陈大志的 Java 笔记
SpringBoot 实战 (十七) | 整合 WebSocke

SpringBoot 实战 (十七) | 整合 WebSocke

作者: JavaFish | 来源:发表于2019-03-05 23:07 被阅读254次

    微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。

    前言

    昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。但这无法解决消息由谁发送,又由谁接收的问题。所以,今天写一篇实现一对一的聊天室。

    今天这一篇建立在昨天那一篇的基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot 整合WebSocket 实现广播消息

    准备工作

    • Spring Boot 2.1.3 RELEASE
    • Spring Security 2.1.3 RELEASE
    • IDEA
    • JDK8

    pom 依赖

    因聊天室涉及到用户相关,所以在上一篇基础上引入 Spring Security 2.1.3 RELEASE 依赖

    <!-- Spring Security 依赖 -->
    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    Spring Security 的配置

    虽说涉及到 Spring Security ,但鉴于篇幅有限,这里只对这个项目相关的部分进行介绍,具体的 Spring Security 教程,后面会出。

    这里的 Spring Security 配置很简单,具体就是设置登录路径、设置安全资源以及在内存中创建用户和密码,密码需要注意加密,这里使用 BCrypt 加密算法在用户登录时对密码进行加密。 代码注释很详细,不多说。

    package com.nasus.websocket.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @Configuration
    // 开启Spring Security的功能
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                 // 设置 SpringSecurity 对 / 和 "/login" 路径不拦截
                .mvcMatchers("/","/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                // 设置 Spring Security 的登录页面访问路径为/login
                .loginPage("/login")
                // 登录成功后转向 /chat 路径
                .defaultSuccessUrl("/chat")
                .permitAll()
                .and()
                .logout()
                .permitAll();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                // 在内存中分配两个用户 nasus 和 chenzy ,用户名和密码一致
                // BCryptPasswordEncoder() 是 Spring security 5.0 中新增的加密方式
                // 登陆时用 BCrypt 加密方式对用户密码进行处理。
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("nasus")
                // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对
                .password(new BCryptPasswordEncoder().encode("nasus")).roles("USER")
                .and()
                // 登陆时用 BCrypt 加密方式对用户密码进行处理。
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("chenzy")
                // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对
                .password(new BCryptPasswordEncoder().encode("chenzy")).roles("USER");
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            // /resource/static 目录下的静态资源,Spring Security 不拦截
            web.ignoring().antMatchers("/resource/static**");
        }
    }
    

    WebSocket 的配置

    在上一篇的基础上另外注册一个名为 "/endpointChat" 的节点,以供用户订阅,只有订阅了该节点的用户才能接收到消息;然后,再增加一个名为 "/queue" 消息代理。

    @Configuration
    // @EnableWebSocketMessageBroker 注解用于开启使用 STOMP 协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)
    // 开始支持@MessageMapping,就像是使用 @requestMapping 一样。
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            //注册一个名为 /endpointNasus 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。
            registry.addEndpoint("/endpointNasus").withSockJS();
            //注册一个名为 /endpointChat 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。
            registry.addEndpoint("/endpointChat").withSockJS();
        }
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配
            // 点对点增加一个 /queue 消息代理
            registry.enableSimpleBroker("/queue","/nasus/getResponse");
        }
    }
    

    控制器 controller

    指定发送消息的格式以及模板。详情见,代码注释。

    @Autowired
    //使用 SimpMessagingTemplate 向浏览器发送信息
    private SimpMessagingTemplate messagingTemplate;
    
    @MessageMapping("/chat")
    public void handleChat(Principal principal,String msg){
        // 在 SpringMVC 中,可以直接在参数中获得 principal,principal 中包含当前用户信息
        if (principal.getName().equals("nasus")){
            // 硬编码,如果发送人是 nasus 则接收人是 chenzy 反之也成立。
            // 通过 messageingTemplate.convertAndSendToUser 方法向用户发送信息,参数一是接收消息用户,参数二是浏览器订阅地址,参数三是消息本身
            messagingTemplate.convertAndSendToUser("chenzy",
                    "/queue/notifications",principal.getName()+"-send:" + msg);
        } else {
            messagingTemplate.convertAndSendToUser("nasus",
                   "/queue/notifications",principal.getName()+"-send:" + msg);
        }
    }
    

    登录页面

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <meta charset="UTF-8" />
    <head>
        <title>登陆页面</title>
    </head>
    <body>
    <div th:if="${param.error}">
        无效的账号和密码
    </div>
    <div th:if="${param.logout}">
        你已注销
    </div>
    <form th:action="@{/login}" method="post">
        <div><label> 账号 : <input type="text" name="username"/> </label></div>
        <div><label> 密码: <input type="password" name="password"/> </label></div>
        <div><input type="submit" value="登陆"/></div>
    </form>
    </body>
    </html>
    

    聊天页面

    <!DOCTYPE html>
    
    <html xmlns:th="http://www.thymeleaf.org">
    <meta charset="UTF-8" />
    <head>
        <title>Home</title>
        <script th:src="@{sockjs.min.js}"></script>
        <script th:src="@{stomp.min.js}"></script>
        <script th:src="@{jquery.js}"></script>
    </head>
    <body>
    <p>
        聊天室
    </p>
    
    <form id="nasusForm">
        <textarea rows="4" cols="60" name="text"></textarea>
        <input type="submit"/>
    </form>
    
    <script th:inline="javascript">
        $('#nasusForm').submit(function(e){
            e.preventDefault();
            var text = $('#nasusForm').find('textarea[name="text"]').val();
            sendSpittle(text);
        });
    
        // 连接 SockJs 的 endpoint 名称为 "/endpointChat"
        var sock = new SockJS("/endpointChat");
        var stomp = Stomp.over(sock);
        stomp.connect('guest', 'guest', function(frame) {
            // 订阅 /user/queue/notifications 发送的消息,这里与在控制器的
            // messagingTemplate.convertAndSendToUser 中订阅的地址保持一致
            // 这里多了 /user 前缀,是必须的,使用了 /user 才会把消息发送到指定用户
            stomp.subscribe("/user/queue/notifications", handleNotification);
        });
    
    
    
        function handleNotification(message) {
            $('#output').append("<b>Received: " + message.body + "</b><br/>")
        }
    
        function sendSpittle(text) {
            stomp.send("/chat", {}, text);
        }
        $('#stop').click(function() {sock.close()});
    </script>
    
    <div id="output"></div>
    </body>
    </html>
    

    页面控制器 controller

    @Controller
    public class ViewController {
    
        @GetMapping("/nasus")
        public String getView(){
            return "nasus";
        }
    
        @GetMapping("/login")
        public String getLoginView(){
            return "login";
        }
    
        @GetMapping("/chat")
        public String getChatView(){
            return "chat";
        }
    
    }
    

    测试

    预期结果应该是:两个用户登录系统,可以互相发送消息。但是同一个浏览器的用户会话的 session 是共享的,这里需要在 Chrome 浏览器再添加一个用户。

    具体操作在 Chrome 的 设置-->管理用户-->添加用户:

    谷歌浏览器添加用户

    两个用户分别访问 http://localhost:8080/login 登录系统,跳转至聊天界面:

    聊天界面

    相互发送消息:

    互发消息

    完整代码

    https://github.com/turoDog/Demo/tree/master/springboot_websocket_demo

    如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。

    后语

    如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。

    另外,关注之后在发送 1024 可领取免费学习资料。

    资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享

    一个优秀的废人,给你讲几斤技术

    相关文章

      网友评论

        本文标题:SpringBoot 实战 (十七) | 整合 WebSocke

        本文链接:https://www.haomeiwen.com/subject/dspauqtx.html