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

Spring Boot+Vue实现Socket通知推送

目录

Spring Boot端

第一步,引入依赖

第二步,创建WebSocket配置类

第三步,创建WebSocket服务

第四步,创建Controller进行发送测试

Vue端

第一步,创建连接工具类

第二步,建立连接

​编辑

第三步,监听服务器发送过来的消息

第四步,关闭连接 


Spring Boot端

第一步,引入依赖

首先我们需要引入WebSocket所需的依赖,以及处理输出格式的依赖

<!--格式转换-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.73</version>
</dependency>
<!--WebSocket依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

第二步,创建WebSocket配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author: tjp* @create: 2023-04-03 09:58* @Description: WebSocket配置*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

第三步,创建WebSocket服务

这一步我们通过userId作为标识符,区分系统中对应的用户,后续也可基于此,进行其他的操作步骤。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.excel.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;/*** @author: tjp* @create: 2023-04-03 13:55* @Description: WebSocket服务*/@ServerEndpoint("/websocket/{userId}")
@Slf4j
@Component
public class WebSocketServer {/*** 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/private static int onlineCount = 0;/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 接收userId*/private String userId = "";/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {this.session = session;this.userId = userId;if (webSocketMap.containsKey(userId)) {webSocketMap.remove(userId);//加入set中} else {webSocketMap.put(userId, this);//加入set中addOnlineCount();//在线数加1}log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());try {HashMap<Object, Object> map = new HashMap<>();map.put("key", "连接成功");sendMessage(JSON.toJSONString(map));} catch (IOException e) {log.error("用户:" + userId + ",网络异常!!!!!!");}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {if (webSocketMap.containsKey(userId)) {webSocketMap.remove(userId);//从set中删除subOnlineCount();}log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session) {log.info("用户消息:" + userId + ",报文:" + message);//可以群发消息//消息保存到数据库、redisif (StringUtils.isNotBlank(message)) {try {//解析发送的报文JSONObject jsonObject = JSONObject.parseObject(message);//追加发送人(防止串改)jsonObject.put("fromUserId", this.userId);String fromUserId = jsonObject.getString("fromUserId");//传送给对应toUserId用户的websocketif (StringUtils.isNotBlank(fromUserId) && webSocketMap.containsKey(fromUserId)) {webSocketMap.get(fromUserId).sendMessage(jsonObject.toJSONString());//自定义-业务处理//                    DeviceLocalThread.paramData.put(jsonObject.getString("group"),jsonObject.toJSONString());} else {log.error("请求的userId:" + fromUserId + "不在该服务器上");//否则不在这个服务器上,发送到mysql或者redis}} catch (Exception e) {e.printStackTrace();}}}/*** 发生错误时候** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());error.printStackTrace();}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException {//加入线程锁synchronized (session) {try {//同步发送信息this.session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("服务器推送失败:" + e.getMessage());}}}/*** 发送自定义消息* *//*** 发送自定义消息** @param message  发送的信息* @param toUserId 如果为null默认发送所有* @throws IOException*/public static void sendInfo(String message, String toUserId) throws IOException {//如果userId为空,向所有群体发送if (StringUtils.isEmpty(toUserId)) {//向所有用户发送信息Iterator<String> itera = webSocketMap.keySet().iterator();while (itera.hasNext()) {String keys = itera.next();WebSocketServer item = webSocketMap.get(keys);item.sendMessage(message);}}//如果不为空,则发送指定用户信息else if (webSocketMap.containsKey(toUserId)) {WebSocketServer item = webSocketMap.get(toUserId);item.sendMessage(message);} else {log.error("请求的userId:" + toUserId + "不在该服务器上");}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}public static synchronized ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() {return WebSocketServer.webSocketMap;}}

第四步,创建Controller进行发送测试

获取当前在线人数

import com.......WebSocketServer;@ApiOperation(value = "获取当前在线人数")
@GetMapping("/getOnlineCount")
public Integer getOnlineCount() {return WebSocketServer.getOnlineCount();
}

通过接口,向前端用户推送消息

import com.......WebSocketServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.IOException;/*** @author: tjp* @create: 2023-04-03 13:57* @Description: 测试*/
@RestController
@RequestMapping("/news")
public class NewsController {@GetMapping("/send")public String send() {try {WebSocketServer.sendInfo("这是websocket发送过来的消息!", "需要推送的用户的编号");} catch (IOException e) {throw new RuntimeException(e);}return "发送消息成功";}}

Vue端

第一步,创建连接工具类

创建工具类websocket.js,这里的userId就是用来作为标识符的userId

/*** @author: tjp* @create: 2023-04-03 11:22* @Description: Socket客户端*/
export class WebSocketClient {constructor(userId) {this.userId = userId;this.websocket = null;this.timeout = 10000; // 心跳超时时间,单位msthis.timeoutObj = null; // 心跳定时器this.serverTimeoutObj = null; // 服务器超时定时器this.lockReconnect = false; // 避免重复连接this.timeoutnum = null; // 重连延迟定时器}// 初始化WebSocket连接initWebSocket() {let wsUrl = `ws://127.0.0.1:8080/websocket/${this.userId}`;this.websocket = new WebSocket(wsUrl);this.websocket.onopen = this.websocketonopen.bind(this);this.websocket.onerror = this.websocketonerror.bind(this);this.websocket.onmessage = this.setOnmessageMessage.bind(this);this.websocket.onclose = this.websocketclose.bind(this);// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = this.websocketclose.bind(this);}// 启动心跳start() {console.log('start');// 清除延时器this.timeoutObj && clearTimeout(this.timeoutObj);this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);/*// 向服务器发送心跳消息let actions = { "test": "12345" };this.websocket && this.websocket.readyState == 1 && this.websocket.send(JSON.stringify(actions));// 启动心跳定时器this.timeoutObj = setTimeout(() => {this.start();// 定义一个延时器等待服务器响应,若超时,则关闭连接,重新请求server建立socket连接this.serverTimeoutObj = setTimeout(() => {this.websocket.close();}, this.timeout)}, this.timeout)*/}// 重置心跳reset() {// 清除时间clearTimeout(this.timeoutObj);clearTimeout(this.serverTimeoutObj);// 重启心跳this.start();}// 重新连接reconnect() {if (this.lockReconnect) return;this.lockReconnect = true;// 没连接上会一直重连,设置延迟避免请求过多this.timeoutnum && clearTimeout(this.timeoutnum);this.timeoutnum = setTimeout(() => {this.initWebSocket();this.lockReconnect = false;}, 5000)}// 处理收到的消息async setOnmessageMessage(event) {console.log(event.data, '获得消息');// 重置心跳// this.reset();// 自定义全局监听事件window.dispatchEvent(new CustomEvent('onmessageWS', {detail: {data: event.data}}))// //发现消息进入    开始处理前端触发逻辑// if (event.data === 'success' || event.data === 'heartBath') return}// WebSocket连接成功回调websocketonopen() {// 开启心跳this.start();console.log("WebSocket连接成功!!!" + new Date() + "----" + this.websocket.readyState);clearInterval(this.otimer);//停止}// WebSocket连接错误回调websocketonerror(e) {console.log("WebSocket连接发生错误" + e);}// WebSocket连接关闭回调websocketclose(e) {this.websocket.close();clearTimeout(this.timeoutObj);clearTimeout(this.serverTimeoutObj);console.log("websocketcloe关闭连接")}// 关闭WebSocket连接closeWebSocket() {this.websocket.close();console.log("closeWebSocket关闭连接")}// 监听窗口关闭事件onbeforeunload() {this.closeWebSocket();}
}

第二步,建立连接

在任意你想建立连接的页面中建立Socket连接

比如,在用户点击登录按钮之后

在这里可以使用原型,创建连接对象,并启动连接

<script>
import Vue from "vue";
import {WebSocketClient} from "@/utils/websocket";
......
......
methods:{handleLogin() {this.$refs.loginForm.validate(valid => {if (valid) {this.loading = truethis.$store.dispatch('user/login', this.loginForm).then(() => {this.$router.push({path: this.redirect || '/'})this.loading = false/*-----------在此处放入原型中------------*/Vue.prototype.$WebSocketClientInstance = new WebSocketClient('t');Vue.prototype.$WebSocketClientInstance.initWebSocket()/*-----------------end------------*/}).catch(() => {this.loading = false})} else {this.$message({message: '请填写正确格式的用户名或密码', type: 'error'})return false}})}    
}.....
.....
</script>

第三步,监听服务器发送过来的消息

在你想监听的页面,使用监听器进行监听

<script>
....
....
mounted() {// 添加socket通知监听window.addEventListener('onmessageWS', this.getSocketData)
},
methods: {// 收到消息处理getSocketData(res) {console.log(res.detail)console.log("llll")},
}....
....
</script>

这个时候,你就可以通过后端的接口进行发送了

