springBoot与Vue共同搭建webSocket环境

欢迎使用Markdown编辑器

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

前端

1. 创建websocket的配置文件在utils文件夹下websocket.js
// my-js-file.js
import { Notification } from 'element-ui'
// 暴露自定义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 === '物资管理模块-导入成功!') {
          Notification({
            title: '消息',
            message: '物资管理模块-导入成功,请手动刷新页面查看数据!',
            type: 'success',
            duration: 0,
            position: 'top-righ'
          })
        } else {
          Notification({
            title: '消息',
            message: '错了:' + info.message,
            type: 'error',
            duration: 0,
            position: 'top-righ'
          })
        }

        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连接是否断开,断开重连(在router的router.beforeEach函数中)
 if (socket.websocketState === false) {
      socket.initWebSocket(
        `ws:${process.env.VUE_APP_WEBSOCKET_BASE_API}/websocket/` + localStorage.getItem('USERNAME')
      )
    }
5. 设置退出时关闭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));
        }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MSQL系列(九) Mysql实战-Join算法底层原理

Mysql实战-Join算法底层原理 前面我们讲解了BTree的索引结构&#xff0c;及Mysql的存储引擎MyISAM和InnoDB,今天我们来详细讲解下Mysql的查询连接Join的算法原理 文章目录 Mysql实战-Join算法底层原理1.Simple Nested-Loop Join 简单嵌套循环2.Block Nested-Loop Join 块嵌套…

linux 内存检测工具 kfence 详解(一)

版本基于&#xff1a; Linux-5.10 约定&#xff1a; PAGE_SIZE&#xff1a;4K 内存架构&#xff1a;UMA 系列博文&#xff1a; linux 内存检测工具 kfence 详解(一) linux 内存检测工具 kfence 详解(二) 0. 前言 本文 kfence 之外的代码版本是基于 Linux5.10&#xff0c;…

ORACLE-递归查询、树操作

