Spring Boot整和WebSocket
前言
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
一、消息群发
首先创建项目,pom文件添加如下依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.17.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hzw</groupId>
<artifactId>springboot-websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-websocket</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入前端库-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建一个配置websocket的类
spring框架提供了基于websocket的STOMP支持。
package com.hzw.websocket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker//开启websocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//定义一个chat的endPoint并开启sockjs
registry.addEndpoint("/chat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//消息代理
config.enableSimpleBroker("/topic");
//过滤出需要被注解方法处理的消息
config.setApplicationDestinationPrefixes("/app");
}
}
定义一个Controller用来实现对消息的处理
package com.hzw.websocket.controller;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
//用来接收/aoo/hello路径发送的消息对消息进行处理后再将消息转发到sendTo定义的路径上
//被交给消息代理broker
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Message greeting(Message message) throws Exception{
return message;
}
}
class Message{
private String name;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
构建聊天页面进行测试,注意,这里的js文件是引入外部的JS库,这些JS库在pom.xml文件中通过依赖加入进来。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
<script src="/webjars/jquery/3.3.1/jquery.min.js"></script>
<script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
</head>
<body>
<div>
<label for="name">请输入用户名:</label>
<input type="text" id="name" placeholder="用户名"/>
</div>
<div>
<button id="connect" type="button">连接</button>
<button id="disconnect" type="button" disabled>断开连接</button>
</div>
<div id="chat" style="display: none">
<div>
<label for="name">请输入聊天内容:</label>
<input type="text" id="content" placeholder="聊天内容"/>
</div>
<button id="send" type="button">发送</button>
<div id="greetings">
<div id="conversation" style="display: none">
群聊进行中...
</div>
</div>
</div>
<script >
var stompClient = null;
//连接状态
function setConnected(connected) {
$('#connect').prop("disabled",connected);
$('#disconnect').prop("disabled",!connected);
if (connected){
$('#conversation').show();
$('#chat').show();
} else{
$('#conversation').hide();
$('#chat').hide();
}
$("#greetings").html("");
}
function connect() {
if (!$('#name').val()){
return;
}
//创建一个连接并发起请求
var socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect({},function(frame) {
setConnected(true);
stompClient.subscribe('/topic/greetings',function (greeting) {
//订阅服务端发送回来的消息
showGreeting(JSON.parse(greeting.body));
});
});
}
//断开连接
function disconnect() {
if (stompClient!==null){
stompClient.disconnect();
}
setConnected(false);
}
//向服务端发送一个消息
function senName() {
stompClient.send("/app/hello",{},JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}));
}
function showGreeting(message) {
$("#greetings").append("<div>"+message.name+":"+message.content+"</div>");
}
$(function () {
$("#connect").click(function () {
connect();
});
$("#disconnect").click(function () {
disconnect();
});
$("#send").click(function () {
senName();
});
});
</script>
</body>
</html>
效果图:
二、消息点对点发送
点对点发送,就应该有用户的概念,首先在项目中添加spring security依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置Spring security :
package com.hzw.websocket.config;
import org.springframework.context.annotation.Bean;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class WebSecrityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest()
.authenticated()
.and()
.formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$2iKXGSiQxbbeHR7vV3mx0.cYtek3m/bR2mMwxEXRXD0CvwveQsheG")
.roles("admin")
.and()
.withUser("sang")
.password("$2a$10$2iKXGSiQxbbeHR7vV3mx0.cYtek3m/bR2mMwxEXRXD0CvwveQsheG")
.roles("user");
}
}
这里我们基于群发消息项目对WebSocket配置进行改造:
package com.hzw.websocket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker//开启websocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//定义一个chat的endPoint并开启sockjs
registry.addEndpoint("/chat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//消息代理,queue进行点对点消息管理
config.enableSimpleBroker("/topic","/queue");
//过滤出需要被注解方法处理的消息
config.setApplicationDestinationPrefixes("/app");
}
}
用到一个Chat实体类,用于封装消息的信息
package com.hzw.websocket.bean;
public class Chat {
private String to;
private String from;
private String content;
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
对WebSocket的Controller进行改造:
package com.hzw.websocket.controller;
import com.hzw.websocket.bean.Chat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.security.Principal;
@Controller
public class GreetingController {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
//用来接收/aoo/hello路径发送的消息对消息进行处理后再将消息转发到sendTo定义的路径上
//被交给消息代理broker
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Message greeting(Message message) throws Exception{
return message;
}
//点对点发送
@MessageMapping("/chat")
//Principal可以用来获取当前登录用户的信息,chat客户端发送来的信息
public void chat(Principal principal,Chat chat){
String from = principal.getName();
chat.setFrom(from);
simpMessagingTemplate.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);
}
}
class Message{
private String name;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
编写一个onlinechat.html页面作为点对点消息测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>群聊</title>
<script src="/webjars/jquery/3.3.1/jquery.min.js"></script>
<script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
</head>
<body>
<div>
<label for="name">请输入用户名:</label>
<input type="text" id="name" placeholder="用户名"/>
</div>
<div>
<button id="connect" type="button">连接</button>
<button id="disconnect" type="button" disabled>断开连接</button>
</div>
<div id="chat" style="display: none">
<div>
<label for="name">请输入聊天内容:</label>
<input type="text" id="content" placeholder="聊天内容"/>
</div>
<button id="send" type="button">发送</button>
<div id="greetings">
<div id="conversation" style="display: none">
群聊进行中...
</div>
</div>
</div>
<script >
var stompClient = null;
function setConnected(connected) {
$('#connect').prop("disabled",connected);
$('#disconnect').prop("disabled",!connected);
if (connected){
$('#conversation').show();
$('#chat').show();
} else{
$('#conversation').hide();
$('#chat').hide();
}
$("#greetings").html("");
}
function connect() {
if (!$('#name').val()){
return;
}
var socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect({},function(frame) {
setConnected(true);
stompClient.subscribe('/topic/greetings',function (greeting) {
showGreeting(JSON.parse(greeting.body));
});
});
}
function disconnect() {
if (stompClient!==null){
stompClient.disconnect();
}
setConnected(false);
}
function senName() {
stompClient.send("/app/hello",{},JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}));
}
function showGreeting(message) {
$("#greetings").append("<div>"+message.name+":"+message.content+"</div>");
}
$(function () {
$("#connect").click(function () {
connect();
});
$("#disconnect").click(function () {
disconnect();
});
$("#send").click(function () {
senName();
});
});
</script>
</body>
</html>
登录security中配置的两个用户,明文密码都为123456,登录成功后,就可以测试点对点在线聊天功能了,效果图如下: