1001 界面刷新 1006 服务不稳定 1005 前端主动关闭的默认编码
①只有心跳和接收消息 ②只有接收消息类型后,在界面再去调用服务获取信息(后端这样设计的😇) ③根据ai的指导,用了指数重连机制
websocket封装
// WebSocketService.js
import dayjs from 'dayjs';
export default class WebSocketService {
constructor(url) {
this.wsuri = url;
this.socket = null;
this.lockReconnect = false;
this.initialDelay = 1000; // 初始延迟时间,单位为毫秒
this.maxDelay = 60 * 1000; // 最大延迟时间,单位为毫秒
this.maxRetries = 20; // 最大重连次数
this.retryCount = 0; // 当前重连次数
this.callbacks = {}; // 用于存储回调函数
// 心跳、心跳、心跳
this.timeout = 10 * 1000
this.timer = null
this.serverTimer = null // 这个有值表示正在心跳
this.isAlive = false // 连接标识,默认为 false
this.isStart = false
}
/* 心跳 */
heartReset () {
console.log('-进行心跳断开-', dayjs().format('YYYY-MM-DD HH:mm:ss'));
clearInterval(this.serverTimer)
this.serverTimer = null // 心跳断开
this.isAlive = false;
}
heartStart () {
clearInterval(this.serverTimer)// 清除上次的定时器,防止重复开启
this.serverTimer = null
this.isAlive = true; // 状态设置为 true
this.serverTimer = setInterval(() => { // 开始心跳
if (this.isAlive === true) {
console.log('发送 ping 心跳', dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.isAlive = false;
this.socket.send('ping'); // 发送心跳包
} else {
console.log('-心跳已经断开-', dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.heartReset() // 如果心跳包没有收到响应,停止心跳
this.socket.close(1005, '未收到心跳,重连'); // 断开重连
}
}, this.timeout);
}
/* 初始化 */
initWebSocket () {
if (!('WebSocket' in window)) {
console.error('您的浏览器不支持 WebSocket', dayjs().format('YYYY-MM-DD HH:mm:ss'));
return;
}
console.log(`WebSocket初始化中`, dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.socket = new WebSocket(this.wsuri);
this.socket.onopen = this.onOpen.bind(this);
this.socket.onerror = this.onError.bind(this);
this.socket.onmessage = this.onMessage.bind(this);
this.socket.onclose = this.onClose.bind(this);
}
onOpen () {
console.log('WebSocket连接成功', dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.retryCount = 0; // 重置重试计数
this.heartStart(); // 开启心跳
}
onError (error) {
console.error('WebSocket连接发生错误', error, dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.onClose(error);
}
onMessage (event) {
let message;
try {
if (event.data.includes('pong')) {
console.log('收到 pong 响应', dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.isAlive = true; // 收到 pong,说明服务器正常
} else {
message = JSON.parse(event.data); // 尝试解析 JSON 格式数据
const { messageType, data } = message;
// 调用注册的回调函数
if (this.callbacks[messageType]) {
console.log('收到注册消息:', message, dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.callbacks[messageType](data);
} else {
console.warn('未注册的消息类型:', messageType, dayjs().format('YYYY-MM-DD HH:mm:ss'));
}
}
} catch (e) {
console.warn('收到非 JSON 格式消息:', event.data, event.data.includes('pong'), dayjs().format('YYYY-MM-DD HH:mm:ss'));
// 调用非 JSON 数据的处理回调
if (this.nonJsonCallback) {
this.nonJsonCallback(event.data);
} else {
console.warn('未定义非 JSON 数据的处理回调', dayjs().format('YYYY-MM-DD HH:mm:ss'));
}
}
}
onClose (event) {
console.log(`WebSocket连接关闭`, event, dayjs().format('YYYY-MM-DD HH:mm:ss'));
this.heartReset(); // 关闭心跳
if (event.code !== 1000) {
this.reconnect(); // 重连
}
}
reconnect () {
if (this.retryCount < this.maxRetries) { // 如果小于最大重连次数
const delay = Math.min(this.initialDelay * Math.pow(2, this.retryCount), this.maxDelay);
console.log(`WebSocket开始第${this.retryCount + 1}次重连,(若失败,延迟${delay}ms后,执行下一次重连`, dayjs().format('YYYY-MM-DD HH:mm:ss'));
setTimeout(this.initWebSocket(), delay); // 第一次直接重连,第二次等待1s,后面指数回避(重连次数增加,delay时间指数增加
this.retryCount++;
} else {
console.error('Max retries reached, giving up.到达最大重连次数');
}
}
sendMessage (data) {
const payload = {
timestamp: new Date().toISOString(),
requestId: this.generateUUID(),
messageType: data.messageType,
data: data.data,
};
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(payload));
} else {
console.error('WebSocket未连接,无法发送消息', dayjs().format('YYYY-MM-DD HH:mm:ss'));
}
}
closeWebSocket () {
if (this.socket) {
this.socket.close(1000, '正常关闭');
}
}
generateUUID () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// 注册回调函数
registerCallback (messageType, callback) {
if (typeof callback === 'function') {
this.callbacks[messageType] = callback;
} else {
console.error('回调必须是函数');
}
}
// 移除回调函数
unregisterCallback (messageType) {
delete this.callbacks[messageType];
}
}
store使用
(之前看的时候有找到封装了的,看了不会用,要碎了)
// store/modules/websocket.js
import WebSocketService from '@/utils/websocket'
const { protocol, host } = location
export default {
namespaced: true,
state: {
wsService: null, // WebSocket连接对象
userId: ''// 连接的用户id
},
mutations: {
// 设置WebSocket连接
SET_SOCKET (state, socket) {
state.wsService = socket;
},
SET_USER (state, userId) {
state.userId = userId;
},
},
actions: {
// 初始化WebSocket连接
initSocket ({ commit, dispatch, state, rootState }) {
const userId = rootState.user.userInfo.userId;
if (!userId) {
return Promise.reject(new Error('用户id不存在'))
}
// 判断wsService是否存在。
// 如果存在,判断用户id是否相同,如果相同是否已经连接。
// 如果已连接判断心跳是否开启,如果关闭就开启;如果未连接则创建新连接
if (state.wsService) {
const wsService = state.wsService
const socket = state.wsService.socket;
if (state.userId === userId && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
// CLOSED: 3
// CLOSING: 2
// CONNECTING: 0
// OPEN: 1
if (!wsService.serverTimer) {
wsService.heartStart();
}
} else {
dispatch('startSocket')
return Promise.resolve()
}
} else {
dispatch('startSocket')
return Promise.resolve()
}
},
startSocket ({ commit, dispatch, state, rootState }) {
const userId = rootState.user.userInfo.userId;
if (state.wsService) {
state.wsService.closeWebSocket() // 正常关闭连接
}
// wsService进行初始化。
const wsService = new WebSocketService(
`${protocol === 'https:' ? 'wss' : 'ws'}://${host}//${userId}`)
wsService.initWebSocket()
commit('SET_USER', userId)
commit('SET_SOCKET', wsService)
},
closeSocket ({ commit, dispatch, state }, path) {
if (state.wsService) {
const pageArr = [''] // 需要开启websocket的页面
let needClose = true
pageArr.forEach(item => {
if (path.includes(item)) {
needClose = false
}
})
if (needClose === true) {
state.wsService.closeWebSocket() // 正常关闭连接
}
}
},
},
};