手撸编辑器(三)
发表于:2023-06-19 |

前言

既然聊天系统都讲到这里了,索性再讲一下,聊天实现的核心逻辑,websocket好了

websocket

首先,我们得知道,这是个啥,我的理解就是长连接,我们可以通过这个发送消息,接收消息,当然,为了保持这个状态,我们需要做心跳检测,因此就有了如下代码,核心部分我都会注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { useStore } from "@/store/index";
import { postMessageToParent } from "./common";
let webSocket: any = null;
let timeout: any = null;
let reTimes = 0;
export let socketOpen: boolean;

const store = useStore();

// 其他系统嵌入聊天系统时,输出websocket事件信息
export const outputWebSocket = (data: any) => {
console.log(data);
postMessageToParent(data);
//@ts-ignore
if (typeof window.webFunctionHandler == "function") {
//@ts-ignore
webFunctionHandler(data);
}
};

// websocket状态监听
export const contactSocket = () => {
webSocket.onopen = function () {
heartCheck.reset().start(); //心跳检测重置
const outputStr = JSON.stringify({ type: "onOpen", data: null });
outputWebSocket(outputStr);
store.changeWebSocketStatus(true);
ElMessage({
type: "success",
message: "连接成功",
});
};
webSocket.onmessage = function (evt: { data: any }) {
heartCheck.reset().start(); //心跳检测重置
const received_msg = evt.data;
// 非心跳检测的加入新消息队列
if (!received_msg.includes("HEARTBEAT")) {
const parseData = JSON.parse(evt.data);
// 排除websocket返回自己的消息
if (store.openId !== parseData.sendOpenId) {
const outputStr = JSON.stringify({
type: "onMessage",
data: JSON.parse(received_msg),
});
store.changeNewMsg(parseData);
if (parseData.msgContent === "rocket-[抖动]") {
postMessageToParent("shakeOnce");
} else {
outputWebSocket(outputStr);
}
}
// 新建的房间
if (!store.curListId.includes(parseData.roomId)) {
store.changeNewMsg({ ...parseData, messageTypeNote: "newRoom" });
}
}
};
webSocket.onclose = function () {
const outputStr = JSON.stringify({ type: "onClose", data: null });
outputWebSocket(outputStr);
ElMessage({
type: "info",
message: "连接断开,重连中,请耐心等待...",
});
reconnect();
};
webSocket.onerror = function () {
const outputStr = JSON.stringify({ type: "onError", data: null });
outputWebSocket(outputStr);
ElMessage({
type: "info",
message: "连接异常,重连中,请耐心等待...",
});
reconnect();
};
};

// 初始化websocket
export const initWebSocket = () => {
const url = `wss://rocket-chat.nbhuojian.com/webSocket/101/web/${store.openId}`;
if ("WebSocket" in window) {
webSocket = new WebSocket(url);
}
contactSocket();
};

// 重连websocket
const reconnect = () => {
// 避免重复请求
clearTimeout(timeout);
if (socketOpen) {
reTimes = 0;
return;
}
reTimes++;
if (reTimes > 3) {
ElMessage({
type: "error",
message: "连接已断开,请联系管理员",
});
store.changeWebSocketStatus(false);
outputWebSocket("重连失败");
return;
}
socketOpen = true;
// 延迟2秒重连 避免过多次过频繁请求重连
timeout = setTimeout(function () {
initWebSocket();
socketOpen = false;
}, 2000);
};
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
// 下载文件避免手动断开连接
const store = useStore();
if (store.isDownLoad) {
store.changeIsDownLoad(false);
return;
}
webSocket.close();
};

//心跳检测
const heartCheck = {
timeout: 3000, //3s发一次心跳
timeoutObj: null,
serverTimeoutObj: null,
reset: function () {
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
this.timeoutObj = setTimeout(() => {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
const pingStr = JSON.stringify({
msgType: "HEARTBEAT",
});
if (webSocket.readyState === 1) {
webSocket.send(pingStr);
}
this.serverTimeoutObj = setTimeout(function () {
// 弹出连接断开,自动重连弹框
store.changeWebSocketStatus(false);
outputWebSocket("重连失败");
//如果超过一定时间还没重置,说明后端主动断开了
webSocket.close(); //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, this.timeout) as any;
}, this.timeout) as any;
},
};

上一篇:
【可视化学习】24-使用vue和react初始化项目
下一篇:
手撸编辑器(二)