【SOLUTION】Spring Boot 集成 WebSocket

1. Maven 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2. 注册ServerEndpointExporter、WebSocketConfigProperties

WebSocketConfigProperties:

  1. 建议代码中的第三方插件配置属性都通过@ConfigurationProperties注解类实现配置属性的定义,方便Spring管理和维护
  2. 建议第三方插件资源都设置一个enabel属性控制资源的加载,可以节省内存开销和代码安全
package xx.xx.xx.xx.xx;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;@Data
@ConfigurationProperties("spring.socket")
public class WebSocketConfigProperties {//是否开启WebSocketprivate boolean enabel;
}

ServerEndpointExporter:

  1. WebSocket 需要创建一个ServerEndpointExporter的实例,ServerEndpointExporter负责注册ServerEndpoint和管理ServerContainer
  2. 配置类继承BeanFactoryAware 是因为WebSocket在每次连接时都会重新创建ServerEndpoint类,所以导致在ServerEndpoint类中无法通过@Autowrite自动注入IOC中的Bean,需要借助BeanFactory来获取对应的类
package xx.xx.xx.xx.xx;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@ConditionalOnProperty(prefix = "spring.socket", name = "enabel", havingValue = "true")
@Configuration
@EnableConfigurationProperties(WebSocketConfigProperties.class)
public class WebSocketAutoConfig implements BeanFactoryAware {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {SocketBaseService.setBeanFactory(beanFactory);}
}

配置SocketBaseService

这里配置SocketBaseService类的原因有以下几点:

  1. 每个Socket端点都需要创建一个ServerEndpoint类,所以在代码上可以节省时间成本且规范ServerEndpoint
  2. ServerEndpoint类有4个声明周期@OnOpen@OnMessage@OnError@OnClose,而每次连接在四个周期方法内都需要统一处理一些逻辑,例如在@OnOpen时需要储存Session,在@OnClose时需要删除Session@OnError时需要记录错误日志等;所以如果不写SocketBaseService类,则需要在每个ServerEndpoint中写重复的逻辑,导致代码可读性低,维护难等问题
  3. 子级只需要通过继承SocketBaseService 抽象类,实现内部4个生命周期的抽象方法,即可实现对应的业务逻辑,由于子级的4个生命周期方法是父级抽象出来的,所以子级方法上就无需在标注@OnOpen@OnMessage@OnError@OnClose注解
package xx.xx.xx.xx.xx;import cn.hutool.core.util.*;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;import javax.websocket.*;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;public abstract class SocketBaseService {protected String randomId;protected Session session;protected static BeanFactory beanFactory;/*** 会话池*/private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();/*** 解析自动装配属性*/private void processAutowriteFields() {Field[] fields = this.getClass().getDeclaredFields();Arrays.stream(fields).forEach(field -> {Autowired annotation = field.getAnnotation(Autowired.class);if (ObjectUtil.isEmpty(annotation)) {return;}Object bean = SocketBaseService.beanFactory.getBean(field.getType());if (ObjectUtil.isNotEmpty(bean)) {try {field.setAccessible(true);field.set(this, bean);} catch (IllegalAccessException e) {}}});}@OnOpenpublic void onConnect(Session session) {this.processAutowriteFields();this.randomId = IdUtil.nanoId();this.session = session;sessionPool.put(randomId, session);this.afterConnect(session);}/*** 连接回调** @param session*/public abstract void afterConnect(Session session);/*** 消息** @param message*/@OnMessagepublic void onMessage(String message) {this.afterMessage(message);}/*** 接受消息回调** @param message*/public abstract void afterMessage(String message);/*** 连接错误** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {this.afterError(error);}/*** 连接错误回调** @param error*/public abstract void afterError(Throwable error);/*** 关闭连接*/@OnClosepublic void onClose() {this.beforeClose();sessionPool.remove(randomId);}/*** 关联连接回调*/public abstract void beforeClose();
}

案例:文件上传进度监听

在面对大文件上传时通常遇到无法即时获取上传进度的问题,在没有WebSocket之前都是异步上传,然后通过轮询的方式实时获取上传进度
而在有了WebSocket后就更方便了,可以实现持续连接,实现实时获取上传进度,步骤如下:

第一步:异步上传

由于userFileService.asynceUpload是异步方法,所以这里不会等待userFileService.asynceUpload方法执行,而是立即返回任务id

