当前位置: 首页 > article >正文

Springboot中添加原生websocket支持

1、添加配置

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器,并允许所有来源的连接(在生产环境中应限制来源)registry.addHandler(new WebSocketHandler(), "/ws/[请求的地址]").setAllowedOrigins("*").addInterceptors(new WebSocketSecurityTokenInterceptor());}
}

2、添加Handler对请求进行处理

@Component
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();private static final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());private static final Map<String,Long> lastPongTimes = new ConcurrentHashMap<>();private static final String PING = "ping";private static final String PONG = "pong";private static final String GET_DATA = "getData";/*** 检测心跳是否正常的周期时间*/private static final Integer heartbeatInterval = 30_000;/*** 检测客户端连接心跳保持时间是否超时的时间*/private static final Integer heartbeatTimeout = 60_000;@Resourceprivate ReportDashboardService reportDashboardService;// 使用Guava 弱引用缓存数据private static final Cache<String, Object> CACHE = CacheBuilder.newBuilder().softValues().expireAfterWrite(3, TimeUnit.SECONDS).build();@PostConstructpublic void init() {scheduledThreadPool.scheduleWithFixedDelay(() -> {try {checkHeartbeats();} catch (IOException e) {throw new RuntimeException(e);}},heartbeatInterval,heartbeatInterval,TimeUnit.SECONDS);}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 广播消息给所有已连接的客户端String payload = message.getPayload();if (PING.equals(payload)) {session.sendMessage(new TextMessage(PONG));} else if (GET_DATA.equals(payload)) {sendData(session);} else {sendDataByPayload(session,payload);//broadcast(payload);}recordPong(session.getId());}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 当新连接建立时添加到列表sessions.add(session);//session.sendMessage(new TextMessage(PONG));recordPong(session.getId());sendData(session);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// 当连接关闭时从列表中移除sessions.remove(session);log.info("Connection closed.sessionId={},status={}",session.getId(),status);}private void sendData(WebSocketSession sess) {try {if (sess.isOpen()) {String query = sess.getUri().getQuery();String[] split = query.split("&");ReqObj req = new ReqObj();String path = "";for (String s : split) {String[] arr = s.split("=");if ("path".equals(arr[0])) {path = arr[1];} else if ("deviceCode".equals(arr[0])) {req.setDeviceCode(arr[1]);} else if ("pointType".equals(arr[0])) {req.setPointType(arr[1]);} else if ("gap".equals(arr[0])) {if (arr[1] != null) {req.setGap(Integer.parseInt(arr[1]));}}}sess.sendMessage(new TextMessage(sendDataByPath(path,req)));}} catch (Exception e) {System.err.println("Failed to send message: " + e.getMessage());}}private boolean closeTimeoutSession(String sessionId) throws IOException {WebSocketSession s = null;for (WebSocketSession sess : sessions) {if (sess.isOpen() && sess.getId().equals(sessionId)) {sess.sendMessage(new TextMessage("当前连接1分钟内未发送心跳消息,即将关闭"));s = sess;}}log.info("关闭心跳超过的连接,sessionId={}",sessionId);return s != null && sessions.remove(s);}private void recordPong(String sessionId) {lastPongTimes.put(sessionId,System.currentTimeMillis());}private boolean isClientAlive(String sessionId) {Long lastPongTime = lastPongTimes.get(sessionId);if (lastPongTime == null){return false;}return System.currentTimeMillis() - lastPongTime <= heartbeatTimeout;}private void checkHeartbeats() throws IOException {log.info("开始检查连接的心跳是否超时......");Set<Map.Entry<String, Long>> entries = lastPongTimes.entrySet();for (Map.Entry<String, Long> entry : entries) {String sessionId = entry.getKey();log.info("sessionId = {}",sessionId);if (!isClientAlive(sessionId)) {closeTimeoutSession(sessionId);}}}private void sendDataByPayload(WebSocketSession sess,String payload){try {if (sess.isOpen()) {ChartDto dto = null;String cacheKey = null;try {cacheKey = MD5Util.encrypt(payload);dto = JSON.parseObject(payload, ChartDto.class);} catch (Exception e) {log.error("将payload转为ChartDto对象失败");}if (dto == null) {JSONObject jsonObject = JSON.parseObject(payload);String path = jsonObject.getString("path");ReqObj req = new ReqObj();req.setGap(jsonObject.getIntValue("gap",0));req.setDeviceCode(jsonObject.getString("deviceCode"));req.setPointType(jsonObject.getString("pointType"));String s = sendDataByPath(path, req);sess.sendMessage(new TextMessage("{\"path\":\""+path+"\",\"data\":"+s+"}"));} else {if (reportDashboardService == null) {reportDashboardService = SpringUtil.getBean(ReportDashboardService.class);}synchronized (Thread.currentThread()) {Object data = CACHE.getIfPresent(cacheKey);if (data == null) {data = reportDashboardService.getChartData(dto);if (data != null) {CACHE.put(cacheKey,data);}}String s = JSON.toJSONString(R.success(data,"success",dto.getId()));sess.sendMessage(new TextMessage(s));}}}} catch (Exception e) {System.err.println("Failed to send message: " + e.getMessage());}}private String sendDataByPath(String path,ReqObj req) {return "{}";}private void broadcast(String message) {for (WebSocketSession sess : sessions) {try {if (sess.isOpen()) {String data = "原始数据:"+message+",翻转后的数据:"+new StringBuilder(message).reverse();sess.sendMessage(new TextMessage(data));}} catch (Exception e) {System.err.println("Failed to send message: " + e.getMessage());}}}
}

