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、范式的优缺点 应用数据库范式的好处: 减少数据冗余(这是最主要的好处,其他好处都是由此而附带的)消除异常&…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...