Controller
    @PostMapping("/asynceUpload")@Transactionalpublic Object asynceUpload(FileInfo info) {//获取一个异步上传任务id,方便后续跟踪文件上传进度String taskId = userFileService.getAsynceUploadTaskId(info);userFileService.asynceUpload(info, taskId);return taskId;}
Service

Spring如果想实现异步方法,需要在方法上添加@Async注解,并且在启动类或配置类上添加@EnabelAsync注解开启异步任务

@Async
@Override
public String asynceUpload(FileInfo info, String taskId) {
//处理文件
//更新进度
}

第二步:监听上传进度

监听步骤:

  1. 继承上文的SocketBaseService 抽象类,实现4个生命周期抽象方法
  2. afterConnect方法中注册定时任务,实现定时获取任务进度
  3. afterMessage中接受接口传的任务id
  4. beforeClose中关闭定时任务
  5. exce方法中编写获取任务进度的逻辑,并通过Session返回获取到的进度信息

注意:

  1. 子类中的teamDataService属性通过@Autowite注入成功是因为,在SocketBaseService onConnect方法中执行了processAutowriteFields方法,而processAutowriteFields通过获取当前类中标注了@Autowite注解的字段,再根据字段的类型通过BeanFactoryIOC获取对应的实例的方式注册
package xx.xx.xx.xx.xx.xx;import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.task.Task;
import cn.hutool.json.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xx.common.web.socket.SocketBaseService;
import xx.xx.client.server.domain.TeamData;
import xx.xx.client.server.service.TeamDataService;import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;@Service
@ServerEndpoint("/socket/getSyncFileTask")
public class UserFileSocketController extends SocketBaseService {private String scheduleId;private List<String> tasks = new ArrayList<>();@Autowiredprivate TeamDataService teamDataService;@Overridepublic void afterConnect(Session session) {scheduleId = CronUtil.schedule("*/2 * * * * *", new Task() {@Overridepublic void execute() {exce();}});}@Overridepublic void afterMessage(String message) {this.tasks = JSONUtil.toList(message, String.class);}@Overridepublic void afterError(Throwable error) {}@Overridepublic void beforeClose() {if (ObjectUtil.isNotEmpty(scheduleId)) {CronUtil.remove(scheduleId);}}private void exce() {if (session.isOpen()) {//获取任务进度List<TeamData> taskList = teamDataService.findByIds(tasks);//返回进度信息session.getAsyncRemote().sendText(JSONUtil.toJsonStr(taskList.stream().filter(el -> StrUtil.startWith(el.getKey(), "SYNCE_UPLOAD_")).collect(Collectors.toList())));}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/50895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

W30-python01-Selenium Web自动化基础--百度搜索案例-chrome浏览器为例

原理图 一、下载webdriver--chrome浏览器 根据本机浏览器的版本号下载对应的webdriver版本 http://chromedriver.storage.googleapis.com/index.html 二、安装selenium库 pip install selenium -i Simple Index 三、第一个Web自动化脚本 selenium实现Web自动化的基本步骤&…

【Qt 】JSON 数据格式详解

文章目录 1. JSON 有什么作用?2. JSON 的特点3. JSON 的两种数据格式3.1 JSON 数组3.2 JSON 对象 4. Qt 中如何使用 JSON 呢&#xff1f;4.1 QJsonObject4.2 QJsonArray4.3 QJsonValue4.4 QJsonDocument 5. 构建 JSON 字符串6. 解析 JSON 字符串 1. JSON 有什么作用? &#x…

1、hadoop环境搭建

1、环境配置 ip(/etc/sysconfig/network-scripts) # 网卡1 DEVICEeht0 TYPEEthernet ONBOOTyes NM_CONTROLLEDyes BOOTPROTOstatic IPADDR192.168.59.11 GATEWAY192.168.59.1 NETMASK 255.255.255.0 # 网卡2 DEVICEeht0 TYPEEthernet ONBOOTyes NM_CONTROLLEDyes BOOTPROTOdh…

算法通关:006_3二分查找:查找数组中<=num 最右边的值

文章目录 说明主要代码全部代码运行结果 说明 大于等于最右不考&#xff0c;意义不大。 直接看&#xff08;arr.length-1&#xff09; 位&#xff08;即数组最后一位&#xff09;&#xff0c;如果大于num&#xff0c;那就说明arr[arr.length-1]是大于等于最右的数字数组最后一…

Redux +Toolkit 工具包快速入门

您将学到什么 如何设置并使用 Redux Toolkit 和 React-Redux 先决条件 熟悉ES6 语法和功能了解 React 术语&#xff1a;JSX、State、Function Components 、 Props和Hooks理解Redux 术语和概念 1、基本使用 1.1、安装 Redux Toolkit 和 React- Redux 将 Redux Toolkit 和 Rea…

学习C语言第十四天(指针练习)

1.第一题C 2.第二题C 3.第三题 00345 short类型解引用一次访问两个字节 4.第四题 6&#xff0c;12 5.第五题C 6.第六题 下面代码结果是0x11223300 7.第七题 int main() {int a 0;int n 0;scanf("%d %d",&a,&n);int i 0;int k 0;int sum 0;for (i 0;…

sklearn聚类算法用于图片压缩与图片颜色直方图分类

上期文章:机器学习之SKlearn(scikit-learn)的K-means聚类算法 我们分享了sklearn的基本知识与基本的聚类算法,这里主要是机器学习的算法思想,前期文章我们也分享过人工智能的深度学习,二者有如何区别,可以先参考如下几个实例来看看机器学习是如何操作的 不同K值下的聚…

算法解决海量数据的 topK

目录 设计算法解决海量数据的 topK 问题如何统计不同电话号码的个数&#xff1f;题目描述解答思路与实现步骤注意点 如何在大量的数据中判断一个数是否存在&#xff1f;题目描述解决方案具体步骤优化与注意事项 如何从大量的 URL 中找出相同的 URL&#xff1f;题目描述解答思路…

系统架构设计师教程 第4章 信息安全技术基础知识-4.5 密钥管理技术4.6 访问控制及数字签名技术-解读

系统架构设计师教程 第4章 信息安全技术基础知识-4.5 密钥管理技术&4.6 访问控制及数字签名技术 4.5 密钥管理技术4.5.1 对称密钥的分配与管理4.5.1.1 密钥的使用控制4.5.1.1.1 密钥标签4.5.1.1.2 控制矢量4.5.1.2 密钥的分配4.5.1.2.1物理方式14.5.1.2.2 物理方式24.5.1…

【多线程】定时器

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 定时器是什么&#xff1f;2. 定时器的应用场景3. Timer类的使用3.1 Timer类创建定时器3.2 schedule()方法…

C语言——结构体(struct)对齐

目录 前言 一、结构体对齐规则 1、结构体的总大小对齐规则 2、结构体成员的对齐规则 3、数组和结构体的对齐规则 二、改变编译器对齐数&#xff08;#pragma pack&#xff09; 三、如何减小结构体占用内存 1、 重新排列成员顺序 2、使用#pragma pack指令 3、使用位域 4、其他 总…

使用sheetjs导出CSV文本为excel

使用SheetJS&#xff08;也称为xlsx库&#xff09;导出CSV文本为Excel文件&#xff0c;你可以先将CSV文本解析为SheetJS支持的工作表格式&#xff0c;然后再将其写入为一个新的Excel文件。以下是一个简单的示例代码&#xff1a; const XLSX require(xlsx); const fs requir…

.net core 8.0 新建的项目无法使用 IApplicationBuilder

1、在项目文件中添加 <ItemGroup><FrameworkReference Include"Microsoft.AspNetCore.App" /> </ItemGroup> 2、在使用的地方添加 using Microsoft.AspNetCore.Builder;

工作流 Flowable

工作流包括业务流和审批流等业务流程。 在一个流程系统中&#xff0c;任务间往往存在复杂的依赖关系&#xff0c;为保证pipeline的正确执行&#xff0c;就是要解决各任务间依赖的问题&#xff0c;这样DAG结合拓扑排序是解决存在依赖关系的一类问题的利器。DAG ( Directed Acyc…

池化层pytorch最大池化练习

神经网络构建 class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 MaxPool2d(kernel_size3, ceil_modeFalse)def forward(self, input):output self.maxpool1(input)return output Tensorboard 处理 writer SummaryWriter("./l…

【React】详解如何获取 DOM 元素

文章目录 一、基础概念1. 什么是DOM&#xff1f;2. 为什么需要获取DOM&#xff1f; 二、使用 ref 获取DOM元素1. 基本概念2. 类组件中的 ref3. 函数组件中的 ref 三、 ref 的进阶用法1. 动态设置 ref2. ref 与函数组件的结合 四、处理特殊情况1. 多个 ref 的处理2. ref 与条件渲…

基于STM32F103的FreeRTOS系列(四)·FreeRTOS资料获取以及简介

目录 1. FreeRTOS简介 1.1 FreeRTOS介绍 1.2 为何选择FreeRTOS 1.3 FreeRTOS资料获取 1.3.1 官网下载 1.3.2 Github下载 1.3.3 托管网站下载 1.4 FreeRTOS的编程风格 1.4.1 数据类型 1.4.2 变量名 1.4.3 函数名 1.4.4 宏 1. FreeRTOS简介 1.1 Free…

11. Hibernate 持久化对象的各种状态

1. 前言 本节课和大家聊聊持久化对象的 3 种状态。通过本节课程&#xff0c;你将了解到&#xff1a; 持久化对象的 3 种状态&#xff1b;什么是对象持久化能力。 2. 持久化对象的状态 程序运行期间的数据都是存储在内存中。内存具有临时性。程序结束、计算机挂机…… 内存中…

前端开发大屏适配几种方案

方案一&#xff1a;vw&#xff08;单位&#xff09; 假设设计稿尺寸为 1920*1080&#xff0c;直接使用 vw 单位&#xff0c;屏幕的宽默认为 100vw&#xff0c;那么100vw 1920px&#xff0c; 1vw 19.2px 。 新建px2vw.scss / 使用 scss 的 math 函数 use "sass:math&q…

Web前端浅谈ArkTS组件开发

本文由JS老狗原创。 有幸参与本厂APP的鸿蒙化改造&#xff0c;学习了ArkTS以及IDE的相关知识&#xff0c;并有机会在ISSUE上与鸿蒙各路大佬交流&#xff0c;获益颇丰。 本篇文章将从一个Web前端的视角出发&#xff0c;浅谈ArkTS组件开发的基础问题&#xff0c;比如属性传递、插…