3、拦截器握手时进行校验token

@Getter
@Slf4j
@Component
public class WebSocketSecurityTokenInterceptor implements HandshakeInterceptor {private TokenAcquireHandler tokenAcquireHandler;private TokenAnalysisHandler tokenAnalysisHandler;{tokenAcquireHandler = SpringUtil.getOrDefault( TokenAcquireHandler.class, new DefaultTokenAcquireHandler() );tokenAnalysisHandler = SpringUtil.getOrDefault( TokenAnalysisHandler.class, new DefaultTokenAnalysisHandler() );}@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {//  放开的路径直接放行if ( FilterContextHandler.getContext().isExclude() ) {//  如果已经手动setContext 此处不再赋值Emptyif ( SecurityContextHandler.getContext() == null ) {SecurityContextHandler.setContext( SecurityContext.EMPTY );}return true;}String token = getToken(request);if ( !StringUtils.hasText( token ) ) {throw new TokenNotFoundException( "token not found" );}UserDetails userDetails = tokenAnalysisHandler.analysisToken( token );checkUserDetails( token, userDetails );SecurityContextHandler.setContext( new SecurityContext( token, userDetails ) );return true;}/*** 校验用户信息*/private void checkUserDetails( String token, UserDetails userDetails ) {//  解析的UserDetails不能为空if ( userDetails == null ) {throw new TokenAnalysisException( "token analysis userDetails cannot be empty" );}//  判断用户是否启用if ( !userDetails.isEnabled() ) {throw new TokenAnalysisException();}//  判断用户是否过期if ( userDetails.isAccountNonExpired() ) {throw new UserDetailsExpiredException();}//  判断用户是否锁定if ( userDetails.isAccountNonLocked() ) {throw new UserLockException();}//  判断Token是否过期if ( userDetails.isCredentialsNonExpired( token ) ) {throw new TokenExpiredException();}}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {// 握手完成后进行一些初始化工作//log.info("握手完成......");}private String getToken( ServerHttpRequest req ) {List< String > headerList = req.getHeaders().get( HttpHeaders.AUTHORIZATION );String token = CollectionUtils.isEmpty( headerList ) ? "" : headerList.get( 0 );if ( StrUtil.isNotBlank( token ) ) {
//            req.setAttribute( HttpHeaders.AUTHORIZATION, token );return token;}List< String > cookies = req.getHeaders().get( HttpHeaders.COOKIE );for (String cookieStr : Optional.ofNullable(cookies).orElse(Collections.emptyList())) {HttpCookie cookie = parseAuthCookie(cookieStr);if ( cookie != null ){return cookie.getValue();}}return null;}private HttpCookie parseAuthCookie(String cookieStr) {if (!StringUtils.hasText(cookieStr)){return null;}List<HttpCookie> cookieList = Arrays.stream(cookieStr.split(";")).map(this::parseCookie).filter(Objects::nonNull).collect(Collectors.toList());for (HttpCookie cookie : cookieList) {if ( HttpHeaders.AUTHORIZATION.equals( cookie.getName() ) ) {return cookie;}}return null;}private HttpCookie parseCookie(String cookieStr) {try {List<HttpCookie> cookies = HttpCookie.parse(cookieStr);return CollectionUtils.isEmpty(cookies) ? null : cookies.get(0);}catch (Exception e){return null;}}
}

相关文章:

Springboot中添加原生websocket支持

1、添加配置 Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器&#xff0c;并允许所有来源的连接&#xff08;在生…...

财务主题数据分析-企业盈利能力分析

企业盈利能力数据主要体现在财务三张表中的利润表里面&#xff0c;盈利能力需要重点需要关注的指标有&#xff1a;毛利率、净利率、净利润增长率、营业成本增长率等&#xff1b; 接下来我们分析一下某上市公司披露的财务数据&#xff0c;看看该企业盈利能力如何&#xff1a; …...

你需要了解的远程登录协议——Telnet

你需要了解的远程登录协议——Telnet 一. 什么是Telnet&#xff1f;二. Telnet的优缺点三. Telnet vs SSH&#xff1a;哪一个更适合&#xff1f;四. Telnet的应用场景 前言 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神…...

Git -> Git配置密钥对,并查看公钥

Git密钥对的核心作用 私钥 (id_rsa) 你的数字身份证&#xff1a;存放在本机 ~/.ssh 目录下必须严格保密&#xff08;类似银行卡密码&#xff09;&#xff0c;不可泄露或共享用于 解密 来自服务器的加密信息 公钥 (id_rsa.pub) 可公开的验证锁&#xff1a;需要上传到 Git 服…...

web逆向企鹅音乐,下载歌手歌单音乐

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 下载资源链接&#xff1a;https://download.csdn.net/download/randy521520/90374039 一、找出需要加密的参数 1.js运行 atob…...

stm32 lwip tcp服务端频繁接收连接失效问题解决(tcp_recved)

一、问题描述 最近用stmf429单片机作为TCP服务端遇到一个问题&#xff0c;就是客户端特别频繁的发送消息&#xff0c;过一段时间以后&#xff0c;客户端的请求不再被客户端接收到&#xff0c;而且服务器端监控的掉线回调函数也不会被调用&#xff0c;好像这个连接就凭空的消失…...

Python Pandas(7):Pandas 数据清洗

数据清洗是对一些没有用的数据进行处理的过程。很多数据集存在数据缺失、数据格式错误、错误数据或重复数据的情况&#xff0c;如果要使数据分析更加准确&#xff0c;就需要对这些没有用的数据进行处理。数据清洗与预处理的常见步骤&#xff1a; 缺失值处理&#xff1a;识别并…...

【重构谷粒商城】06:Maven快速入门教程

重构谷粒商城06——Maven快速入门教程 前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到…...

Elasticsearch:同义词在 RAG 中重要吗?

作者&#xff1a;来自 Elastic Jeffrey Rengifo 及 Toms Mura 探索 RAG 应用程序中 Elasticsearch 同义词的功能。 同义词允许我们使用具有相同含义的不同词语在文档中搜索&#xff0c;以确保用户无论使用什么确切的词语都能找到他们所寻找的内容。你可能会认为&#xff0c;由于…...

React 低代码项目:组件设计

React 低代码项目&#xff1a;组件设计 Date: February 6, 2025 React表单组件 **目标&#xff1a;**使用 Ant Design 表单组件&#xff0c;开发登录、注册、搜索功能 内容&#xff1a; 使用 React 表单组件、受控组件使用 Ant Design 表单组件使用 表单组件的校验和错误提…...

从0到1的回溯算法学习

回溯算法 前言这个算法能帮我们做啥算法模版力扣例题&#xff08; 以下所有题目代码都经过力扣认证 &#xff09;形式一 元素无重不可复选46.全排列思路详解代码 77.组合思路详解代码 78.子集思路详解代码 形式二 元素可重不可复选思考&#xff08;deepseek&#xff09;核心思想…...

24、深度学习-自学之路-卷积神经网络

一、你怎么理解卷积神经网络呢&#xff0c;我的理解是当你看一个东西的时候&#xff0c;你的眼睛距离图片越近&#xff0c;你看到的东西就越清晰&#xff0c;但是如果你看到的图片只是整个物体的一小部分&#xff0c;那么你将不知道你看到的物品是什么&#xff0c;因为关注整体…...

AVL树:高效平衡的二叉搜索树

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; 引言&#x1f914; 在数据结构的奇妙世界里&#xff0c;二叉搜索树&#xff08;BST&#xff09;原本是查找数据的好帮手。想象一下…...

RHCA练习5:配置mysql8.0使用PXC实现高可用

准备4台CentOS7的虚拟机&#xff08;CentOS7-1、CentOS7-2、CentOS7-3、CentOS7-4&#xff09; 备份原yum源的配置&#xff1a; mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 更换阿里云镜像YUM源&#xff1a; curl -o /etc/yum.repos.…...

若输入超过 5 位数个时,推荐使用 scanf 输入数据。

【知识点】 在 C 中&#xff0c;当需要处理超过 5 位数个输入时&#xff0c;推荐使用 scanf 而不是 cin 输入数据。 这是因为 scanf 通常比 cin 更快。 另外&#xff0c;若整数超过 10 位&#xff0c;选择用 long long 型&#xff0c;而不是 int 型。 【参考文献】 https://b…...

Java 大视界 -- 边缘计算与 Java 大数据协同发展的前景与挑战(85)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

Android 原生层SurfaceView截屏

背景&#xff1a;flutter嵌入原生view时&#xff0c;原生view使用的surfaveview&#xff0c;导致下面两种方法无法正常使用。 导致flutter无法通过id找到RenderRepaintBoundary的toImage来抓取widget&#xff0c;原生层无法通过view去获取Bitmap 方案&#xff1a;使用PixelCopy…...

机器学习 - 理论和定理

在机器学习中&#xff0c;有一些非常有名的理论或定理&#xff0c;对理解机器学习的内在特性非常有帮助。本文列出机器学习中常用的理论和定理&#xff0c;并举出对应的举例子加以深化理解&#xff0c;有些理论比较抽象&#xff0c;我们可以先记录下来&#xff0c;慢慢啃&#…...

2025.2.11——一、[极客大挑战 2019]PHP wakeup绕过|备份文件|代码审计

题目来源&#xff1a;BUUCTF [极客大挑战 2019]PHP 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;目录扫描、爆破 step 2&#xff1a;代码审计 1.index.php 2.class.php 3.flag.php step 3&#xff1a;绕过__wakeup重置 ​编辑 三、小结…...

读取本地excel删除第一行,并生成List数组

在 pom.xml 里添加如下依赖&#xff1a; <dependencies><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.3</version></dependency><dependency><groupId>org.ap…...

Vivado生成edif网表及其使用

介绍如何在Vivado中将模块设为顶层&#xff0c;并生成相应的网表文件&#xff08;Verilog文件和edif文件&#xff09;&#xff0c;该过程适用于需要将一个模块作为顶层设计进行综合&#xff0c;并生成用于其他工程中的网表文件的情况。 例如要将fpga_top模块制作成网表给其它工…...

JAVA生产环境(IDEA)排查死锁

使用 IntelliJ IDEA 排查死锁 IntelliJ IDEA 提供了强大的工具来帮助开发者排查死锁问题。以下是具体的排查步骤&#xff1a; 1. 编写并运行代码 首先&#xff0c;我们编写一个可能导致死锁的示例代码&#xff1a; public class DeadlockExample {private static final Obj…...

Mac 下使用多版本 Node

一、导读 使用 n 实现 Mac 下 Nodejs 的多版本切换&#xff0c;需要先安装一个版本的 Node.js&#xff0c;然后使用 npm 安装 n&#xff0c;再通过 n 管理 node 的多版本切换。 二、使用 npm 全局安装 n sudo npm install -g n 三、根据需求安装指定版本的 node sudo -E n…...

AI学习记录 - 最简单的专家模型 MOE

代码 import torch import torch.nn as nn import torch.nn.functional as F from typing import Tupleclass BasicExpert(nn.Module):# 一个 Expert 可以是一个最简单的&#xff0c; linear 层即可# 也可以是 MLP 层# 也可以是 更复杂的 MLP 层&#xff08;active function 设…...

【2025深度学习系列专栏大纲:深入探索与实践深度学习】

第一部分:深度学习基础篇 第1章:深度学习概览 1.1 深度学习的历史背景与发展轨迹 1.2 深度学习与机器学习、传统人工智能的区别与联系 1.3 深度学习的核心组件与概念解析 神经网络基础 激活函数的作用与类型 损失函数与优化算法的选择 1.4 深度学习框架简介与选择建议 第2…...

DDD聚合在 ASP.NET Core中的实现

目录 工作单元&#xff08;UnitOfWork&#xff09;的实现 聚合与聚合根的实现 实现 聚合与DbContext的关系 区分聚合根实体和其他实体 跨表查询 实现实体不要面向数据库建模 工作单元&#xff08;UnitOfWork&#xff09;的实现 EFCore的DbContext&#xff1a;跟踪对象状…...

数据治理双证通关经验分享 | CDGA/CDGP备考全指南

历经1个月多的系统准备&#xff0c;本人于2024年顺利通过DAMA China的CDGA&#xff08;数据治理工程师&#xff09;和CDGP&#xff08;数据治理专家&#xff09;双认证。现将备考经验与资源体系化整理&#xff0c;助力从业者高效通关。 &#x1f31f; 认证价值与政策背景 根据…...

Aitken 逐次线性插值

Aitken 逐次线性插值 用 Lagrange 插值多项式 L n ( x ) L_n(x) Ln​(x)计算函数近似值时&#xff0c;如需增加插值节点&#xff0c;那么原来算出的数据均不能利用&#xff0c;必须重新计算。为克服这个缺点&#xff0c;可用逐次线性插值方法求得高次插值。 令 I i 1 , i 2…...

亚信安全正式接入DeepSeek

亚信安全致力于“数据驱动、AI原生”战略&#xff0c;早在2024年5月&#xff0c;推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体&#xff0c;为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来&#xff0c;亚信安全人工智能实验室利…...

unet学习(初学者 自用)

代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 以上面链接中的代码为例&#xff0c;逐行解释。 训练 unet的train.py如下&#xff1a; import torch.nn as nn import torch import gdal import numpy as np from torch…...