Spring Boot整和WebSocket

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,登录成功后,就可以测试点对点在线聊天功能了,效果图如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值