feat(websocket): 重构 WebSocket 连接逻辑并增加心跳机制

- 替换原有 SignalR 连接方式为原生 WebSocket 实现
- 增加心跳检测机制确保连接稳定性
- 实现自动重连功能防止意外断开
- 优化消息处理逻辑与原有功能兼容
This commit is contained in:
yangzhe 2025-12-05 10:49:53 +08:00
parent a8a3f9d312
commit a4d4968fc5
1 changed files with 163 additions and 91 deletions

254
App.vue
View File

@ -7,6 +7,13 @@ export default {
data() {
return {
show: false,
// WebSocket
ws: null, // WebSocket
lockReconnect: false, //
timeout: 30000, //
timeoutObj: null, //
serverTimeoutObj: null, //
timeoutnum: null, //
};
},
globalData: {},
@ -161,103 +168,168 @@ export default {
return false;
}
},
charLink() {
var that = this;
(function start(ms) {
setTimeout(() => {
try {
that.$connection
.start()
.then(() => {
// console.log(that.$connection.connection.transport.webSocket._webSocket.url)
// that.connectionId = that.$connection.connection.transport.url.split(
// "=")[1]
that.connectionId =
that.$connection.connection.transport.webSocket._webSocket.url.split(
"="
)[1];
that.CharLogin();
})
.catch((e) => {
//
that.$connection.sto;
console.log("websocket连接失败", e);
return start(5000);
});
} catch (e) {
//
return start(5000);
}
}, ms);
})(0);
//
this.$connection.on("ReceiveMessage", (user, message, type) => {
if (this._route.fullPath.indexOf("dialogBox") < 0) {
var tab = this.vuex_tabbar;
tab[1].isDot = true;
this.$u.vuex("vuex_tabbar", tab);
var msgList = this.vuex_tabbar;
var msgList = this.vuex_msgList;
//
if (type == 4) {
user = "admin";
}
if (type == 3) {
user += "user";
}
if (msgList.indexOf(user) < 0) {
msgList += user + ",";
}
this.$u.vuex("vuex_msgList", msgList);
}
});
//
this.$connection.on("SystemMessage", (title, content, time) => {
if (this._route.fullPath.indexOf("sysList") < 0) {
var tab = this.vuex_tabbar;
tab[1].isDot = true;
this.$u.vuex("vuex_tabbar", tab);
var msgList = this.vuex_tabbar;
var msgList = this.vuex_msgList;
if (msgList.indexOf("SystemMessage") < 0) {
msgList += "SystemMessage,";
}
this.$u.vuex("vuex_msgList", msgList);
}
});
//
this.$connection.on("InteractMessage", (data, type) => {
if (this._route.fullPath.indexOf("interactionList") < 0) {
var tab = this.vuex_tabbar;
tab[1].isDot = true;
this.$u.vuex("vuex_tabbar", tab);
var msgList = this.vuex_tabbar;
var msgList = this.vuex_msgList;
if (msgList.indexOf("InteractMessage") < 0) {
msgList += "InteractMessage,";
}
this.$u.vuex("vuex_msgList", msgList);
}
});
// WebSocket oa-web-phone
buildWsUrl() {
const protocol =
window.location.protocol.indexOf("https") === 0 ? "wss" : "ws";
const host = this.$u.http.config.baseUrl; //
const userId =
(this.vuex_user && (this.vuex_user.id || this.vuex_user.Id)) || "";
return `${protocol}://${host}/api/Dialogue/HandleConnection?Id=${userId}`; // &equipmentType=0
},
CharLogin() {
//connectionId
if (this.vuex_user.id) {
this.$connection
.invoke("login", this.connectionId, this.vuex_user.id)
.then((res) => {})
.catch((err) => {});
//
// this.getLocation()
// WebSocket
initWebSocket() {
console.log(this.buildWsUrl());
try {
this.ws = new WebSocket(this.buildWsUrl());
this.ws.onopen = () => this.handleWsOpen();
this.ws.onmessage = (e) => this.handleWsMessage(e);
this.ws.onclose = (e) => this.handleWsClose(e);
this.ws.onerror = (e) => this.handleWsError(e);
} catch (err) {
console.log("[WebSocket] 创建连接失败:", err);
this.reconnect();
}
},
//
reconnect() {
if (this.lockReconnect) return;
this.timeoutnum && clearTimeout(this.timeoutnum);
this.timeoutnum = setTimeout(() => {
this.startLink();
}, 5000);
},
//
reset() {
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.start();
},
//
start() {
this.timeoutObj = setTimeout(() => {
try {
if (
this.ws &&
this.ws.readyState === 1 &&
this.vuex_user &&
(this.vuex_user.id || this.vuex_user.Id)
) {
this.ws.send("heartCheck");
} else {
this.lockReconnect = false;
this.reconnect();
}
} catch (err) {
this.lockReconnect = false;
this.reconnect();
}
//
this.serverTimeoutObj = setTimeout(() => {
try {
this.ws && this.ws.close();
} catch (e) {}
this.lockReconnect = false;
this.reconnect();
}, this.timeout);
}, this.timeout);
},
//
handleWsOpen() {
this.lockReconnect = true;
//
// this.$u.vuex('vuex_websocket', this.ws)
this.reset();
},
//
handleWsMessage(e) {
//
this.reset();
//
if (typeof e.data === "string" && e.data.indexOf("heartCheck") >= 0) {
return;
}
// JSON oa-web-phone { Type, Data }
let msgData = null;
try {
msgData = JSON.parse(e.data);
} catch (err) {
console.log("[WebSocket] 消息解析失败:", err, e.data);
return;
}
const type = msgData.Type;
// 退
if (type === "Exit") {
try {
this.ws && this.ws.close();
} catch (err) {}
//
this.$u.vuex("vuex_user", "");
this.$u.vuex("vuex_token", "");
uni.clearStorage();
const typeNum = this.vuex_userType || 0; // 0: 1:
uni.reLaunch({ url: `/pages/login/login/index?type=${typeNum}` });
return;
}
// TabBar
const tab = this.vuex_tabbar || [];
if (Array.isArray(tab) && tab[1]) {
tab[1].isDot = true;
this.$u.vuex("vuex_tabbar", tab);
}
// 使 key
let msgList = this.vuex_msgList || "";
if (type === "ChatMessageDto") {
const data = msgData.Data || {};
const msgUserId = data.ChatType === 1 ? data?.ToId : data?.UserId;
const key = `chat_${msgUserId || "unknown"}`;
if (msgList.indexOf(key) < 0) {
msgList += key + ",";
this.$u.vuex("vuex_msgList", msgList);
}
} else {
setTimeout(() => {
this.CharLogin();
}, 1000);
if (msgList.indexOf("WebSocketMessage") < 0) {
msgList += "WebSocketMessage,";
this.$u.vuex("vuex_msgList", msgList);
}
}
},
//
handleWsClose(e) {
this.lockReconnect = false;
this.reconnect();
},
//
handleWsError(e) {
this.lockReconnect = false;
this.reconnect();
},
//
startLink() {
if (this.lockReconnect) return;
const hasUser =
this.vuex_user && (this.vuex_user.id || this.vuex_user.Id);
const hasToken =
!!this.vuex_token || !!window.localStorage.getItem("token");
if (hasUser && hasToken) {
this.initWebSocket();
} else {
this.lockReconnect = false;
this.reconnect();
}
},
},
mounted() {
this.charLink();
// 使 oa-web-phone WebSocket
this.startLink();
},
};
</script>