springboot+websocket+vue 服务端像前端推送消息

最近项目中需要接收告警提示 故采用了wobsocket来实现消息推送至前端

pom依赖

<!-- WebSocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

2.配置WebSocketConfig

package com.witsky.work.push;

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

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3.创建WebSocketServer

package com.witsky.work.push.service;

import com.alibaba.fastjson.JSON;
import com.witsky.core.response.RestResponse;
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.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/push/{sid}")
@Component
@Slf4j
public class WebSocketServer {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //接收sid
    private String sid="";
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
        this.sid=sid;
        /*try {
            sendMessage(JSON.toJSONString(RestResponse.success()));
        } catch (IOException e) {
            log.error("websocket IO异常");
        }*/
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        //log.info("收到来自窗口"+sid+"的信息:"+message);
        if("heart".equals(message)){
            try {
                sendMessage("heartOk");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //群发消息
       /* for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
    }

    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message) throws IOException {

        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
//                if(sid==null) {

                    item.sendMessage(message);
                log.info("推送消息到窗口"+item.sid+",推送内容:"+message);
//                }else if(item.sid.equals(sid)){
//                    item.sendMessage(message);
//                }
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

}

4.推送信息

package com.witsky.work.push.controller;

import com.witsky.work.push.service.WebSocketServer;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

/**
 * @Author: Administrator
 * @Description:
 * @Date:Create:in 2019/9/27 16:51
 */
@RestController
@Slf4j
public class PushMsgConttroller {

    @Autowired
    WebSocketServer webSocketServer;
    @ApiOperation(value = "报警信息订阅",notes = "查询")
    @ResponseBody
    @RequestMapping(value = "/alarmEventPushWord",method = RequestMethod.POST)
    public void alarmEventPushWord(@RequestBody(required = true) String requestBody) {
        try {
            webSocketServer.sendInfo(requestBody);//接收需要推送的消息 推送给前端
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.前端vue 代码(找前端小姐姐要的代码)  前端链接的地址 开头是需要用  http对应 ws://;

ws://ip:端口/sjqbakend/push/{sid}

<template>
  <div class="app-wrapper">
    <nav-bar></nav-bar>
    <sidebar class="sidebar-container" v-if="isVisible"></sidebar>
    <app-main></app-main>
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
import {NavBar, AppMain, Sidebar} from './components'
export default {
  name: 'Layout',
  data () {
    return {
      isVisible: false,
      websocket: null,
      lockReconnect: false, //避免ws重复连接
      timeout: 60*1000, // 1分钟一次心跳
      timeoutObj: null, // 心跳定时器
      serverTimeoutObj: null, // 心跳定时器
      timeoutnum: null, // 断开 重连的定时器
      notes: []
    }
  },
  components: {
    NavBar,
    AppMain,
    Sidebar
  },
  computed: {
    ...mapGetters([
      'name'
    ]),
  },
  watch: {
    '$route' (to, from) {
      if (to.meta && to.meta.sliderSys) {
        this.isVisible = true
      }else {
        this.isVisible = false
      }
    }
  },
  created() {
    if (this.$route.meta.sliderSys) {
      this.isVisible = true
    }else {
      this.isVisible = false
    }
  },
  mounted() {
    if ('WebSocket' in window) {
      this.initWebSocket()
    } else {
      alert('当前浏览器不支持websocket,无法接收告警推送! ')
    }
  },
  beforeDestroy() {
    if (this.websocket != null) {
      clearTimeout(this.timeoutObj)
      clearTimeout(this.serverTimeoutObj)
      clearTimeout(this.timeoutnum)
      this.timeoutObj = null
      this.serverTimeoutObj = null
      this.timeoutnum = null
      this.onbeforeunload()
    }
  },
  methods: {
    initWebSocket() {
      
      //console.log(url);//'ws://ip:端口/sjqbakend/push/'+ this.name 
      const wsUrl = process.env.WEBSOCKET_URL +'/push/'+ this.name
      if (this.websocket == null) {
        this.websocket = new WebSocket(wsUrl)
      }

      //连接错误
      this.websocket.onerror = this.setErrorMessage
 
      // //连接成功
      this.websocket.onopen = this.setOnopenMessage
 
      //收到消息的回调
      this.websocket.onmessage = this.setOnmessageMessage
 
      //连接关闭的回调
      this.websocket.onclose = this.setOncloseMessage
 
      //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
      window.onbeforeunload = this.onbeforeunload
    },
    reconnect () { //重新连接
      var that = this;
      if(that.lockReconnect) {
          return;
      };
      that.lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      console.log('断开重新连接...');
      that.timeoutnum && clearTimeout(that.timeoutnum);
      that.timeoutnum = setTimeout(function () {
          //新连接
          that.initWebSocket();
          that.lockReconnect = false;
      },10000);
    },
    reset () { // 重置心跳
      var that = this;
      //清除时间
      clearTimeout(that.timeoutObj);
      clearTimeout(that.serverTimeoutObj);
      //重启心跳
      that.start();
    },
    start(){//开启心跳
      var that = this;
      that.timeoutObj && clearTimeout(that.timeoutObj);
      that.serverTimeoutObj && clearTimeout(that.serverTimeoutObj);
      that.timeoutObj = setTimeout(function(){
          //这里发送一个心跳,后端收到后,返回一个心跳消息,
          if (that.websocket.readyState == 1) {//如果连接正常
              that.websocket.send("heart");
          }else{//否则重连
              that.reconnect();
          }
          that.serverTimeoutObj = setTimeout(function() {
              //超时关闭
              that.websocket.close();
          }, that.timeout);

      }, that.timeout)
    },
    setErrorMessage() {
      console.log("WebSocket连接发生错误" + '   状态码:' + this.websocket.readyState)
      this.reconnect() //重连websocket
    },
    setOnopenMessage() {
      console.log("WebSocket连接成功" + '   状态码:' + this.websocket.readyState)
      this.start() //开启心跳
    },
    setOnmessageMessage(event) {
      //const redata = JSON.parse(event.data);
      this.reset() // 收到服务器消息,心跳重置
      const res =  event.data
      if (res !== 'heartOk') {
        console.log('服务端返回:' + res)
        let result = JSON.parse(res)
        for (let i = 0; i < result.length; i++) {
          let _strHtml = `<div class="warn-body">
                            <div style="color:#78cdff;font-size:16px;font-weight:bold;margin-bottom:10px;"><span style="color:#fff;font-weight:normal;">告警时间:</span>${result[i].time}</div>
                            <div style="color:#78cdff;font-size:16px;font-weight:bold;margin-bottom:10px;"><span style="color:#fff;font-weight:normal;">告警位置:</span>${result[i].detectorName?result[i].detectorName:result[i].mainframeName}</div>
                            <div style="color:#78cdff;font-size:16px;font-weight:bold;margin-bottom:10px;"><span style="color:#fff;font-weight:normal;">告警类型:</span>${result[i].alarmTypeName?result[i].alarmTypeName:''}</div>
                            <div style="color:#78cdff;font-size:16px;font-weight:bold;margin-bottom:10px;"><span style="color:#fff;font-weight:normal;">告警数值:</span>${result[i].analogvalue}</div>
                            <div style="color:#78cdff;font-size:16px;font-weight:bold;"><span style="color:#fff;font-weight:normal;">告警内容:</span>${result[i].alarmContent}</div>
                          </div>`
          let noti = this.$notify({
            title: '告警提醒',
            dangerouslyUseHTMLString: true,
            message:  _strHtml,
            position: 'bottom-right',
            duration: 0,
          })
          this.notes.push(noti)
        }
      }
    },
    setOncloseMessage() {
      console.log("WebSocket连接关闭" + '   状态码:' + this.websocket.readyState)
      // console.log(this.$route);
      if (this.$route.meta.noWebsocket) { // 登录页面不需要重连
        clearTimeout(this.timeoutObj)
        clearTimeout(this.serverTimeoutObj)
        clearTimeout(this.timeoutnum)
        this.timeoutObj = null
        this.serverTimeoutObj = null
        this.timeoutnum = null
        this.notes.forEach((item)=>{
          item.close()
        })
      }else {
        this.reconnect() //重连websocket
      }
    },
    onbeforeunload() {
      this.closeWebSocket()
    },
    closeWebSocket() {
      this.websocket.close()
    }
  }
}
</script>
<style scope>
  .app-wrapper {
    width: 100%;
    height: 100%;
  }
  .el-notification {
    width: 415px;
    background-color: rgba(18, 47, 86, 0.95);
    border: none;
    border-radius: 0;
    padding: 0;
  }
  .el-notification.right {
    bottom: 14px !important;
  }
  .el-notification__group{
    margin: 0;
  }
  .el-notification__title {
    color: #ffbb18;
    font-size: 18px;
    margin: 0;
    margin: 20px auto 20px 20px;
  }
  .el-notification__content {
    width: 100%;
    border-top: 1px solid #2b5082;
    margin: 0;
    padding: 28px 30px;

  }
  .el-notification__closeBtn {
    color: #78cdff;
  }
  .el-notification__closeBtn:hover {
    color: #54bbf7;
  }
</style>

我们用的前后端分离 这个推送需要配置nginx  连接过一段时间就会断开  解决方法就是添加心跳  案例中给出的是有添加心跳来控制的  还有在nginx中配置

这个 proxy_read_timeout 默认60s  就是连接60s内没有会话信息那么websock连接就会关闭,心跳设置的时间要小于这个会话过期的时间才可

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值