关键词:
微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。
前言
昨天那篇介绍了 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、前端、算法资料分享
十七springboot2核心技术——整合mybatis(代码片段)
整合mybatis1.1、添加mybatis、数据库驱动依赖<dependencies><!--SpringBoot框架web项目起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web&l 查看详情
springboot2系列教程|整合thymeleaf
前言如题,今天介绍Thymeleaf,并整合Thymeleaf开发一个简陋版的学生信息管理系统。SpringBoot提供了大量模板引擎,包含Freemarker、Groovy、Thymeleaf、Velocity以及Mustache,SpringBoot中推荐使用Thymeleaf作为模板引擎,因为Thymeleaf提供了完美... 查看详情
springboot2系列教程|springboot整合mybatis
前言如题,今天介绍SpringBoot与Mybatis的整合以及Mybatis的使用,本文通过注解的形式实现。什么是MybatisMyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取... 查看详情
springboot2系列教程(十八)|整合mongodb
微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言如题,今天介绍下SpringBoot是如何整合MongoDB的。MongoDB简介MongoDB是由C++编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,它将... 查看详情
springboot2系列教程(十六)|整合websocket实现广播
前言如题,今天介绍的是SpringBoot整合WebSocket实现广播消息。什么是WebSocket?WebSocket为浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送信息,反之也成立。WebSocket是通过一个socket来实现双工异步通信能力... 查看详情
springboot2系列教程|整合数据缓存cache
如题,今天介绍SpringBoot的数据缓存。做过开发的都知道程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的,当需要重复获取相同数据时,一次又一次的请求数据库或者远程服务,导致大量时间耗费在数据库查询... 查看详情
springboot应用系列5--springboot2整合logback
上一篇我们梳理了SpringBoot2整合log4j2的配置过程,其中讲到了SpringBoot2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对于那些日志量不算太高的项目来说,选择logback更简单方便。1.pom.xmlpom.xml不需要添加... 查看详情
springboot应用系列6--springboot2整合quartz
Quartz是实现定时任务的利器,Quartz主要有四个组成部分,分别是:1.Job(任务):包含具体的任务逻辑;2.JobDetail(任务详情):是对Job的一种详情描述;3. Trigger(触发器):负责管理触发JobDetail的机制;4. Scheduler(调度器):负... 查看详情
springboot2系列教程|集成swagger2构建强大的restfulapi文档
前言快过年了,不知道你们啥时候放年假,忙不忙。反正我是挺闲的,所以有时间写blog。今天给你们带来SpringBoot集成Swagger2的教程。什么是Swagger2Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服... 查看详情
springboot2.x系列教程48--多数据源配置之aop动态切换数据源
SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情
springboot2.x系列教程48--多数据源配置之aop动态切换数据源
SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情
springboot2系列教程(十五)|服务端参数校验之一
估计很多朋友都认为参数校验是客户端的职责,不关服务端的事。其实这是错误的,学过Web安全的都知道,客户端的验证只是第一道关卡。它的参数验证并不是安全的,一旦被有心人抓到可乘之机,他就可以有各种方法来摸拟系... 查看详情
springboot2.0整合同时支持jsp+html跳转(代码片段)
springboot项目创建教程 https://blog.csdn.net/q18771811872/article/details/88126835springboot2.0跳转html教程 https://blog.csdn.net/q18771811872/article/details/88312862springboot2.0跳转jsp教程 https:/ 查看详情
2018最新springboot2.0教程(零基础入门)
一、零基础快速入门SpringBoot2.01、SpringBoot2.x课程全套介绍和高手系列知识点简介:介绍SpringBoot2.x课程大纲章节java基础,jdk环境,maven基础2、SpringBoot2.x依赖环境和版本新特性说明简介:讲解新版本依赖环境和springboot2新特性概述3... 查看详情
十六springboot2核心技术——整合jsp(代码片段)
一、整合jsp1.1、添加依赖<dependencies><!--springboot整合web框架起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId&g 查看详情
十六springboot2核心技术——整合jsp(代码片段)
一、整合jsp1.1、添加依赖<dependencies><!--springboot整合web框架起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId&g 查看详情
comicenhancerpro系列教程十七:二值化图像去毛刺
作者:马健邮箱:[email protected] 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23教程十七:二值化图像去毛刺 在灰度图像处理成纯黑白(二值化)图像以后,经常出现的一个问题是轮廓边缘出现毛刺。如下面这个图像: 为... 查看详情
springboot2.x最佳实践《一》之springboot2.x初体验
SpringBoot2.X最佳实践前言本系列文章,从零基础接触 SpringBoot2.x新版本,基础入门使用,热部署,到整合各个主流框架Redis4.x,消息队列AciveMQ,RocketMQ等,搜索框架ElasticSearch5.6版本,到web-flux反应式编程,到Actuator监控应用信息... 查看详情