 搞个测试

 

第四步,关闭连接 

搞个按钮

<template><div><button @click="closeConnect">关闭连接</button></div>
</template><script>
import {WebSocketClient} from "@/utils/websocket";
import Vue from "vue";export default {methods: {closeConnect() {console.dir(Vue.prototype)Vue.prototype.$WebSocketClientInstance.closeWebSocket();},}
}
</script>

 

相关文章:

Spring Boot+Vue实现Socket通知推送

目录 Spring Boot端 第一步&#xff0c;引入依赖 第二步&#xff0c;创建WebSocket配置类 第三步&#xff0c;创建WebSocket服务 第四步&#xff0c;创建Controller进行发送测试 Vue端 第一步&#xff0c;创建连接工具类 第二步&#xff0c;建立连接 ​编辑 第三步&a…...

python---python介绍

python介绍 1.1介绍 1.1.1为什么学习 1.1.2什么是python 优雅简单易学1.1.3在线2进制转换 在线二进制转文本工具 - 转换 1.1.4python的安装和配置 1.需要配置对应的环境变量。可以设置多个。 默认全选 设置安装的路径 最后安装完成即可。 验证&#xff1a;python 如何退出 1.1.…...

第十四届蓝桥杯大赛——真题训练第10天

目录 第一题&#xff1a;扫雷 题目描述 输入描述 输出描述 输入输出样例 运行限制 题目代码 第 2 题&#xff1a;完全平方数 问题描述 输入格式 输出格式 样例输入 1 样例输出 1 样例输入 2 样例输出 2 题目分析 题目代码 第三题&#xff1a;求阶乘 问题描述…...

3.29~3.30学习总结

刷题情况&#xff1a; 蓝桥杯刷题&#xff1a; Java学习情况: 抽象类&#xff1a;如果将子类中抽取的共性行为&#xff08;方法&#xff09;&#xff0c;子类的执行不一样的时候 &#xff08;通俗一点来说&#xff0c;就是无法找到一个万能的方法体供子类使用&#xff0c;但这…...

结构体详解 ——— C语言

目录 1.结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段&#xff08;位段的填充&可移植性&#xff09; 位段的内存分配 1.结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对…...

Java SE 基础(4) Java的语言特点

语言特点 Java是一门面向对象编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论&a…...

都炸店了,拼多多还在坚持什么

子超这两天听说了拼多多被“炸店”事件&#xff0c;第一反应是震惊&#xff1a;这都什么年代了&#xff0c;还有这种不择手段的暴力行为&#xff1f;所谓的炸店&#xff0c;就是一些人员被煽动和组织起来&#xff0c;有预谋地对店铺发起打砸行动&#xff0c;这和线下去打砸商铺…...

vue尚品汇商城项目-day01【6.Footer组件的显示与隐藏】

文章目录6.Footer组件的显示与隐藏6.1我们可以根据组件身上的$route获取当前路由的信息&#xff0c;通过路由路径判断Footer显示与隐藏6.2配置路由的时候&#xff0c;可以给路由添加元信息[meta]&#xff0c;路由需要配置对象&#xff0c;它的key不能乱接、瞎写、胡写&#xff…...

命令行上的数据科学第二版 一、简介

原文&#xff1a;https://datascienceatthecommandline.com/2e/chapter-1-introduction.html 贡献者&#xff1a;Ting-xin 这本书是关于如何利用命令行做数据科学的。我的目标是通过教你使用命令行的力量&#xff0c;让自己成为一名更高效和多产的数据科学家。 在标题中同时使…...

utf-8转换到utf-16的转换过程你懂吗?

人生自是有情痴&#xff0c;此恨不关风与月。——唐代元稹《离思》 从UTF-8编码的文件中读取文本并将其存储到Java的String对象中&#xff0c;涉及到从字节序列到Unicode码点&#xff0c;再到UTF-16编码的转换。以下是详细的步骤&#xff1a; 从文件读取字节序列&#xff1a;首…...

C++编程大师之路:从入门到精通--持续更新中~

文章目录前言主要内容C基础入门初识C第一个C程序注释变量常量关键字标识符命名规则数据类型整型sizeof关键字实型&#xff08;浮点型&#xff09;字符型转义字符字符串型布尔类型 bool数据的输入运算符算术运算符赋值运算符比较运算符逻辑运算符程序流程结构选择结构if语句三目…...

面试阿里软件测试岗,收到offer后我却毫不犹豫拒绝了....

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是2年。我想说的是&#xff0c;但凡有点机会&#xff0c;千万别去外包&#xff01; 在深思熟虑过后&am…...

【c语言多线程编程】关于pthread_create()和pthread_join()的多线程详解

关于pthread_create()和pthread_join()的多线程详解 一、首先说一下pthread_create() 函数的用法&#xff1a; int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);各参数的含义&#xff1a; 1、pthread_t *thre…...

抖音seo矩阵系统源码搭建技术+二开开源代码定制部署

抖音已经成为了当今最为流行的短视频平台之一&#xff0c;拥有着庞大的用户群体和海量的视频资源。对于一些商家或者运营者来说&#xff0c;如何从这些视频资源中挖掘出有效的信息&#xff0c;进而提升自己的品牌、产品或者内容的曝光度&#xff0c;就成为了一个非常重要的问题…...

【周赛刷题】平衡树+图中最短环

2612. 最少翻转操作数&#xff08;平衡树&#xff09; 题目的难度有一部分在于数学推导。对于某个点 iii 进行反转是有一个范围的&#xff0c;这个范围需要考虑到边界的情况。可以的得到的一个结论是。对于窗口反转&#xff0c;KaTeX parse error: Expected group after ^ at p…...

C++笔记——第十篇 继承 的解析,详细易懂哦

目录 一、继承的概念及定义 1.继承的概念 2. 继承定义 2.1定义格式 2.2继承关系和访问限定符 2.3继承基类成员访问方式的变化 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 五、继承与友元 六、继承与静态成员 七、复杂的菱形继承…...

SQL Server中的全文搜索

SQL Server中的全文搜索一、概述二、全文搜索查询三、将全文搜索查询与 LIKE 谓词进行比较四、全文搜索体系结构4.1、SQL Server 进程4.2、过滤器守护程序主机进程五、全文搜索处理5.1、全文索引过程5.2、全文查询流程六、全文索引体系结构6.1、全文索引结构6.2、全文索引片段6…...

自适应平移混音方法

一、简介&#xff1a; 自适应平移混音方法是一种常见的音频混音技术&#xff0c;它利用自适应滤波器对不同音频信号进行平移和加权&#xff0c;从而实现混音。 二、该方法的基本步骤如下&#xff1a; 采集和存储需要混音的音频信号。 对其中一个音频信号进行预处理&#xff0c…...

炼钢厂VR职业技能实训软件,提高员工学习效率和掌握技能速度

炼钢作业是一个高危、高压、高温的行业&#xff0c;在实际操作中需要严格遵守安全规范和操作规程&#xff0c;一旦出现差错可能造成巨大的经济损失和人员伤亡。 利用广州华锐互动开发的炼钢厂VR职业技能实训软件&#xff0c;可以有效帮助员工更好地理解和掌握炼钢作业中的相关…...

MySQL数据库范式

文章目录MySQL数据库范式1、范式的优缺点2、第一范式3、第二范式4、第三范式5、BC范式6、第四范式MySQL数据库范式 1、范式的优缺点 应用数据库范式的好处&#xff1a; 减少数据冗余&#xff08;这是最主要的好处&#xff0c;其他好处都是由此而附带的&#xff09;消除异常&…...

ESP32-S3驱动JW01二氧化碳传感器:从供电陷阱到数据解析的实战指南

1. 硬件连接&#xff1a;电压匹配是生死线 第一次拿到JW01传感器时&#xff0c;我像往常一样顺手接上了ESP32-S3开发板的5V引脚——毕竟大多数传感器模块都标着"5V供电"的字样。结果串口监视器里一片死寂&#xff0c;连乱码都没有。翻出万用表测量才发现&#xff0c;…...

告别插件切换!一款满足你所有挖洞需求的浏览器插件助力高效挖洞

0x01 工具介绍 由于目前网上流通的插件功能都各有千秋&#xff0c;每个插件都有他自己的亮点&#xff0c;每次使用都得按场景去选择插件&#xff0c;为了能够有一款属于自己的完美插件&#xff0c;不用来回倒腾切换&#xff0c;由此GodEyes 诞生了。 它是一款可以帮助安全研究…...

告别Node版本混乱!用NVM管理多项目环境(Mac保姆级指南+Zsh配置)

告别Node版本混乱&#xff01;用NVM管理多项目环境&#xff08;Mac保姆级指南Zsh配置&#xff09; 在开发过程中&#xff0c;你是否遇到过这样的场景&#xff1a;接手一个老项目时&#xff0c;发现它依赖Node.js 12.x版本&#xff0c;而新项目却要求使用18.x甚至更高版本&#…...

项目管理和技术管理的区别

在单位从事管理岗快2年了&#xff0c;负责单位内的研发项目管理和技术管理工作。感觉这是两个不同的管理赛道。其中项目管理侧重进度、资源、风险、责任人、排期等要素推进和汇报。技术管理则侧重研发环节的技术深度、技术方向、技术领先性、技术栈&#xff0c;以及项目产出的质…...

如何快速掌握ViGEmBus虚拟手柄驱动:新手5分钟完全指南

如何快速掌握ViGEmBus虚拟手柄驱动&#xff1a;新手5分钟完全指南 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 你是否遇到过这样的困扰&#xff1a;心爱的…...

腾讯开源翻译大模型HY-MT1.5-7B镜像使用教程:新手快速入门

腾讯开源翻译大模型HY-MT1.5-7B镜像使用教程&#xff1a;新手快速入门 你是否曾为寻找一个既强大又好用的翻译工具而烦恼&#xff1f;无论是阅读外文资料、处理多语言客服&#xff0c;还是开发一个需要实时翻译的应用&#xff0c;找到一个靠谱的翻译引擎总是关键一步。今天&am…...

时间序列异常检测新思路:拆解VAE-LSTM论文,看混合模型如何1+1>2

时间序列异常检测新思路&#xff1a;拆解VAE-LSTM混合模型的设计哲学与实战优势 当工业传感器以每秒数百次频率生成数据时&#xff0c;人工巡检异常早已不切实际。传统阈值检测在面对设备渐进性故障时&#xff0c;误报率可能高达60%。这引出一个核心命题&#xff1a;如何让算法…...

Graphormer在放射性药物中的应用:螯合剂分子稳定常数与配位能力预测

Graphormer在放射性药物中的应用&#xff1a;螯合剂分子稳定常数与配位能力预测 1. 项目概述 Graphormer是一种基于纯Transformer架构的图神经网络模型&#xff0c;专门为分子图(原子-键结构)的全局结构建模与属性预测而设计。该模型在OGB、PCQM4M等分子基准测试中表现优异&a…...

像素语言传送门效果实测:Hunyuan-MT-7B对中文网络新词(如‘绝绝子‘)的跨语种意译能力

像素语言传送门效果实测&#xff1a;Hunyuan-MT-7B对中文网络新词&#xff08;如绝绝子&#xff09;的跨语种意译能力 1. 测试背景与工具介绍 像素语言跨维传送门是基于腾讯Hunyuan-MT-7B翻译引擎构建的创新翻译工具。与传统翻译软件不同&#xff0c;它将语言转换过程设计成一…...

AI结对编程:与快马AI对话式迭代,智能优化你的系统ER图设计

AI结对编程&#xff1a;与快马AI对话式迭代&#xff0c;智能优化你的系统ER图设计 最近在做一个员工管理系统的数据库设计&#xff0c;发现ER图设计是个需要反复推敲的过程。传统方式下&#xff0c;每次修改都要手动调整图形&#xff0c;效率很低。直到尝试了InsCode(快马)平台…...