Spring Boot2 系列教程 (十七) | 整合 WebSocket 实现聊天室

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

前言

昨天那篇介绍了 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、前端、算法资料分享

原文地址:https://www.cnblogs.com/nasus/p/12205082.html

时间: 2024-09-29 15:49:47

Spring Boot2 系列教程 (十七) | 整合 WebSocket 实现聊天室的相关文章

Spring Boot2 系列教程(十七)SpringBoot 整合 Swagger2

前后端分离后,维护接口文档基本上是必不可少的工作. 一个理想的状态是设计好后,接口文档发给前端和后端,大伙按照既定的规则各自开发,开发好了对接上了就可以上线了.当然这是一种非常理想的状态,实际开发中却很少遇到这样的情况,接口总是在不断的变化之中,有变化就要去维护,做过的小伙伴都知道这件事有多么头大!还好,有一些工具可以减轻我们的工作量,Swagger2 就是其中之一,至于其他类似功能但是却收费的软件,这里就不做过多介绍了.本文主要和大伙来聊下 在Spring Boot 中如何整合 Swagger

Spring Boot2 系列教程 (九) | SpringBoot 整合 Mybatis

前言 如题,今天介绍 SpringBoot 与 Mybatis 的整合以及 Mybatis 的使用,本文通过注解的形式实现. 什么是 Mybatis MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生 Map 使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记

Spring Boot2 系列教程 (十二) | 整合 thymeleaf

前言 如题,今天介绍 Thymeleaf ,并整合 Thymeleaf 开发一个简陋版的学生信息管理系统. SpringBoot 提供了大量模板引擎,包含 Freemarker.Groovy.Thymeleaf.Velocity 以及 Mustache,SpringBoot 中推荐使用 Thymeleaf 作为模板引擎,因为 Thymeleaf 提供了完美的 SpringMVC 支持.Thymeleaf 是新一代 Java 模板引擎,在 Spring 4 后推荐使用. 什么是模板引擎? Thym

Spring Boot2 系列教程 (十八) | 整合 MongoDB

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 如题,今天介绍下 SpringBoot 是如何整合 MongoDB 的. MongoDB 简介 MongoDB 是由 C++ 编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,它将数据存储为一个文档,数据结构由键值 (key=>value) 对组成.MongoDB 文档类似于 JSON 对象.字段值可以包含其他文档,数组及文档数组,非常灵活.存储结构如下: { "studentId": &qu

Spring Boot2 系列教程(二十七)Nginx 极简扫盲入门

上篇文章和大家聊了 Spring Session 实现 Session 共享的问题,有的小伙伴看了后表示对 Nginx 还是很懵,因此有了这篇文章,算是一个 Nginx 扫盲入门吧! 基本介绍 Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务. Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日. Nginx 特点是占有内存少,

Spring Boot2 系列教程(二十一) | 自动配置原理

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 这个月过去两天了,这篇文章才跟大家见面,最近比较累,大家见谅下.下班后闲着无聊看了下 SpringBoot 中的自动配置,把我的理解跟大家说下. 配置文件能写什么? 相信接触过 SpringBoot 的朋友都知道 SpringBoot 有各种 starter 依赖,想要什么直接勾选加进来就可以了.想要自定义的时候就直接在配置文件写自己的配置就好.但你们有没有困惑,为什么 SpringBoot 如此智能,到底配置文件里面能写

Spring Boot2 系列教程 (七) | 使用 Spring Data JPA 访问 Mysql

前言 如题,今天介绍 Spring Data JPA 的使用. 什么是 Spring Data JPA 在介绍 Spring Data JPA 之前,首先介绍 Hibernate . Hibernate 使用 O/R 映射 (Object-Relation Mapping) 技术实现数据访问, O/R 映射即将领域模型类与数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无需关注数据库相关技术. Hibernate 主导了 EJB 3.0 的 JPA 规范, JPA 即

Spring Boot2 系列教程 (五) | yaml 配置文件详解

自定义属性加载 首先构建 SpringBoot 项目,不会的看这篇旧文 使用 IDEA 构建 Spring Boot 工程. 首先在项目根目录 src >> resource >>?application.properties 文件下加入以下自定义属性: # 防止读取乱码 spring.http.encoding.charset=UTF-8 # 项目启动端口 server.port=9999 # 自定义配置 com.nasus.author.name=一个优秀的废人 com.nas

Spring Boot2 系列教程 (六) | 使用 JdbcTemplates 访问 Mysql

前言 如题,今天介绍 springboot 通过jdbc访问关系型mysql,通过 spring 的 JdbcTemplate 去访问. 准备工作 SpringBoot 2.x jdk 1.8 maven 3.0 idea mysql 构建 SpringBoot 项目,不会的朋友参考旧文章:如何使用 IDEA 构建 Spring Boot 工程 项目目录结构 pom 文件引入依赖 <dependencies> <!-- jdbcTemplate 依赖 --> <depende