springBoot与Vue共同搭建webSocket环境

news2025/1/12 12:10:33

欢迎使用Markdown编辑器

你好! 这片文章将教会你从后端springCloud到前端VueEleementAdmin如何搭建Websocket

前端

1. 创建websocket的配置文件在utils文件夹下websocket.js

// 暴露自定义websocket对象
export const socket = {
  // 后台请求路径
  url: '',
  websocketCount: -1,
  // websocket对象
  websocket: null,
  // websocket状态
  websocketState: false,
  // 重新连接次数
  reconnectNum: 0,
  // 重连锁状态,保证重连按顺序执行
  lockReconnect: false,
  // 定时器信息
  timeout: null,
  clientTimeout: null,
  serverTimeout: null,
  // 初始化方法,根据url创建websocket对象封装基本连接方法,并重置心跳检测
  initWebSocket(newUrl) {
    socket.url = newUrl
    socket.websocket = new WebSocket(socket.url)
    socket.websocket.onopen = socket.websocketOnOpen
    socket.websocket.onerror = socket.websocketOnError
    socket.websocket.onmessage = socket.webonmessage
    socket.websocket.onclose = socket.websocketOnClose
    this.resetHeartbeat()
  },
  reconnect() {
    // 判断连接状态
    console.log('判断连接状态')
    if (socket.lockReconnect) return
    socket.reconnectNum += 1
    // 重新连接三次还未成功调用连接关闭方法
    if (socket.reconnectNum === 3) {
      socket.reconnectNum = 0
      socket.websocket.onclose()
      return
    }
    // 等待本次重连完成后再进行下一次
    socket.lockReconnect = true
    // 5s后进行重新连接
    socket.timeout = setTimeout(() => {
      socket.initWebSocket(socket.url)
      socket.lockReconnect = false
    }, 5000)
  },
  // 重置心跳检测
  resetHeartbeat() {
    socket.heartbeat()
  },
  // 心跳检测
  heartbeat() {
    socket.clientTimeout = setTimeout(() => {
      if (socket.websocket) {
        // 向后台发送消息进行心跳检测
        socket.websocket.send(JSON.stringify({ type: 'heartbeat' }))
        socket.websocketState = false
        // 一分钟内服务器不响应则关闭连接
        socket.serverTimeout = setTimeout(() => {
          if (!socket.websocketState) {
            socket.websocket.onclose()
            console.log('一分钟内服务器不响应则关闭连接')
          } else {
            this.resetHeartbeat()
          }
        }, 60 * 1000)
      }
    }, 3 * 1000)
  },
  // 发送消息
  sendMsg(message) {
    socket.websocket.send(message)
  },
  websocketOnOpen(event) {
    // 连接开启后向后台发送消息进行一次心跳检测
    socket.sendMsg(JSON.stringify({ type: 'heartbeat' }))
  },
  // 初始化websocket对象
  // window.location.host获取ip和端口,
  // process.env.VUE_APP_WEBSOCKET_BASE_API获取请求前缀
  // 绑定接收消息方法
  webonmessage(event) {
    // 初始化界面时,主动向后台发送一次消息,获取数据
    this.websocketCount += 1
    if (this.websocketCount === 0) {
      const queryCondition = {
        type: 'message'
      }
      socket.sendMsg(JSON.stringify(queryCondition))
      console.log('初始化界面时,主动向后台发送一次消息,获取数据')
    }
    const info = JSON.parse(event.data)
    switch (info.type) {
      case 'heartbeat':
        socket.websocketState = true
        console.log(JSON.stringify(info))
        break
      case 'message':
        if (info.message === '成功') {
          this.$notify({
            title: '提示',
            message: '成功',
            type: 'success',
            duration: 0
          })
        } else {
          this.$notify({
            title: '提示',
            message: '上传失败:' + info.message,
            type: 'warning',
            duration: 0
          })
        }

        break
      case 'error':
        console.log('websocket:error')
        break
    }
  },
  websocketOnError(error) {
    console.log(error)
    console.log('websocket报错了' + error)
    socket.reconnect()
  },
  websocketOnClose() {
    console.log('websocke他关闭了')
    socket.websocket.close()
  }
}




