【关于SpringBoot整合WebSocket】

关于SpringBoot整合WebSocket

  1. 我们知道SpringBoot经典的语句就是约定大于配置,所以我们这里采用自动注入的方式进行添加webSocket
  2. 所以这里我们需要理解SpringBoot的自动注入原理,方便理解后进行别的功能添加。

我们介绍两种WebSocket进行整合

第一种不进行分组的方式进行连接订阅

 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
  <version>2.0.0.RELEASE</version>
 </dependency>
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
@EnableWebSocketMessageBroker
public class WebSoketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@Component
@ServerEndpoint("/webSocket/{uId}")
@Slf4j
public class WebSocketServerUtil {

    private Session session;
    private static CopyOnWriteArraySet<WebSocketServerUtil > webSocketSet = new CopyOnWriteArraySet<>();
    private static ConcurrentHashMap<Long,WebSocketServerUtil > webSocketMap  = new ConcurrentHashMap<>();
    private Long uId = null;


    @OnOpen
    public void onOpen(Session session, @PathParam("uId") Long uId){
        this.session = session;
        this.uId = uId;
        if(webSocketMap .containsKey(uId)){
            webSocketMap .remove(uId);
            webSocketMap .put(uId,this);
        }else{
            webSocketMap .put(uId,this);
            webSocketSet.add(this);
        }

        log.info("【websocket消息】有新的连接,总数:{}",webSocketMap.size());
    }

    @OnClose
    public void onClose(){
        if(webSocketMap.containsKey(uId)){
            webSocketMap.remove(uId);
            //从set中删除
            webSocketSet.remove(this);
        }
        log.info("【websocket消息】连接断开,总数:{}",webSocketSet.size());
    }

    @OnMessage
    public void onMessage(String message){
        log.info("【websocket消息】收到客户端发来的消息:{}",message);
    }

