feat(websocket): 重构 WebSocket 连接逻辑并增加心跳机制
- 替换原有 SignalR 连接方式为原生 WebSocket 实现 - 增加心跳检测机制确保连接稳定性 - 实现自动重连功能防止意外断开 - 优化消息处理逻辑与原有功能兼容
This commit is contained in:
parent
a8a3f9d312
commit
a4d4968fc5
254
App.vue
254
App.vue
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue