vue(element ui)使用websocket及心跳檢測方式
業(yè)務(wù)需求
前后端分離,后端java。項目添加websocket消息推送,檢測到數(shù)據(jù)更改前端進(jìn)行數(shù)據(jù)捕獲,并實時渲染。添加心跳檢測機(jī)制實時檢測連接是否斷開,斷開則重新連接。
項目業(yè)務(wù)
是在消息推送后返回一個有新消息提醒,然后再調(diào)用一個列表查詢接口,用vuex維護(hù)起來,放到需要的頁面進(jìn)行展示及之后的業(yè)務(wù)流程
效果圖(123張圖分別為初始化連接,模擬網(wǎng)絡(luò)斷開進(jìn)行重連,模擬網(wǎng)絡(luò)恢復(fù)重新建立連接)



需要連接點(維護(hù)點)
- 登錄后進(jìn)行websocket連接 。
- 瀏覽器強制刷新時websocket會自動斷開,所以需要重新連接
- 代碼維護(hù)在vuex中
登錄調(diào)用方法
//去vuex,utils里調(diào)用getUserInfo方法
this.$store
.dispatch("utils/getUserInfo", { initSocket: true })
.then((res) => {
});
因為我的消息推送接口依賴于登錄接口返回的id,所以我需要再登錄成功回調(diào)后再進(jìn)行我的webwocket連接方法。業(yè)務(wù)不同的小伙伴只需要把此方法放在登錄成功的回調(diào)后即可

vuex里index進(jìn)行注冊

utils.js代碼塊,主要代碼(備注會一行一行寫清楚,請耐心查看)
//因為我本身的業(yè)務(wù)是在有新消息收到后再調(diào)用列表查詢的接口,所以會引入框架的請求方法(無此需求的小伙伴不用理會)
import * as $http from '@/utils/request'
import { mapGetters } from 'vuex';
const utils = {
namespaced: true,
state: { //聲明變量,跟單頁面data return聲明一個意思
message: [],
readSysMsgList: [],
readSysMsgListLength: "",
wsUrl: '',
ws_heart: '', // ws心跳定時器
lockReconnect: false, //是否真正建立連接
timeoutnum: null //斷開 重連倒計時
},
getters: {},
mutations: {
//vuex突變,拿到維護(hù)的數(shù)據(jù)后交給對應(yīng)的變量,供頁面使用(可查看資料vuex用法)
unReadSysMsg(state, options) {
state.readSysMsgList = options
},
unReadSysMsglenght(state, options) {
state.readSysMsgListLength = options
},
},
actions: {
// 獲取連接信息(在登錄接口調(diào)用getUserInfo方法建立連接)
//getUserInfo有一個dispatch 參數(shù),此參數(shù)目的為了調(diào)用actions中其他方法
getUserInfo({ state, commit, dispatch },) {
//先判斷瀏覽器是否支持WebSocket
if (typeof WebSocket === "undefined") {
alert("您的瀏覽器不支持socket");
} else {
//提前判斷 WebSocket是否已經(jīng)建立,避免重復(fù)連接問題
if (this.socket) {
this.socket.close()
}
//WebSocket連接時我的地址需要拼接用戶的id所以再此進(jìn)行獲取,也就是在登錄時為啥把getUserInfo放在登錄成功的回調(diào)中,無此需要的小伙伴可省略
let id = JSON.parse(localStorage.getItem('loginUserAllInfo')).user.id
// 實例化socket (長連接為ws地址格式)這一步就是建立連接(自行放入url即可)
this.socket = new WebSocket(`ws://xxx.xxx.xxx/${id}`);
//new WebSocket有很多內(nèi)置的方法,onopen 就是證明連接已經(jīng)成功,可以在此進(jìn)行心跳檢測
this.socket.onopen = () => {
console.log('websocket已連接');
//調(diào)用reset方法,reset方法是跟getUserInfo同級,在vuex中需要用dispatch進(jìn)行調(diào)用(可參考actions里第二行注釋解釋,必須得有接收參數(shù))。跟單頁面中的this.reset()同理
dispatch("reset")
//本項目業(yè)務(wù),建立連接后先進(jìn)行列表查詢
$http.get("/task/sysMessage/getUnReadSysMsg").then((res) => {
console.log(res.data)
//列表查詢的結(jié)果拿commit存到vuex當(dāng)中,然后再給了state中的變量,到時候頁面就可以直接拿到了。比如先把unReadSysMsg存起來,給了readSysMsgList ,然后頁面獲取數(shù)據(jù)的時候就可以this.readSysMsgList 稍后會有頁面展示的代碼
commit("unReadSysMsg", res.data);
commit("unReadSysMsglenght", res.data.length);
})
};
// 監(jiān)聽socket消息(主要內(nèi)置方法,收到消息后會進(jìn)行監(jiān)聽接收)
this.socket.onmessage = (msg) => {
// console.log('接收到新消息--------' + msg.data)
//同樣調(diào)用reset進(jìn)行心跳檢測重置
dispatch("reset")
//本項目業(yè)務(wù),主要心跳檢測,我會在start方法里每隔30秒主動進(jìn)行ping推送,看連接是否還存在,如果接收到pong到pong就證明是因為我主動推送消息拿到的監(jiān)聽結(jié)果,就不需要處理。如果接收到非pong則證明是后端有新消息推送過來,重新進(jìn)行列表查詢,刷新數(shù)據(jù)
if (msg.data == "pong") {
} else {
//直接返回數(shù)據(jù)的可避免此操作,直接拿到msg,commit存儲好就可
$http.get("/task/sysMessage/getUnReadSysMsg").then((res) => {
commit("unReadSysMsg", res.data);
commit("unReadSysMsglenght", res.data.length);
console.log(res.data)
})
}
};
// 監(jiān)聽socket錯誤信息(websocket斷開會進(jìn)入此方法,需要進(jìn)行重連)
this.socket.onclose = function (e) {
console.log('關(guān)閉了')
//斷開連接后調(diào)用reconnect進(jìn)行重新連接
dispatch("reconnect")
};
// WebSocket發(fā)生錯誤
this.socket.onerror = function (e) {
//斷開連接后調(diào)用reconnect進(jìn)行重新連接
dispatch("reconnect")
console.log("WebSocket發(fā)生錯誤");
};
}
},
//重新連接(斷開跟錯誤后都需要進(jìn)行重連操作)
reconnect({ dispatch }) {
var that = this;
if (that.lockReconnect) {
// 是否真正建立連接
return;
}
that.lockReconnect = true;
//沒連接上會一直重連,設(shè)置延遲避免請求過多
that.timeoutnum && clearTimeout(that.timeoutnum);
// 如果到了這里斷開重連的倒計時還有值的話就清除掉
that.timeoutnum = setTimeout(function () {
console.log('重啟中')
//然后新連接(dispatch照樣進(jìn)行方法getUserInfo的調(diào)用)
dispatch('getUserInfo')
that.lockReconnect = false;
}, 60000);
},
//建立連接及有新消息接收后進(jìn)行心跳重置
reset({ dispatch }) {
//重置心跳
var that = this;
//清除時間(清除心跳計時)
clearInterval(that.ws_heart)
//重啟心跳
dispatch("start")
},
//心跳檢測
start({ dispatch }) {
//實時推送ping消息,查看連接是否斷開
this.ws_heart = setInterval(() => {
let actions = "ping"
this.socket.send(JSON.stringify(actions));
}, 30000)
},
}
}
export default utils

以上主要代碼已完畢
app.vue中再次調(diào)用getUserInfo方法,避免瀏覽器強制刷新導(dǎo)致斷開連接
mounted() {
//退出登錄后會把localStorage清空,避免控制臺報錯
if (JSON.parse(localStorage.getItem('loginUserAllInfo'))) {
let userId = JSON.parse(localStorage.getItem('loginUserAllInfo')).user.id
if (userId) {
this.$store
.dispatch("utils/getUserInfo", { initSocket: true })
.then(() => {
});
}
} else {
}
},

頁面使用(附效果圖)
//頁面可直接使用數(shù)據(jù),放到想用到的地方
<template>
<div>
{{this.readSysMsgList}}
</div>
</template>
//引入vuex
import { mapActions, mapState } from 'vuex'
export default {
computed: {
//獲取vuex中存放的數(shù)據(jù)
...mapState("utils", ["readSysMsgList","readSysMsgListLength"]),
},
}
//貼入本項目的業(yè)務(wù)代碼(主要看我el-table遍歷data :data="this.readSysMsgList"直接就可渲染)
<template>
<div class="navbar">
<el-dialog :modal="false" title="您的消息" :visible.sync="moreFlag" class="tableLocation">
<el-table :data="this.readSysMsgList" height="200" @row-click="rowClick">
<el-table-column align="center" width="40">
<template slot-scope="scope">
<i v-if="scope.row.messageType == '1'" class="el-icon-warning" style="color:#d0183d"></i>
<i v-else class="el-icon-warning" style="color:#45d720"></i>
</template>
</el-table-column>
<el-table-column property="messageType" label="消息類型" width="70">
<template slot-scope="scope">
<div v-if="scope.row.messageType == '1'">新任務(wù)</div>
<div v-else-if="scope.row.orderStatus == '2'">待跟進(jìn)</div>
<div v-else-if="scope.row.orderStatus == '3'">待執(zhí)行</div>
</template>
</el-table-column>
<el-table-column property="content" label="消息內(nèi)容"></el-table-column>
</el-table>
</el-dialog>
</div>
</template>

難點在于連接成功后onopen、onmessage方法心跳檢測,以及onclose、onerror斷開連接后進(jìn)行重連。這塊還添加了自己本身的另一個短連接口業(yè)務(wù),所以會難梳理。不懂的還請vuex里代碼每行注釋進(jìn)行通讀。
通俗講就是new WebSocket 建立連接后,在自帶的方法里onopen(建立連接)、onmessage(收到推送消息)、onclose(連接斷開)、onerror(連接發(fā)生錯誤)進(jìn)行自己的業(yè)務(wù)流程。
注:此方法為原生方法,如感覺繁瑣還可查看"socket.io-client","vue-socket.io"插件使用,但我本項目用的時候不知是不是因為后臺是java寫的,在后端給的地址基礎(chǔ)上,會自動拼接EIO = 3&transport = websocket這個參數(shù),導(dǎo)致匹配不到接口報404,讓后端把后幾個參數(shù)寫死解決方法也沒走通,又回到了原生方法,有興趣的可自行嘗試

總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue @click與@click.native,及vue事件機(jī)制的使用分析
這篇文章主要介紹了vue @click與@click.native,及vue事件機(jī)制的使用分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04
vue-router判斷頁面未登錄自動跳轉(zhuǎn)到登錄頁的方法示例
這篇文章主要介紹了vue-router判斷頁面未登錄自動跳轉(zhuǎn)到登錄頁的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
vue中優(yōu)雅實現(xiàn)數(shù)字遞增特效的詳細(xì)過程
項目中需要做數(shù)字滾動增加的效果,一開始很懵,研究了一下原理,發(fā)現(xiàn)很簡單,下面這篇文章主要給大家介紹了關(guān)于vue中優(yōu)雅實現(xiàn)數(shù)字遞增特效的詳細(xì)過程,需要的朋友可以參考下2022-12-12
vue項目中v-model父子組件通信的實現(xiàn)詳解
vue.js,是一個構(gòu)建數(shù)據(jù)驅(qū)動的 web 界面的庫。Vue.js 的目標(biāo)是通過盡可能簡單的 API 實現(xiàn)響應(yīng)的數(shù)據(jù)綁定和組合的視圖組件。下面這篇文章主要給大家介紹了關(guān)于vue項目中v-model父子組件通信實現(xiàn)的相關(guān)資料,需要的朋友可以參考下。2017-12-12
element-ui el-select下拉框el-date-picker彈出框定位問題解決方案(推薦)
項目開發(fā)過程中發(fā)現(xiàn)el-select和el-date-picker彈出框顯示時候,滾動屏幕,導(dǎo)致彈出框定位出現(xiàn)問題,這篇文章主要介紹了element-ui el-select下拉框el-date-picker彈出框定位問題解決方案(推薦),需要的朋友可以參考下2024-07-07