    public void sendMessage(String message){
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    
    /**
     * 发送自定义消息
     * 
     * */
    public static void sendInfo(String message,Long uId) throws Exception {
        log.info("发送消息到:"+uId+",报文:"+message);
        if(webSocketMap.containsKey(uId)){
             webSocketMap.get(uId).sendMessage(message);
         }else{
             log.error("用户"+uId+",不在线!");
             throw new Exception("连接已关闭,请刷新页面后重试");
         }
    }
}

后端推送消息

Long uId = new Long("1");
Map msgMap = new HashMap();
msgMap.put("step",1);
msgMap.put("type",2);
msgMap.put("msg","hello");
WebSocketServerUtil.sendInfo(JsonUtil.toJson(msgMap),uId);

前端JS

/**
 * 初始化websocket连接
 */
function initWebSocket() {
	let uId = 1;
	var websocket = null;
	if('WebSocket' in window) {
		websocket = new WebSocket("ws://localhost:8009/webSocket"+uId );
	} else {
		alert("该浏览器不支持websocket!");
	}
	websocket.onopen = function(event) {
		console.log("建立连接");
		websocket.send('Hello WebSockets!');
	}
	websocket.onclose = function(event) {
		console.log('连接关闭')
		reconnect(); //尝试重连websocket
	}
	//建立通信后,监听到后端的数据传递
	websocket.onmessage = function(event) {
		let data = JSON.parse(event.data);
		//业务处理....
		if(data.step == 1){
		   alert(data.msg);
		}
	}
	websocket.onerror = function() {
		// notify.warn("websocket通信发生错误!");
		// initWebSocket()
	}
	window.onbeforeunload = function() {
		websocket.close();
	}
// 重连
function reconnect() {
	console.log("正在重连");
	// 进行重连
	setTimeout(function () {
		initWebSocket();
	}, 1000);
}

第二种进行分组的方式进行连接订阅

这里我们采用了redisson,具体大家框架使用的是什么根据自己的框架进行调整
实现代码

		<!-- websocket连接 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
package com.ruoyi.framework.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;


 /**
 * @Description WebSocket配置文件
 * @Author GGBond
 * @Date 2023-07-10 14:05
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {


    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        registry.enableSimpleBroker("/topic", "/all");

        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/webServer").setAllowedOriginPatterns("*").withSockJS();
        registry.addEndpoint("/queueServer").setAllowedOriginPatterns("*").withSockJS();//注册两个STOMP的endpoint,分别用于广播和点对点
    }
}
package com.ruoyi.framework.config;

import com.ruoyi.framework.Interceptor.WebSocketInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

 /**
 * @Description 实现接口来配置Websocket请求的路径和拦截器。 
 * @Author GGBond
 * @Date 2023-07-10 14:05
 */
@Configuration
@EnableWebSocket
public class WebSocketH5Config implements WebSocketConfigurer {

    @Autowired
    WebSocketHandler locusHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //handler是webSocket的核心,配置入口
        registry.addHandler(locusHandler, "/myHandler/{destination}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
    }
}
package com.ruoyi.framework.core;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.utils.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class WebSocketService<T> extends BaseController {
    private final Logger logger = LoggerFactory.getLogger(getClass());


    @Autowired
    private SimpMessagingTemplate template;


    /**
     * @param destination 发送的主题
     * @param message     发送的消息
     * @description: 发送到指定主题
     * @author GGBond
     */
    @Async(value = "threadPoolTaskExecutor")
    public void sendTopicMessage(String destination, T message) {
        template.convertAndSend("/topic/" + destination, message);
    }


    /**
     * @param message 发送的消息
     * @description: 发送到公共主题
     * @author GGBond
     */
    @Async(value = "threadPoolTaskExecutor")
    public void sendTopicMessage(T message) {
        template.convertAndSend("/topic/chat", message);
    }


    /**
     * 给指定用户发送消息,并处理接收者不在线的情况
     *
     * @param receiver 消息接收者
     * @param payload  消息正文
     * @author GGBond
     */
    @Async(value = "threadPoolTaskExecutor")
    public void sendTopicMessageRedis(String receiver, T payload, Boolean flag) {
        if (flag) {
            template.convertAndSend("/topic/" + receiver, payload);
        } else {
            String listKey = Constants.WEBSOCKET_CODE_KEY + ":" + "/topic/" + receiver;
            logger.info(MessageFormat.format("消息接收者{}还未建立WebSocket连接,消息【{}】将被存储到Redis的【{}】列表中", receiver, payload, listKey));
            //存储消息到Redis中
            ArrayList<Object> arrayList = new ArrayList<>();
            arrayList.add(payload);
            RedisUtils.setCacheList(listKey, arrayList);
        }
    }

    //给指定用户組发送消息
    @Async(value = "threadPoolTaskExecutor")
    public void sendTopicMessage(List<Long> userIds, T message) {
        userIds.forEach(x -> {
            template.convertAndSend("/topic/" + x, message);
        });
    }

    //循环发送消息
    public void sendTopicMessageList(List<Object> list, Long userIds) {
        for (Object x : list) {
            template.convertAndSend("/topic/" + userIds, x);
        }
        RedisUtils.deleteObject(Constants.WEBSOCKET_CODE_KEY + ":" + "/topic/" + userIds);
    }
}
package com.ruoyi.framework.Interceptor;

import com.ruoyi.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

@Slf4j
public class WebSocketInterceptor implements HandshakeInterceptor {

    //进入hander之前的拦截器
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            String destination = request.getURI().toString().split("destination=")[1];
            if (destination == null || "".equalsIgnoreCase(destination.trim())) return false;
            log.info("当前session的destination={}", destination);
            map.put(Constants.LOCUS_WEBSOCKET_DESTINATION, destination);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        log.info("webSocket的afterHandshake拦截器!");
    }
}

接下来比较重要,配置好我们的spring.factories文件,文件路径需要自己更换

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.framework.core.WebSocketService,\
com.ruoyi.framework.config.WebSocketConfig
前端

这里用的是 SockJS。

npm install sockjs-client
npm install stompjs
import SockJS from  'sockjs-client';
import  Stomp from 'stompjs';
export default {
    data(){
        return {
            stompClient:'',
            timer:'',
        }
    },
    methods:{
        initWebSocket() {
            this.connection();
            let that= this;
            this.timer = setInterval(() => {
                try {
                    that.stompClient.send("test");
                } catch (err) {
                    console.log("断线了: " + err);
                    that.connection();
                }
            }, 5000);
        },  
        connection() {
            //后端的地址
            let socket = new SockJS('http://10.10.91.4:8081/ws');
            this.stompClient = Stomp.over(socket);
            let headers = {
                Authorization:''
            }
            this.stompClient.connect(headers,() => {
                this.stompClient.subscribe('/topic/public', (msg) => { 
                    console.log('广播成功')
                    console.log(msg);  
                },headers);
                this.stompClient.send("/app/chat.addUser",
                    headers,
                    JSON.stringify({sender: '',chatType: 'JOIN'}),
                )   
            }, (err) => {
                console.log('失败')
                console.log(err);
            });
        },    
        disconnect() {
            if (this.stompClient) {
                this.stompClient.disconnect();
            }
        },  
    },
    mounted(){
        this.initWebSocket();
    },
    beforeDestroy: function () {
        this.disconnect();
        clearInterval(this.timer);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现Spring Boot整合WebSocket,你需要进行以下步骤: 1. 首先,在pom.xml文件中添加WebSocket的相关依赖。可以使用以下两个依赖之一: - 从中提到的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` - 从中提到的依赖: ```xml <!--webSocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建WebSocket配置类,这个类负责配置WebSocket的相关信息。你可以按照以下方式创建一个配置类[3]: ```java package com.loit.park.common.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ``` 3. 至此,你已经完成了WebSocket整合配置。现在,你可以在你的应用中创建WebSocket的控制器并定义你的WebSocket端点。你可以根据你的需求来实现WebSocket端点的业务逻辑。 这就是Spring Boot整合WebSocket的基本步骤。通过这种方式,你可以在Spring Boot应用中轻松地使用WebSocket进行实时通信。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [springboot整合websocket](https://blog.csdn.net/weixin_45390688/article/details/120448778)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [springboot整合websocket(详解、教程、代码)](https://blog.csdn.net/hjq_ku/article/details/127503180)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值