diff --git a/src/api/contract.js b/src/api/contract.js index f72d3e2..c7418e4 100644 --- a/src/api/contract.js +++ b/src/api/contract.js @@ -11,14 +11,14 @@ class Contract { } static getMarketInfo(data) { - return server.get(`/contract/getMarketInfo`, {params:data}) + return server.get(`/contract/getMarketInfo`, {params:data,config:{loading:false}}) } /** * 获取合约市场 */ static getMarketList(data) { - return server.get('/contract/getMarketList', {params:data}) + return server.get('/contract/getMarketList', {params:data,config:{loading:false}}) } /** diff --git a/src/api/server/Socket.js b/src/api/server/Socket.js index 2d82662..7b19002 100644 --- a/src/api/server/Socket.js +++ b/src/api/server/Socket.js @@ -180,7 +180,7 @@ class Socket { constructor(link, ...args) { - // ✅ 修复1:确保 this.link 存的是字符串,方便后面断线重连 + // 修复1:确保 this.link 存的是字符串,方便后面断线重连 if (link.constructor === WebSocket) { this.socket = link; this.link = link.url; @@ -196,7 +196,7 @@ class Socket { this.heartBeatTimer = null; - // ✅ 新增:重连相关的标识符 + // 新增:重连相关的标识符 this.reconnectTimer = null; this.isReconnecting = false; this.manualClose = false; // 判断是否是用户主动退出页面 @@ -244,7 +244,7 @@ class Socket { } for (let item of datas) { - // ✅ 修复2:发送前必须检查状态,彻底解决 "CLOSING or CLOSED state" 报错! + // 修复2:发送前必须检查状态,彻底解决 "CLOSING or CLOSED state" 报错! if (this.socket && this.socket.readyState === 1) { this.socket.send(JSON.stringify(item)); } else { @@ -291,7 +291,7 @@ class Socket { return this.readyState >= 2; } - // ✅ 修复3:核心重连逻辑 + // 修复3:核心重连逻辑 reconnect() { // 如果是手动关闭(退出页面),或者正在重连中,则放弃重连 if (this.manualClose || this.isReconnecting) return; @@ -332,7 +332,7 @@ class Socket { if (!this.manualClose) this.reconnect(); } - // ✅ 修复4:解决内存泄漏,用完立刻注销 + // 修复4:解决内存泄漏,用完立刻注销 emit(data) { return new Promise((resolve) => { this.send(JSON.stringify(data)); diff --git a/src/views/contract/handicap.vue b/src/views/contract/handicap.vue index 91d7431..4ab29e3 100644 --- a/src/views/contract/handicap.vue +++ b/src/views/contract/handicap.vue @@ -140,6 +140,17 @@ export default { buyList: [], tradeList: [], newPriceObj: {}, + // 保存事件回调,防止内存泄漏 + wsOpenHandler: null, + wsMessageHandler: null, + collapseHandler: null, + + // 防止接口无限密集重试的锁 + isRetrying: false, + + lastMessageTime: 0, // 最后一次收到数据的时间戳 + watchdogTimer: null, // 看门狗定时器 + watchdogTimeout: 5000 // 判定假死的时间:5秒(可根据你们后端的推送频率调整) }; }, props: { @@ -197,6 +208,7 @@ export default { if (this.symbol) { this.getMarketInfo(); this.linkSocket(); + this.startWatchdog(); } bus.$on('collapse', msg => { this.newPriceObj.price =this.symbol=='BTC'? (msg.close).toFixed(1):(msg.close).toFixed(3); @@ -215,26 +227,66 @@ export default { }, beforeDestroy() { this.unLink(this.symbol); + this.removeSocketListeners(); + this.stopWatchdog(); }, // 获取盘口 methods: { parseTime: date.parseTime, omitTo: math.omitTo, - getMarketInfo() { - let data = { - symbol: this.symbol - }; - Contract.getMarketInfo(data).then(res => { - this.sellList = res.swapSellList; - this.buyList = res.swapBuyList; - this.tradeList = res.swapTradeList; - // if(this.tradeList.length>0){ - this.newPriceObj = this.tradeList[0]; - // } - this.$emit("input", this.newPriceObj); - localStorage.setItem("price",this.newPriceObj.price) - }); - }, + // getMarketInfo() { + // let data = { + // symbol: this.symbol + // }; + // Contract.getMarketInfo(data).then(res => { + // this.sellList = res.swapSellList; + // this.buyList = res.swapBuyList; + // this.tradeList = res.swapTradeList; + // // if(this.tradeList.length>0){ + // this.newPriceObj = this.tradeList[0]; + // // } + // this.$emit("input", this.newPriceObj); + // localStorage.setItem("price",this.newPriceObj.price) + // }); + // }, + // ✅ 修复:加入 .catch 捕获超时,并自动延迟重试 + getMarketInfo() { + if (!this.symbol) return; + + let data = { symbol: this.symbol }; + + Contract.getMarketInfo(data).then(res => { + this.isRetrying = false; // 成功后解除重试锁 + this.sellList = res.swapSellList; + this.buyList = res.swapBuyList; + this.tradeList = res.swapTradeList; + + if(this.tradeList && this.tradeList.length > 0){ + this.newPriceObj = this.tradeList[0]; + } + this.$emit("input", this.newPriceObj); + localStorage.setItem("price", this.newPriceObj.price); + + }).catch(err => { + // 🔥 捕获超时异常,防止程序崩溃 (Uncaught in promise) + console.error("盘口接口请求失败/超时,3秒后自动重试...", err); + + // 如果没有在重试中,就开启延迟重试 + if (!this.isRetrying) { + this.isRetrying = true; + setTimeout(() => { + this.isRetrying = false; + this.getMarketInfo(); // 重新发起请求 + + // 顺便检查一下 WS,如果断了,通过底层重连 + if (this.ws && !this.ws.checkOpen()) { + console.log("检测到网络较差,尝试重连 WebSocket..."); + this.ws.reconnect(); + } + }, 3000); // 延迟 3 秒重试,避免把服务器打死 + } + }); + }, // 计算深度 getValue(amount) { const arr = this.buyListShow @@ -243,75 +295,180 @@ export default { let max = Math.max(...arr); return math.division(amount, max, 2) * 100; }, + // 封装发送订阅方法 + sendSubscribe() { + if (!this.ws || !this.ws.socket || this.ws.socket.readyState !== 1) return; + this.ws.send({ cmd: "sub", msg: this.msg.buy }); + this.ws.send({ cmd: "sub", msg: this.msg.sell }); + this.ws.send({ cmd: "sub", msg: this.msg.trade }); + }, + + // 清理事件监听器 + removeSocketListeners() { + if (this.wsOpenHandler && this.ws) { + this.ws.off("open", this.wsOpenHandler); + } + if (this.wsMessageHandler && this.ws) { + this.ws.off("message", this.wsMessageHandler); + } + }, + + // 强化版看门狗:直接暴力强杀并重启 WebSocket + startWatchdog() { + this.stopWatchdog(); + this.lastMessageTime = Date.now(); + + this.watchdogTimer = setInterval(() => { + const now = Date.now(); + if (now - this.lastMessageTime > this.watchdogTimeout) { + console.error(`[看门狗报警] 已经 ${this.watchdogTimeout/1000} 秒没收到推送了!判定为后端假死!正在强行掐断并新建 WebSocket...`); + + // 1. 保底先通过 HTTP 请求拉取一次最新数据,防止黑屏 + this.getMarketInfo(); + + // 2. 强行干预底层的 Socket 实例 + if (this.ws) { + // 强制关闭旧的底层的原生连接(必须强制,否则服务器以为你还连着) + if (this.ws.socket) { + this.ws.socket.close(); + } + + // 强制解除底层的重连锁,并调用我们封装好的 reconnect 暴力重启它! + this.ws.isReconnecting = false; + this.ws.manualClose = false; + this.ws.reconnect(); + } + + // 3. 喂一次狗,重置时间,防止在重连的过程中它疯狂重复杀连接 + this.lastMessageTime = Date.now(); + } + }, 5000); // 每 5 秒巡逻检查一次 + }, + + // 🔥 新增:停止看门狗 + stopWatchdog() { + if (this.watchdogTimer) { + clearInterval(this.watchdogTimer); + this.watchdogTimer = null; + } + }, + + // 完善的 WebSocket 绑定与重连机制 + linkSocket() { + if (!this.ws) return; + + this.removeSocketListeners(); + + // 监听 open 事件(无论是初次连接还是断网重连,都会触发这里) + this.wsOpenHandler = () => { + this.sendSubscribe(); + }; + this.ws.on("open", this.wsOpenHandler); + + // 监听消息 + this.wsMessageHandler = res => { + let { data, msg, code, sub, type, status, cmd } = res; + + if (sub == this.msg.buy || sub == this.msg.sell || sub == this.msg.trade) { + this.lastMessageTime = Date.now(); + } + + if (sub == this.msg.buy) { + this.buyList = data; + } else if (sub == this.msg.sell) { + this.sellList = data; + } else if (sub == this.msg.trade) { + this.tradeList.unshift(data); + this.tradeList.pop(); + } else if (type == "ping" || cmd == "ping") { + this.ws.send({ cmd: "pong" }); + } + }; + this.ws.on("message", this.wsMessageHandler); + + // 如果当前已经是连接状态,立刻发送一次 + if (this.ws.socket && this.ws.socket.readyState === 1) { + this.sendSubscribe(); + } + + this.startWatchdog(); // 确保看门狗在跑 + }, + + unLink(symbol) { + if (!this.ws || !this.ws.socket || this.ws.socket.readyState !== 1) return; + this.ws.send({ cmd: "unsub", msg: `swapBuyList_${symbol}` }); + this.ws.send({ cmd: "unsub", msg: `swapSellList_${symbol}` }); + this.ws.send({ cmd: "unsub", msg: `swapTradeList_${symbol}` }); + } // 连接socket - linkSocket() { - if (this.ws.socket.readyState == 1) { - this.ws.send({ - cmd: "sub", - msg: this.msg.buy - }); - this.ws.send({ - cmd: "sub", - msg: this.msg.sell - }); - this.ws.send({ - cmd: "sub", - msg: this.msg.trade - }); - } else { - this.ws.on("open", () => { - this.ws.send({ - cmd: "sub", - msg: this.msg.buy - }); - this.ws.send({ - cmd: "sub", - msg: this.msg.sell - }); - this.ws.send({ - cmd: "sub", - msg: this.msg.trade - }); - }); - } - this.ws.on("message", res => { - let { data, msg, code, sub, type, status, cmd } = res; - if (sub == this.msg.buy) { - this.buyList = data; - } else if (sub == this.msg.sell) { - this.sellList = data; - } else if (sub == this.msg.trade) { - // console.log('if (sub == this.msg.trade)', this.msg.trade) - this.tradeList.unshift(data); - this.tradeList.pop(); - // this.newPriceObj = data; - // this.$emit("input", this.newPriceObj); - } else if (type == "ping" ||cmd == "ping") { - this.ws.send({ - cmd: "pong" - }); - } - }); + // linkSocket() { + // if (this.ws.socket.readyState == 1) { + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.buy + // }); + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.sell + // }); + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.trade + // }); + // } else { + // this.ws.on("open", () => { + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.buy + // }); + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.sell + // }); + // this.ws.send({ + // cmd: "sub", + // msg: this.msg.trade + // }); + // }); + // } + // this.ws.on("message", res => { + // let { data, msg, code, sub, type, status, cmd } = res; + // if (sub == this.msg.buy) { + // this.buyList = data; + // } else if (sub == this.msg.sell) { + // this.sellList = data; + // } else if (sub == this.msg.trade) { + // // console.log('if (sub == this.msg.trade)', this.msg.trade) + // this.tradeList.unshift(data); + // this.tradeList.pop(); + // // this.newPriceObj = data; + // // this.$emit("input", this.newPriceObj); + // } else if (type == "ping" ||cmd == "ping") { + // this.ws.send({ + // cmd: "pong" + // }); + // } + // }); - }, - // 取消订阅 - unLink(symbol) { - // 取消买线 - this.ws.send({ - cmd: "unsub", - msg: `swapBuyList_${symbol}` - }); - // 取消卖线 - this.ws.send({ - cmd: "unsub", - msg: `swapSellList_${symbol}` - }); - // 取消成交 - this.ws.send({ - cmd: "unsub", - msg: `swapTradeList_${symbol}` - }); - } + // }, + // // 取消订阅 + // unLink(symbol) { + // // 取消买线 + // this.ws.send({ + // cmd: "unsub", + // msg: `swapBuyList_${symbol}` + // }); + // // 取消卖线 + // this.ws.send({ + // cmd: "unsub", + // msg: `swapSellList_${symbol}` + // }); + // // 取消成交 + // this.ws.send({ + // cmd: "unsub", + // msg: `swapTradeList_${symbol}` + // }); + // } } }; \ No newline at end of file diff --git a/src/views/contract/index.vue b/src/views/contract/index.vue index 4046a9b..025bde8 100644 --- a/src/views/contract/index.vue +++ b/src/views/contract/index.vue @@ -164,6 +164,244 @@