1. 数据准备 -- 测试数据准备 DROP TABLE untifa_test;CREATE TABLE untifa_test(child_id NUMBER(10) NOT NULL, --子idtitle VARCHAR2(50), --标题relation_type VARCHAR(10) --关系,parent_id NUMBER(10) --父id );insert into untifa_test (CHILD_ID, TITLE, RELATION_TYP…

React 核心与实战2023版

课程亮点: 完整的前后台项目(PC+移动;完成业务;)React 最新企业标准技术栈(React 18 + Redux + ReactRouter + AntD)React + TypeScript (为大型项目奠定了基础)课程内容安排: React 介绍 React 是什么? React 是由Meta公司研发,是一个用于 构建Web和原生交互界面…

支持CT、MR三维后处理的医学PACS源码

医学影像归档与通信系统&#xff08;picture archiving and communication systems&#xff0c;PACS&#xff09;是应用于医院的数字医疗设备&#xff0c;如CT、MR&#xff08;磁共振&#xff09;、US&#xff08;超声成像&#xff09;、X线、DSA&#xff08;数字减影&#xff…

npm更新包时This operation requires a one-time password.

[访问我的npm包](mhfwork/yt-ui - npm) 更新npm包时出现 This operation requires a one-time password.是因为需要认证 解决办法 1. 点击红线处的链接 2. 进入npm官网获取指定秘钥 3. 再次填入 one-time password 即可

word页脚设置,页脚显示第几页共有几页设置步骤

word页脚设置&#xff0c;页脚显示第几页共有几页设置步骤&#xff1a; 具体步骤&#xff1a; 步骤1&#xff1a; 步骤1.1选择页脚---空白页脚 步骤1.2&#xff0c;在"[在此处键入]"&#xff0c;直接输入你需要的格式&#xff0c;如 “第页/共页” 步骤1.3选择第“…

定义USB接口,鼠标类和键盘类都可以作为实现类去实现USB接口

目录 程序设计 程序分析 系列文章 ​ 如图所示,我们电脑上都有USB接口,当我们的鼠标和键盘插上去之后才可以使用,拔出来就关闭使用。其实具体是什么USB设备,笔记本并不关心,只要符合USB规格的设备都可以。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守USB规范…

财务数字化转型是什么?_光点科技

财务数字化转型是当今企业发展中的一项关键策略&#xff0c;旨在借助先进的数字技术&#xff0c;重新塑造和优化财务管理体系&#xff0c;以适应迅速变化的商业环境。这一转型不仅仅是技术的升级&#xff0c;更是对企业财务理念和流程的全面升级和改革。 财务数字化转型的核心在…

一文了解GC垃圾回收

一文了解GC垃圾回收 1 判断一个对象为垃圾对象的方法 引用计数法(弃用) 可达性分析算法 是否有指向GC root 的引用链&#xff0c;如果有&#xff0c;不是垃圾对象 ---->GC roo:即rt.jar包中内容 2 内存泄漏与内存溢出区别 泄漏&#xff1a;原本需要被回收的对象&#…

Mac版好用的Git客户端 Fork 免激活

Fork是一款强大的Git客户端软件&#xff0c;在Mac和Windows操作系统上都可以使用。汇集了众多先进的功能和工具&#xff0c;可以帮助用户更方便地管理和控制Git仓库。 Fork的界面简洁直观&#xff0c;易于使用。它提供了许多高级的Git功能&#xff0c;如分支管理、合并、提交、…

微信小程序 slot 不显示

问题:创建组件&#xff0c;使用带名字的slot&#xff0c;页面调用组件使用slot不显示 源码&#xff1a; 组件xml <view class"p-item br24" style"{{style}}"><slot name"right" wx:if"{{!custBottom}}"></slot>&l…

【咕咕送书 | 第四期】《ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践》

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《粉丝福利》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 ⛳️ 写在前面参与规则一、前言1.0 人工智能新技术如何创新工作 &#xff1f; 二、内容简介三、作者简介四、专家推…

Spring Boot 使用 Disruptor 做内部高性能消息队列

这里写自定义目录标题 一 、背景二 、Disruptor介绍三 、Disruptor 的核心概念3.1 Ring Buffer3.2 Sequence Disruptor3.3 Sequencer3.4 Sequence Barrier3.5 Wait Strategy3.6 Event3.7 EventProcessor3.8 EventHandler3.9 Producer 四、案例-demo五、总结 一 、背景 工作中遇…

C++in/out输入输出流[IO流]

文章目录 1. C语言的输入与输出2.C的IO流2.1流的概念2.2CIO流2.3刷题常见while(cin >> str)重载强制类型转换运算符模拟while(cin >> str) 2.4C标准IO流2.5C文件IO流1.ifstream 1. C语言的输入与输出 C语言用到最频繁的输入输出方式就是scanf ()与printf()。 scanf…

Stable Diffusion 图生图+ControlNet list index out of range

在webui1.5中用图生图ControlNet批量处理图片的时候报错&#xff1a; controlnet indexError: list index out of range 解决方法&#xff1a; 在controlNet的设置页中勾选不输出检测图即可。 参考&#xff1a;https://github.com/AUTOMATIC1111/stable-diffusion-webui/issu…

Makefile三个版本的编写

1.Makefile Makefile是一个工程管理文件&#xff0c;简化编译的流程&#xff0c;完成自动化编译的过程 在Makefile中&#xff0c;会把编译的过程分为两步&#xff0c;先生成.o文件&#xff0c;再对.o文件链接&#xff0c;生成可执行文件 Makefile由变量、函数、和规则构成 2.引…

【APP VTable】和市面上的 Table 组件一样,都是接收表格[] 以及数据源[]

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; uni-app | 小程序开发 开发工具&#xff1a;HBuilderX 这里写目录标题 表格组件USE 表格组件 <template><view class"scroll-table-wrapper"><view class"scroll-table-container"…

2023 MathorCup(妈妈杯) 数学建模挑战赛B题完整解题思路+模型+代码

2023妈妈杯数学建模B题完整版思路、模型代码已出&#xff01;&#xff01;&#xff01; 云顶数模最新完整版解题思路、模型代码&#xff0c;供大家参考~~ B题目 解题思路 详细模型解析&#xff1a;

JavaWeb——关于servlet种mapping地址映射的一些问题

6、Servlet 6.4、Mapping问题 一个Servlet可以指定一个映射路径 <servlet-mapping><servlet-name>hello</servlet-name><url-pattern>/hello</url-pattern> </servlet-mapping>一个Servlet可以指定多个映射路径 <servlet-mapping>&…