2. 在main.js中引入配置文件,使websocket全局都能使用

import { socket } from './utils/websocket'
Vue.prototype.$socket = socket

3. 设置登陆时开启websocket连接,

this. s o c k e t . i n i t W e b S o c k e t ( ‘ w s : socket.initWebSocket( `ws: socket.initWebSocket(ws:{process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + this.loginForm.username
) 这句是开启websocket连接的。

// 登录方法
handleLogin() {
  this.$refs.loginForm.validate(valid => {
    if (valid) {
      this.loading = true
      this.$store.dispatch('user/login', this.loginForm)
        .then(() => {
          this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
          this.$store.dispatch('user/addInfomation', this.infoMation)
          this.$socket.initWebSocket(
            `ws:${process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + this.loginForm.username
          )
          this.loading = false
        })
        .catch(() => {
          this.loading = false
        })
    } else {
      console.log('error submit!!')
      return false
    }
  })
},
4. 设置退出时关闭websocket连接

this.$socket.websocketOnClose()这句是关闭websocket连接的

 logout() {
      await this.$store.dispatch('user/logout')
      this.$socket.websocketOnClose()
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }

重点和需要配置的地方都在websocket.js里比如接收消息方法webonmessage

后端

1.引依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        
2.写配置
package com.szc.material.analysisService.confg.WebSocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig   {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
3.创建Websocket服务类(依据自己的业务去改@OnMessage方法)
package com.szc.material.analysisService.confg.WebSocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.security.auth.message.MessageInfo;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author zhj
 * @ServerEndpoint:将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 * @OnOpen:当WebSocket建立连接成功后会触发这个注解修饰的方法。
 * @OnClose:当WebSocket建立的连接断开后会触发这个注解修饰的方法。
 * @OnMessage:当客户端发送消息到服务端时,会触发这个注解修改的方法。
 * @OnError:当WebSocket建立连接时出现异常会触发这个注解修饰的方法。
 * ————————————————
 * 版权声明:本文为CSDN博主「人人都在发奋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
 * 原文链接:https://blog.csdn.net/qq991658923/article/details/127022522
 */
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class MyWebSocketHandler extends TextWebSocketHandler {

    /**
     * 线程安全的无序的集合
     */
    private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();

    /**
     * 存储在线连接数
     */
    private static final Map<String, Session> SESSION_POOL = new HashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        try {

            SESSIONS.add(session);
            SESSION_POOL.put(userId, session);
            log.info("【WebSocket消息】有新的连接,总数为:" + SESSIONS.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session,@PathParam(value = "userId") String userId) {
        try {

            SESSIONS.remove(session);
            SESSION_POOL.remove(userId);
            log.info("【WebSocket消息】连接断开,总数为:" + SESSION_POOL.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnMessage
    public void onMessage(String message, @PathParam(value = "userId") String userId) {
        JSONObject jsonObject = JSON.parseObject(message);
        if("heartbeat".equals(jsonObject.get("type").toString())){
            Map<String,String> messageInfor=new HashMap<>();
            messageInfor.put("type","heartbeat");
            messageInfor.put("message","我收到了你的心跳");
            sendOneMessage( userId,messageInfor);
            log.info("【WebSocket消息】收到客户端消息:" + message);
        }

    }

    /**
     * 此为广播消息
     *
     * @param message 消息
     */
    public void sendAllMessage(String message) {
        log.info("【WebSocket消息】广播消息:" + message);
        for (Session session : SESSIONS) {
            try {
                if (session.isOpen()) {
                    session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 此为单点消息
     *
     * @param userId  用户编号
     * @param message 消息
     */
    public void sendOneMessage(String userId, Map<String,String> message) {
        Session session = SESSION_POOL.get(userId);
        if (session != null && session.isOpen()) {
            try {
                synchronized (session) {
                    log.info("【WebSocket消息】单点消息:" + message.get("message"));
                    session.getAsyncRemote().sendText(JSON.toJSONString(message));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 此为单点消息(多人)
     *
     * @param userIds 用户编号列表
     * @param message 消息
     */
    public void sendMoreMessage(String[] userIds, String message) {
        for (String userId : userIds) {
            Session session = SESSION_POOL.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【WebSocket消息】单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    }






4.在需要的地方进行调用

(1)先注入websocket服务类

    @Autowired
    MyWebSocketHandler myWebSocketHandler;

(2)在方法中调用给前端发消息的方法

myWebSocketHandler.sendOneMessage(userId,messageInfor);

问题:

1.如果你在controller层调用了service层中身为异步的方法出现了HttpServeletrequst空指针你需要在controller层调用异步方法前加入下面的代码。

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        RequestContextHolder.getRequestAttributes().setAttribute("request", request, RequestAttributes.SCOPE_REQUEST);
        RequestContextHolder.setRequestAttributes(servletRequestAttributes,true);//设置子线程共享
 

调用requst中的参数时必须使用下面的方法

 HttpServletRequest request2 = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  if( request2.getHeader(UserContext.USER_NAME)!=null){
            return Optional.of(request2.getHeader(UserContext.USER_NAME));
        }

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

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

相关文章

Python学习--函数传值问题

四、函数传值问题 先看一个例子&#xff1a; # -*- coding: UTF-8 -*- def chagne_number( b ):b 1000b 1 chagne_number(b) print( b )最后输出的结果为&#xff1a; 1先看看运行的结果&#xff1f; 想一下为什么打印的结果是 1 &#xff0c;而不是 1000 &#xff1f; …

凉鞋的 Godot 笔记 204. 语句

204. 语句 在上一篇&#xff0c;我们接触了三种常见的类型&#xff0c;如下所示&#xff1a; 这样我们算是对变量进行了一个入门了。 其实我们除了变量&#xff0c;我们还接触了一个叫做语句的概念。 我们可以看下代码: extends Node# Called when the node enters the sce…

Kubernetes原生微服务开发实践

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 引言 随着互联网业务的…

差值结构顺序的稳定性

3( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 4( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 5( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 做3个网络,让网络的输入都只有3个节点&#xff0c; 一个网络的训练集有3张图片&#xff0c;一个网络训练集有4张图片&#xff0c;一个网络的训练集有5张…

1.VS2022+QT

项目需要对倾斜摄影进行解析编辑&#xff0c;所以采用osg平台。 1.VS2022 百度下载安装一个包&#xff0c;然后选择C桌面开发。 2.Qt 在以下网站下载在线安装程序&#xff0c;并通过cmd运行安装程序。然后根据截图配置安装。 qt | 镜像站使用帮助 | 清华大学开源软件镜像站 |…

Comate SaaS版:开发者的梦想工具终于来了

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

适用于 Windows 的 7 个免费网络课程录制软件

随着虚拟工作和在线教学的兴起&#xff0c;屏幕录制工具是必不可少的&#xff0c;也是当今必备的应用程序。在录制在线会议、创建教程和学术课程或与在线团队协作项目时&#xff0c;它们会派上用场。 网络上有丰富的屏幕录制工具&#xff0c;支持各种平台并提供不同的功能。选…

Python和Java有什么区别

Python和Java是两种很流行的编程语言,但它们有以下几个主要区别: 语言类型:Python是一种解释型语言,Java是一种编译型语言。Python源代码被解释执行,Java源代码先被编译为字节码,然后在JVM上运行。语法简洁性:Python的语法更简洁简单,Java的语法相对更复杂。Python专注于代码的…

MySQL驱动包下载

使用java来连接&#xff0c;进入下述网址&#xff1a; MySQL :: Download Connector/J 如果不是java&#xff0c;则进入下述网址 MySQL :: MySQL Community Downloads

uni-app小程序,uview-ui组件样式无法穿透修改的解决办法

1.首先设置以下选项.该选项的作用是让微信小程序允许样式穿透. 在需要改动的文件内加上 options: { styleIsolation: shared } 2.然后再使用vue的样式穿透写法. ::v-deep .类样式{} 或者 /deep/ .类样式{}

算法与数据结构-贪心算法

文章目录 什么是贪心算法贪心算法实战分析1. 分糖果2. 钱币找零3. 区间覆盖4.霍夫曼编码 什么是贪心算法 关于贪心算法&#xff0c;我们先看一个例子。 假设我们有一个可以容纳 100kg 物品的背包&#xff0c;可以装各种物品。我们有以下 5 种豆子&#xff0c;每种豆子的总量和…

client-go 实现一个自动创建ingress资源的controller

需求&#xff1a; 创建的service annotaion中如果包含ingress/http: "true"的时候&#xff0c;会自动将该服务的ingress资源创建出来&#xff0c;当删除掉ingress/http: "true"的时候&#xff0c;自动删除ingress&#xff0c; 同时将service删除掉的时候也…

堆排序(HeapSort)详解

堆排序 一&#xff0c;思考二&#xff0c;算法步骤2.1向上调整建堆2.2关键思路2.3完整代码补充&#xff1a;向下调整建堆 三&#xff0c;总结 一&#xff0c;思考 我们上一篇文章讲到了堆的基本实现&#xff0c;那么堆排序我们就先借助堆的结构来实现。 void HeapSort(HP* hp…

2023年10月份最新香港优才计划申请攻略,附政策、申请流程、续签!

2023年10月份最新香港优才计划申请攻略&#xff0c;附政策、申请流程、续签&#xff01; 2023年10月份香港优才计划利好政策持续推进&#xff0c;越来越多的人咨询香港优才计划申请事宜。现在为大家整理了一份全面的优才申请攻略&#xff0c;如果你计划在今年申请香港优才&…

MySQL基础练习题

数据表介绍 --1.学生表 Student(SId,Sname,Sage,Ssex) --SId 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course(CId,Cname,TId) --CId 课程编号,Cname 课程名称,TId 教师编号 --3.教师表 Teacher(TId,Tname) --TId 教师编号,Tname 教师姓名 --4.成绩…

2021年06月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 下列程序运行的结果是&#xff1f; s hello print(sworld)A: sworld B: helloworld C: hello D: world 答案&#xff1a…

数据飞轮拆解车企数据驱动三板斧:数据分析、市场画像、A/B 实验

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 近日&#xff0c;火山引擎数智平台&#xff08;VeDI&#xff09;2023 数据飞轮汽车行业研讨会在上海举办&#xff0c;活动聚焦汽车行业数字化转型痛点&#xff0c;从…

NEWCC:新时代的区块链生态新币私募造势平台

在当今区块链领域&#xff0c;这项技术已经为金融资产注入了全新的生机&#xff0c;同时也为初创企业提供了新的商业模式和融资机会。通过代币的金融属性&#xff0c;企业和项目方得以实现资本的初期积累&#xff0c;同时在区块链空间以更低成本和更高效率进行交易和服务创新。…

适合在虚拟化环境中部署 Kubernetes 的三个场景

在《虚拟化 vs. 裸金属&#xff1a;K8s 部署环境架构与特性对比》文章中&#xff0c;我们从架构和特性的角度&#xff0c;对比了在虚拟化和裸金属环境部署 Kubernetes 的优劣势&#xff0c;并在文末列举了两者更适合的应用场景。本文&#xff0c;我们将聚焦以虚拟化环境支持 K8…

英语——分享篇——每日200词——3401-3581

3401——colony——[kɒlənɪ]——n.殖民地&#xff0c;(某一类人的)聚居区——colony——co可乐(熟词coke)lon笼(拼音)y树杈(编码)——把可乐装在笼子里用树杈挑着去殖民地——The newly-occupied Italian colony of Libya rose in revolt in 1914.——意大利新占领的殖民地利…