美文网首页JavaWeb 知识点
基于SpringBoot、STOMP使用WebSocket实现聊

基于SpringBoot、STOMP使用WebSocket实现聊

作者: 贾顺 | 来源:发表于2017-11-16 11:07 被阅读2499次

    原文连接: 基于SpringBoot、STOMP使用WebSocket实现聊天室功能

    WebSocket:

    新项目中有一个模块需求使用到了WebSocket,因为之前没用过,所以做了一些研究。

    关于WebSocket,简单说两句:最初前端与后端交互都是基于Http协议,前端发送request,后端返回response。存在的问题就是:response永远是被动的,不能主动发起。如果要想保持与后端的长连接,最初的实现方式基本都是ajax轮询或者http long poll。这两种方式都需要占用很多的资源,并且Http还是一个无状态协议。而WebSocket是HTML5出的东西,通俗点就是可以实现前端只发送一次请求,后端就可以与前端保持长连接,并实时的传输数据。最直白的例子就是聊天系统的实现,点对点两个人聊天,你一句我一句。也可以群嗨,一大群人叽叽歪歪,这样每个人对于其他人来说可以理解为一个广播系统,其他人可以理解为自己的订阅者。

    项目中基于SpringBoot和STOMP,其中的逻辑相对复杂,这儿仅写一个简单的Demo:实现点对点聊天功能。话不多说,直接开搞。

    代码中的具体解释看注释,都描述的很清楚。

    首先了解下STOMP:

    Stomp是一种简单(流)文本定向消息协议,提供了一个可互操作的链接格式。允许stomp客户端与任意stomp消息代理(Broker)进行交互。

    一:新建一个SpringBoot项目,选择Security、Thymeleaf和WebSocket依赖。

    WechatIMG45.jpeg

    二:Spring Security的简单配置

    本例只是实现了一个简单的聊天室程序。例子中有两个用户,互相发送消息给彼此,所以我们在这里我们先对Spring Security做一些简单的配置。对于Spring Security这里不做特别多的解释,Spring Security是专门针对基于Spring的项目安全框架,和Shiro一样可以实现程序的认证和权限控制。

    这里主要是分配两个用户,名字为“Michael”和“Janet”,密码都是“freedom”。

    package cn.js.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;
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    protected void configure(HttpSecurity httpSecurity) throws Exception{
    httpSecurity
    .authorizeRequests()
    .antMatchers("/","/login")//设置Spring Security对/和/"login"路径不拦截
    .permitAll()
    .anyRequest()
    .authenticated()
    .and()
    .formLogin()
    .loginPage("/login")//设置SpringSecurity的登录页面为/login
    .defaultSuccessUrl("/chat")//登录成功后转向/chat路径
    .permitAll()
    .and()
    .logout()
    .permitAll();
    }
    //在内存中分配两个用户Michael和Janet,密码都为freedom,角色都是USER
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
    authenticationManagerBuilder
    .inMemoryAuthentication()
    .withUser("Michael").password("freedom").roles("USER")
    .and()
    .withUser("Janet").password("freedom").roles("USER");
    }
    // /resources/static/目录下的静态资源,Spring Security不拦截
    public void configure(WebSecurity webSecurity)throws Exception{
    webSecurity.ignoring().antMatchers("/resources/static/**");
    }
    
    }
    

    三:配置WebSocket

    package cn.js.websocket.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    @Configuration
    //此注解表示开启WebSocket支持。通过此注解开启使用STOMP协议来传输基于代理(message broker)的消息。
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    //注册一个名为/endpointChat的节点,并指定使用SockJS协议。
    stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
    }
    //配置消息代理(Message Broker),可以理解为信息传输的通道
    public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry){
    //点对点式应增加一个/queue的消息代理。相应的如果是广播室模式可以设置为"/topic"
    messageBrokerRegistry.enableSimpleBroker("/queue");
    }
    }
    

    四:编写控制器

    package cn.js.websocket.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.stereotype.Controller;
    import java.security.Principal;
    
    @Controller
    public class WebSocketController {
    @Autowired
    //通过SimpMessagingTemplate模板向浏览器发送消息。如果是广播模式,可以直接使用注解@SendTo
    private SimpMessagingTemplate simpMessagingTemplate;
    
    //开启STOMP协议来传输基于代理的消息,这时控制器支持使用@MessageController,就像使用@RequestMapping是一样的
    //当浏览器向服务端发送请求时,通过@MessageController映射/chat这个路径
    @MessageMapping("/chat")
    //在SpringMVC中,可以直接在参数中获得principal,其中包含当前用户的信息
    public void handleChat(Principal principal,String msg){
    //下面的代码就是如果发送人是Michael,接收人就是Janet,发送的信息是message,反之亦然。
    if(principal.getName().equals("Michael")){
    //通过SimpMessagingTemplate的convertAndSendToUser向用户发送消息。
    //第一参数表示接收信息的用户,第二个是浏览器订阅的地址,第三个是消息本身
    simpMessagingTemplate.convertAndSendToUser("Janet","/queue/notifications",
    principal.getName() + "-发送:" + msg);
    } else {
    simpMessagingTemplate.convertAndSendToUser("Michael","/queue/notifications",
    principal.getName() + "-发送:" + msg);
    }
    }
    }
    

    五:添加脚本

    将sockjs.min.js(SockJS的客户端脚本)、stomp.js(STOMP协议的客户端脚本)、jquery-3.1.1.js(jQuery)放置在src/main/resources/static下,(可在文末GitHub中Clone项目得到三个文件)

    六:编写登录和聊天室页面

    因为此Demo基于Thymeleaf,所以在src/main/resources/templates下新建一个最简单的login.html页面,代码如下:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
    <meta charset="UTF-8">
    <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>
    

    在src/main/resources/templates下新建chat.html页面,代码如下:

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8" />
    <title>聊天页面</title>
    <script th:src="@{sockjs.min.js}"></script>
    <script th:src="@{stomp.js}"></script>
    <script th:src="@{jquery-3.1.1.js}"></script>
    </head>
    <body>
    <p>
    聊天室
    </p>
    
    <form id="JanetForm">
    <textarea rows="4" cols="60" name="text"></textarea>
    <input type="submit"/>
    </form>
    
    <script th:inline="javascript">
    $('#JanetForm').submit(function (e) {
    e.preventDefault();
    var text = $('#JanetForm').find('textarea[name="text"]').val();
    sendSpittle(text);
    });
    //    连接endpoint为"/endpointChat"的节点
    var sock = new SockJS("/endpointChat");
    var stomp = Stomp.over(sock);
    //    连接WebSocket服务端
    stomp.connect('guest','guest',function (frame) {
    //        订阅/user/queue/notifications发送的消息,这里与在控制器的messagingTemplate.convertAndSendToUser中定义的订阅地址保持一致。
    //        这里多了一个/user,并且这个user是必须的,使用了/user才会发送消息到指定的用户
    stomp.subscribe("/user/queue/notifications",handleNotification);
    });
    function handleNotification(message) {
    $('#output').append("<b>收到了:" + message.body + "</b><br/>")
    }
    function sendSpittle(text) {
    //        表示向后端路径/chat发送消息请求,这个是在控制器中@MessageMapping中定义的。
    stomp.send("/chat",{},text);
    }
    $('#stop').click(function () {
    {sock.close()}
    });
    </script>
    <div id="output"></div>
    </body>
    </html>
    

    七:增加页面的viewController,指定页面的跳转

    package cn.js.websocket.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurerAdapter{
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry){
    viewControllerRegistry.addViewController("/login").setViewName("/login");
    viewControllerRegistry.addViewController("/chat").setViewName("/chat");
    }
    }
    

    八:测试

    这样我们开两个浏览器窗口,地址为localhost:8080/login,分别用Michael和Janet两个用户登录,就可以互相发送消息聊天了。

    WechatIMG48.jpeg WechatIMG50.jpeg

    项目Github地址:https://github.com/jia-shun/websocket

    欢迎关注我的GitHub。

    参考资料:Java EE开发的颠覆者:SpringBoot 实战

    相关文章

      网友评论

        本文标题:基于SpringBoot、STOMP使用WebSocket实现聊

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