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端 第一步,引入依赖 第二步,创建WebSocket配置类 第三步,创建WebSocket服务 第四步,创建Controller进行发送测试 Vue端 第一步,创建连接工具类 第二步,建立连接 编辑 第三步&a…...

python---python介绍
python介绍 1.1介绍 1.1.1为什么学习 1.1.2什么是python 优雅简单易学1.1.3在线2进制转换 在线二进制转文本工具 - 转换 1.1.4python的安装和配置 1.需要配置对应的环境变量。可以设置多个。 默认全选 设置安装的路径 最后安装完成即可。 验证:python 如何退出 1.1.…...
第十四届蓝桥杯大赛——真题训练第10天
目录 第一题:扫雷 题目描述 输入描述 输出描述 输入输出样例 运行限制 题目代码 第 2 题:完全平方数 问题描述 输入格式 输出格式 样例输入 1 样例输出 1 样例输入 2 样例输出 2 题目分析 题目代码 第三题:求阶乘 问题描述…...

3.29~3.30学习总结
刷题情况: 蓝桥杯刷题: Java学习情况: 抽象类:如果将子类中抽取的共性行为(方法),子类的执行不一样的时候 (通俗一点来说,就是无法找到一个万能的方法体供子类使用,但这…...

结构体详解 ——— C语言
目录 1.结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段(位段的填充&可移植性) 位段的内存分配 1.结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对…...
Java SE 基础(4) Java的语言特点
语言特点 Java是一门面向对象编程语言,不仅吸收了C语言的各种优点,还摒弃了C里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论&a…...
都炸店了,拼多多还在坚持什么
子超这两天听说了拼多多被“炸店”事件,第一反应是震惊:这都什么年代了,还有这种不择手段的暴力行为?所谓的炸店,就是一些人员被煽动和组织起来,有预谋地对店铺发起打砸行动,这和线下去打砸商铺…...

vue尚品汇商城项目-day01【6.Footer组件的显示与隐藏】
文章目录6.Footer组件的显示与隐藏6.1我们可以根据组件身上的$route获取当前路由的信息,通过路由路径判断Footer显示与隐藏6.2配置路由的时候,可以给路由添加元信息[meta],路由需要配置对象,它的key不能乱接、瞎写、胡写ÿ…...
命令行上的数据科学第二版 一、简介
原文:https://datascienceatthecommandline.com/2e/chapter-1-introduction.html 贡献者:Ting-xin 这本书是关于如何利用命令行做数据科学的。我的目标是通过教你使用命令行的力量,让自己成为一名更高效和多产的数据科学家。 在标题中同时使…...
utf-8转换到utf-16的转换过程你懂吗?
人生自是有情痴,此恨不关风与月。——唐代元稹《离思》 从UTF-8编码的文件中读取文本并将其存储到Java的String对象中,涉及到从字节序列到Unicode码点,再到UTF-16编码的转换。以下是详细的步骤: 从文件读取字节序列:首…...
C++编程大师之路:从入门到精通--持续更新中~
文章目录前言主要内容C基础入门初识C第一个C程序注释变量常量关键字标识符命名规则数据类型整型sizeof关键字实型(浮点型)字符型转义字符字符串型布尔类型 bool数据的输入运算符算术运算符赋值运算符比较运算符逻辑运算符程序流程结构选择结构if语句三目…...

面试阿里软件测试岗,收到offer后我却毫不犹豫拒绝了....
我大学学的是计算机专业,毕业的时候,对于找工作比较迷茫,也不知道当时怎么想的,一头就扎进了一家外包公司,一干就是2年。我想说的是,但凡有点机会,千万别去外包! 在深思熟虑过后&am…...
【c语言多线程编程】关于pthread_create()和pthread_join()的多线程详解
关于pthread_create()和pthread_join()的多线程详解 一、首先说一下pthread_create() 函数的用法: int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);各参数的含义: 1、pthread_t *thre…...

抖音seo矩阵系统源码搭建技术+二开开源代码定制部署
抖音已经成为了当今最为流行的短视频平台之一,拥有着庞大的用户群体和海量的视频资源。对于一些商家或者运营者来说,如何从这些视频资源中挖掘出有效的信息,进而提升自己的品牌、产品或者内容的曝光度,就成为了一个非常重要的问题…...

【周赛刷题】平衡树+图中最短环
2612. 最少翻转操作数(平衡树) 题目的难度有一部分在于数学推导。对于某个点 iii 进行反转是有一个范围的,这个范围需要考虑到边界的情况。可以的得到的一个结论是。对于窗口反转,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…...
自适应平移混音方法
一、简介: 自适应平移混音方法是一种常见的音频混音技术,它利用自适应滤波器对不同音频信号进行平移和加权,从而实现混音。 二、该方法的基本步骤如下: 采集和存储需要混音的音频信号。 对其中一个音频信号进行预处理,…...

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

MySQL数据库范式
文章目录MySQL数据库范式1、范式的优缺点2、第一范式3、第二范式4、第三范式5、BC范式6、第四范式MySQL数据库范式 1、范式的优缺点 应用数据库范式的好处: 减少数据冗余(这是最主要的好处,其他好处都是由此而附带的)消除异常&…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...