2025-02-06 11:14:33 +08:00
|
|
|
|
package excservice
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"go-admin/common/global"
|
|
|
|
|
|
"go-admin/common/helper"
|
|
|
|
|
|
"go-admin/models/binancedto"
|
|
|
|
|
|
"go-admin/models/commondto"
|
|
|
|
|
|
"go-admin/pkg/jsonhelper"
|
|
|
|
|
|
"go-admin/pkg/utility"
|
2025-10-14 19:58:59 +08:00
|
|
|
|
"go-admin/services/proxy"
|
2025-02-06 11:14:33 +08:00
|
|
|
|
"math/rand"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
2025-09-19 15:42:51 +08:00
|
|
|
|
"sync/atomic"
|
2025-02-06 11:14:33 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/bytedance/sonic"
|
|
|
|
|
|
log "github.com/go-admin-team/go-admin-core/logger"
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type BinanceWebSocketManager struct {
|
2025-10-14 19:58:59 +08:00
|
|
|
|
mu sync.Mutex
|
|
|
|
|
|
stopOnce sync.Once // 新增,确保Stop只执行一次
|
|
|
|
|
|
ws *websocket.Conn
|
|
|
|
|
|
stopChannel chan struct{}
|
|
|
|
|
|
url string
|
|
|
|
|
|
wsType int
|
|
|
|
|
|
apiKey string
|
|
|
|
|
|
apiSecret string
|
|
|
|
|
|
proxyType string
|
|
|
|
|
|
proxyAddress string
|
|
|
|
|
|
reconnect chan struct{}
|
|
|
|
|
|
isStopped bool
|
|
|
|
|
|
cancelFunc context.CancelFunc
|
|
|
|
|
|
listenKey string
|
|
|
|
|
|
reconnecting atomic.Bool
|
|
|
|
|
|
ConnectTime time.Time
|
|
|
|
|
|
lastPingTime atomic.Value
|
|
|
|
|
|
forceReconnectTimer *time.Timer
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ... (省略中间代码)
|
|
|
|
|
|
|
|
|
|
|
|
// sendPing 使用锁来确保发送ping时的线程安全
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) sendPing() error {
|
|
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
defer wm.mu.Unlock()
|
|
|
|
|
|
if wm.ws == nil {
|
|
|
|
|
|
return errors.New("websocket connection is nil")
|
|
|
|
|
|
}
|
|
|
|
|
|
wm.lastPingTime.Store(time.Now())
|
|
|
|
|
|
return wm.ws.WriteMessage(websocket.PingMessage, []byte("ping"))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stop 优雅地停止WebSocket管理器
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) Stop() {
|
|
|
|
|
|
wm.stopOnce.Do(func() {
|
|
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
wm.mu.Unlock()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
wm.isStopped = true
|
|
|
|
|
|
wm.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
if wm.cancelFunc != nil {
|
|
|
|
|
|
wm.cancelFunc()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wm.closeConn() // 统一调用带锁的关闭方法
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试关闭stopChannel,如果已经关闭则忽略
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-wm.stopChannel:
|
|
|
|
|
|
default:
|
|
|
|
|
|
close(wm.stopChannel)
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Infof("WebSocket 已完全停止 key:%s", wm.apiKey)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handleReconnect 处理重连逻辑
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) {
|
|
|
|
|
|
const maxRetries = 10
|
|
|
|
|
|
backoff := time.Second
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-wm.reconnect:
|
|
|
|
|
|
if !wm.reconnecting.CompareAndSwap(false, true) {
|
|
|
|
|
|
continue // 如果已经在重连,则忽略
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在开始重连循环之前,先安全地关闭旧的连接
|
|
|
|
|
|
wm.closeConn()
|
|
|
|
|
|
|
|
|
|
|
|
retryCount := 0
|
|
|
|
|
|
for retryCount < maxRetries {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-wm.stopChannel:
|
|
|
|
|
|
wm.reconnecting.Store(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
default:
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Infof("WebSocket (%s) 正在尝试重连,第 %d 次...", wm.wsType, retryCount+1)
|
|
|
|
|
|
if err := wm.connect(ctx); err == nil {
|
|
|
|
|
|
log.Infof("WebSocket (%s) 重连成功", wm.wsType)
|
|
|
|
|
|
wm.reconnecting.Store(false)
|
|
|
|
|
|
go wm.startDeadCheck(ctx) // 重连成功后,重新启动假死检测
|
|
|
|
|
|
break // 跳出重连循环
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
retryCount++
|
|
|
|
|
|
time.Sleep(backoff)
|
|
|
|
|
|
backoff *= 2
|
|
|
|
|
|
if backoff > time.Minute {
|
|
|
|
|
|
backoff = time.Minute
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if retryCount >= maxRetries {
|
|
|
|
|
|
log.Errorf("WebSocket (%s) 重连失败次数过多,停止重连", wm.wsType)
|
|
|
|
|
|
wm.Stop()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
wm.reconnecting.Store(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 已有连接
|
|
|
|
|
|
var SpotSockets = map[string]*BinanceWebSocketManager{}
|
|
|
|
|
|
var FutureSockets = map[string]*BinanceWebSocketManager{}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 创建新的Binance WebSocket管理器
|
|
|
|
|
|
* @param wsType WebSocket类型:0-现货,1-合约
|
|
|
|
|
|
* @param apiKey API密钥
|
|
|
|
|
|
* @param apiSecret API密钥
|
|
|
|
|
|
* @param proxyType 代理类型
|
|
|
|
|
|
* @param proxyAddress 代理地址
|
|
|
|
|
|
* @return WebSocket管理器实例
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func NewBinanceWebSocketManager(wsType int, apiKey, apiSecret, proxyType, proxyAddress string) *BinanceWebSocketManager {
|
|
|
|
|
|
url := ""
|
|
|
|
|
|
|
|
|
|
|
|
switch wsType {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
url = "wss://stream.binance.com:9443/ws"
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
url = "wss://fstream.binance.com/ws"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
wm := &BinanceWebSocketManager{
|
2025-02-06 11:14:33 +08:00
|
|
|
|
stopChannel: make(chan struct{}, 10),
|
|
|
|
|
|
reconnect: make(chan struct{}, 10),
|
|
|
|
|
|
isStopped: false,
|
|
|
|
|
|
url: url,
|
|
|
|
|
|
wsType: wsType,
|
|
|
|
|
|
apiKey: apiKey,
|
|
|
|
|
|
apiSecret: apiSecret,
|
|
|
|
|
|
proxyType: proxyType,
|
|
|
|
|
|
proxyAddress: proxyAddress,
|
|
|
|
|
|
}
|
2025-10-14 19:58:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化最后ping时间
|
|
|
|
|
|
wm.lastPingTime.Store(time.Now())
|
|
|
|
|
|
|
|
|
|
|
|
return wm
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取API密钥
|
|
|
|
|
|
* @return API密钥
|
|
|
|
|
|
*/
|
2025-09-19 15:42:51 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) GetKey() string {
|
|
|
|
|
|
return wm.apiKey
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 启动WebSocket连接
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) Start() {
|
|
|
|
|
|
utility.SafeGo(wm.run)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 重启连接,更新配置参数
|
|
|
|
|
|
* @param apiKey 新的API密钥
|
|
|
|
|
|
* @param apiSecret 新的API密钥
|
|
|
|
|
|
* @param proxyType 新的代理类型
|
|
|
|
|
|
* @param proxyAddress 新的代理地址
|
|
|
|
|
|
* @return WebSocket管理器实例
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) Restart(apiKey, apiSecret, proxyType, proxyAddress string) *BinanceWebSocketManager {
|
2025-02-20 20:19:49 +08:00
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
defer wm.mu.Unlock()
|
|
|
|
|
|
|
2025-02-06 11:14:33 +08:00
|
|
|
|
wm.apiKey = apiKey
|
|
|
|
|
|
wm.apiSecret = apiSecret
|
|
|
|
|
|
wm.proxyType = proxyType
|
|
|
|
|
|
wm.proxyAddress = proxyAddress
|
|
|
|
|
|
|
2025-02-14 09:43:49 +08:00
|
|
|
|
if wm.isStopped {
|
2025-02-20 20:19:49 +08:00
|
|
|
|
wm.isStopped = false
|
|
|
|
|
|
utility.SafeGo(wm.run)
|
2025-02-14 09:43:49 +08:00
|
|
|
|
} else {
|
2025-09-19 15:42:51 +08:00
|
|
|
|
log.Warnf("调用restart")
|
|
|
|
|
|
wm.triggerReconnect(true)
|
2025-02-14 09:43:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-02-06 11:14:33 +08:00
|
|
|
|
return wm
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 触发重连机制
|
|
|
|
|
|
* @param force 是否强制重连
|
|
|
|
|
|
*/
|
2025-09-19 15:42:51 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) triggerReconnect(force bool) {
|
|
|
|
|
|
if force {
|
|
|
|
|
|
wm.reconnecting.Store(false) // 强制重置标志位
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if wm.reconnecting.CompareAndSwap(false, true) {
|
|
|
|
|
|
log.Warnf("准备重连 key: %s wsType: %v", wm.apiKey, wm.wsType)
|
|
|
|
|
|
// 发送信号触发重连协程
|
|
|
|
|
|
select {
|
|
|
|
|
|
case wm.reconnect <- struct{}{}:
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 防止阻塞,如果通道满了就跳过
|
|
|
|
|
|
log.Debugf("reconnect 信号已存在,跳过 key:%s", wm.apiKey)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-14 19:58:59 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 外部重启函数
|
|
|
|
|
|
* @param wm WebSocket管理器实例
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func Restart(wm *BinanceWebSocketManager) {
|
2025-09-19 15:42:51 +08:00
|
|
|
|
log.Warnf("调用restart")
|
|
|
|
|
|
wm.triggerReconnect(true)
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
2025-10-14 19:58:59 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 主运行循环
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) run() {
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
wm.cancelFunc = cancel
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// utility.SafeGo(wm.handleSignal)
|
2025-02-06 11:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算错误记录键
|
|
|
|
|
|
errKey := fmt.Sprintf(global.API_WEBSOCKET_ERR, wm.apiKey)
|
|
|
|
|
|
errMessage := commondto.WebSocketErr{Time: time.Now()}
|
|
|
|
|
|
helper.DefaultRedis.SetString(errKey, jsonhelper.ToJsonString(errMessage))
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
default:
|
|
|
|
|
|
if err := wm.connect(ctx); err != nil {
|
|
|
|
|
|
wm.handleConnectionError(errKey, err)
|
|
|
|
|
|
|
|
|
|
|
|
if wm.isErrorCountExceeded(errKey) {
|
|
|
|
|
|
log.Error("连接 %s WebSocket 时出错次数过多,停止 WebSocket 管理器: %v", wm.wsType, wm.apiKey)
|
|
|
|
|
|
wm.Stop()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
<-wm.stopChannel
|
|
|
|
|
|
log.Info("停止 %s WebSocket 管理器...", getWsTypeName(wm.wsType))
|
|
|
|
|
|
wm.Stop()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理WebSocket连接错误
|
|
|
|
|
|
* @param errKey Redis错误记录键
|
|
|
|
|
|
* @param err 错误信息
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) handleConnectionError(errKey string, err error) {
|
|
|
|
|
|
// 从 Redis 获取错误记录
|
|
|
|
|
|
var errMessage commondto.WebSocketErr
|
|
|
|
|
|
val, _ := helper.DefaultRedis.GetString(errKey)
|
|
|
|
|
|
if val != "" {
|
|
|
|
|
|
sonic.UnmarshalString(val, &errMessage)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新错误记录
|
|
|
|
|
|
errMessage.Count++
|
|
|
|
|
|
errMessage.Time = time.Now()
|
|
|
|
|
|
errMessage.ErrorMessage = err.Error()
|
|
|
|
|
|
|
|
|
|
|
|
// 将错误记录保存到 Redis
|
|
|
|
|
|
if data, err := sonic.MarshalString(errMessage); err == nil {
|
|
|
|
|
|
helper.DefaultRedis.SetString(errKey, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录错误日志
|
|
|
|
|
|
log.Error("连接 %s WebSocket 时出错: %v, 错误: %v", wm.wsType, wm.apiKey, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 检查错误次数是否超过阈值
|
|
|
|
|
|
* @param errKey Redis错误记录键
|
|
|
|
|
|
* @return 是否超过阈值
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) isErrorCountExceeded(errKey string) bool {
|
|
|
|
|
|
val, _ := helper.DefaultRedis.GetString(errKey)
|
|
|
|
|
|
if val == "" {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var errMessage commondto.WebSocketErr
|
|
|
|
|
|
if err := sonic.UnmarshalString(val, &errMessage); err != nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return errMessage.Count >= 5
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 建立WebSocket连接
|
|
|
|
|
|
* @param ctx 上下文
|
|
|
|
|
|
* @return 错误信息
|
|
|
|
|
|
*/
|
2025-02-06 11:14:33 +08:00
|
|
|
|
func (wm *BinanceWebSocketManager) connect(ctx context.Context) error {
|
2025-10-14 19:58:59 +08:00
|
|
|
|
dialer, err := proxy.GetDialer(wm.proxyType, wm.proxyAddress)
|
2025-02-06 11:14:33 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
listenKey, err := wm.getListenKey()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wm.listenKey = listenKey
|
|
|
|
|
|
url := fmt.Sprintf("%s/%s", wm.url, listenKey)
|
|
|
|
|
|
wm.ws, _, err = dialer.Dial(url, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// 连接成功,更新连接时间和ping时间
|
2025-09-19 15:42:51 +08:00
|
|
|
|
wm.ConnectTime = time.Now()
|
2025-10-14 19:58:59 +08:00
|
|
|
|
wm.lastPingTime.Store(time.Now())
|
2025-02-06 11:14:33 +08:00
|
|
|
|
log.Info(fmt.Sprintf("已连接到 Binance %s WebSocket【%s】 key:%s", getWsTypeName(wm.wsType), wm.apiKey, listenKey))
|
|
|
|
|
|
|
|
|
|
|
|
// Ping处理
|
|
|
|
|
|
wm.ws.SetPingHandler(func(appData string) error {
|
|
|
|
|
|
log.Info(fmt.Sprintf("收到 wstype: %v key:%s Ping 消息【%s】", wm.wsType, wm.apiKey, appData))
|
|
|
|
|
|
|
|
|
|
|
|
for x := 0; x < 5; x++ {
|
|
|
|
|
|
if err := wm.ws.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second*10)); err != nil {
|
|
|
|
|
|
log.Error("binance 回应pong失败 次数:", strconv.Itoa(x), " err:", err)
|
|
|
|
|
|
time.Sleep(time.Second * 1)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// 更新ping时间和Redis记录
|
|
|
|
|
|
wm.lastPingTime.Store(time.Now())
|
2025-02-06 11:14:33 +08:00
|
|
|
|
setLastTime(wm)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// 启动各种协程
|
2025-02-06 11:14:33 +08:00
|
|
|
|
utility.SafeGo(func() { wm.startListenKeyRenewal2(ctx) })
|
|
|
|
|
|
utility.SafeGo(func() { wm.readMessages(ctx) })
|
|
|
|
|
|
utility.SafeGo(func() { wm.handleReconnect(ctx) })
|
2025-09-19 15:42:51 +08:00
|
|
|
|
utility.SafeGo(func() { wm.startPingLoop(ctx) })
|
2025-10-14 19:58:59 +08:00
|
|
|
|
utility.SafeGo(func() { wm.startDeadCheck(ctx) }) // 连接成功后立即启动假死检测
|
|
|
|
|
|
utility.SafeGo(func() { wm.start24HourReconnectTimer(ctx) }) // 启动24小时强制重连
|
2025-09-19 15:42:51 +08:00
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ReplaceConnection 创建新连接并关闭旧连接,实现无缝连接替换
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) ReplaceConnection() error {
|
|
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
wm.mu.Unlock()
|
|
|
|
|
|
return errors.New("WebSocket 已停止")
|
|
|
|
|
|
}
|
|
|
|
|
|
oldCtxCancel := wm.cancelFunc
|
|
|
|
|
|
oldConn := wm.ws
|
|
|
|
|
|
wm.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
log.Infof("🔄 正在替换连接: %s", wm.apiKey)
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤 1:先获取新的 listenKey 和连接
|
|
|
|
|
|
newListenKey, err := wm.getListenKey()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取新 listenKey 失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
dialer, err := proxy.GetDialer(wm.proxyType, wm.proxyAddress)
|
2025-09-19 15:42:51 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
newURL := fmt.Sprintf("%s/%s", wm.url, newListenKey)
|
|
|
|
|
|
newConn, _, err := dialer.Dial(newURL, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("新连接 Dial 失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤 2:创建新的上下文并启动协程
|
|
|
|
|
|
newCtx, newCancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
|
|
// 设置 ping handler
|
|
|
|
|
|
newConn.SetPingHandler(func(appData string) error {
|
|
|
|
|
|
log.Infof("收到 Ping(新连接) key:%s msg:%s", wm.apiKey, appData)
|
|
|
|
|
|
for x := 0; x < 5; x++ {
|
|
|
|
|
|
if err := newConn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(10*time.Second)); err != nil {
|
|
|
|
|
|
log.Errorf("Pong 失败 %d 次 err:%v", x, err)
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
setLastTime(wm)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤 3:安全切换连接
|
|
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
wm.ws = newConn
|
|
|
|
|
|
wm.listenKey = newListenKey
|
|
|
|
|
|
wm.ConnectTime = time.Now()
|
|
|
|
|
|
wm.cancelFunc = newCancel
|
|
|
|
|
|
wm.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
log.Infof("✅ 替换连接成功: %s listenKey: %s", wm.apiKey, newListenKey)
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤 4:启动新连接协程
|
|
|
|
|
|
go wm.startListenKeyRenewal2(newCtx)
|
|
|
|
|
|
go wm.readMessages(newCtx)
|
|
|
|
|
|
go wm.handleReconnect(newCtx)
|
|
|
|
|
|
go wm.startPingLoop(newCtx)
|
|
|
|
|
|
// go wm.startDeadCheck(newCtx)
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤 5:关闭旧连接、取消旧协程
|
|
|
|
|
|
if oldCtxCancel != nil {
|
|
|
|
|
|
oldCtxCancel()
|
|
|
|
|
|
}
|
|
|
|
|
|
if oldConn != nil {
|
|
|
|
|
|
_ = oldConn.Close()
|
|
|
|
|
|
log.Infof("🔒 旧连接已关闭: %s", wm.apiKey)
|
|
|
|
|
|
}
|
2025-02-06 11:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新最后通信时间
|
|
|
|
|
|
func setLastTime(wm *BinanceWebSocketManager) {
|
|
|
|
|
|
subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey)
|
|
|
|
|
|
val, _ := helper.DefaultRedis.GetString(subKey)
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
var data binancedto.UserSubscribeState
|
|
|
|
|
|
if val != "" {
|
|
|
|
|
|
sonic.Unmarshal([]byte(val), &data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if wm.wsType == 0 {
|
|
|
|
|
|
data.SpotLastTime = &now
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.FuturesLastTime = &now
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val, _ = sonic.MarshalString(&data)
|
|
|
|
|
|
|
|
|
|
|
|
if val != "" {
|
|
|
|
|
|
helper.DefaultRedis.SetString(subKey, val)
|
|
|
|
|
|
}
|
2025-09-19 15:42:51 +08:00
|
|
|
|
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复用创建HTTP客户端的逻辑
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) createBinanceClient() (*helper.BinanceClient, error) {
|
|
|
|
|
|
return helper.NewBinanceClient(wm.apiKey, wm.apiSecret, wm.proxyType, wm.proxyAddress)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取listenKey
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) getListenKey() (string, error) {
|
|
|
|
|
|
client, err := wm.createBinanceClient()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var resp []byte
|
|
|
|
|
|
switch wm.wsType {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
resp, _, err = client.SendSpotRequestByKey("/api/v3/userDataStream", "POST", nil)
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "POST", nil)
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
log.Error("链接类型错误")
|
|
|
|
|
|
return "", errors.New("链接类型错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var dataMap map[string]interface{}
|
|
|
|
|
|
if err := sonic.Unmarshal(resp, &dataMap); err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if listenKey, ok := dataMap["listenKey"]; ok {
|
|
|
|
|
|
return listenKey.(string), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return "", errors.New("listenKey 不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 接收消息
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) {
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
default:
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, msg, err := wm.ws.ReadMessage()
|
2025-10-14 19:58:59 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 检查是否是由于我们主动关闭连接导致的错误
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
log.Infof("WebSocket read loop gracefully stopped for key: %s", wm.apiKey)
|
|
|
|
|
|
return
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// 如果不是主动关闭,再判断是否是连接关闭错误,并触发重连
|
|
|
|
|
|
if strings.Contains(err.Error(), "websocket: close") {
|
|
|
|
|
|
log.Error("WebSocket connection closed unexpectedly, triggering reconnect for key: %s. Error: %v", wm.apiKey, err)
|
|
|
|
|
|
wm.triggerReconnect(false)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Error("Error reading message for key: %s. Error: %v", wm.apiKey, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return // 任何错误发生后都应退出读取循环
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
wm.handleOrderUpdate(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) {
|
|
|
|
|
|
setLastTime(wm)
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
if reconnect, fatal, err := ReceiveListen(msg, wm.wsType, wm.apiKey); err != nil {
|
|
|
|
|
|
log.Errorf("处理消息时出错: %v", err)
|
|
|
|
|
|
// 根据错误类型决定是否停止
|
|
|
|
|
|
if fatal {
|
|
|
|
|
|
log.Errorf("收到致命错误,将停止 WebSocket 管理器 for key: %s", wm.apiKey)
|
|
|
|
|
|
wm.Stop()
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
} else if fatal {
|
|
|
|
|
|
log.Errorf("收到致命错误,将停止 WebSocket 管理器 for key: %s", wm.apiKey)
|
|
|
|
|
|
wm.Stop()
|
|
|
|
|
|
return
|
|
|
|
|
|
} else if reconnect {
|
|
|
|
|
|
log.Warnf("收到重连请求,将触发重连 for key: %s", wm.apiKey)
|
2025-09-19 15:42:51 +08:00
|
|
|
|
wm.triggerReconnect(false)
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 19:58:59 +08:00
|
|
|
|
// closeConn 安全地关闭WebSocket连接
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) closeConn() {
|
2025-02-06 11:14:33 +08:00
|
|
|
|
wm.mu.Lock()
|
|
|
|
|
|
defer wm.mu.Unlock()
|
|
|
|
|
|
if wm.ws != nil {
|
2025-10-14 19:58:59 +08:00
|
|
|
|
_ = wm.ws.Close()
|
2025-09-19 15:42:51 +08:00
|
|
|
|
wm.ws = nil
|
2025-02-06 11:14:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 15:42:51 +08:00
|
|
|
|
// 主动心跳发送机制
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) startPingLoop(ctx context.Context) {
|
|
|
|
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-10-14 19:58:59 +08:00
|
|
|
|
if err := wm.sendPing(); err != nil {
|
2025-09-19 15:42:51 +08:00
|
|
|
|
log.Error("主动 Ping Binance 失败:", err)
|
|
|
|
|
|
wm.triggerReconnect(false)
|
2025-10-14 19:58:59 +08:00
|
|
|
|
return // 退出循环,等待重连
|
2025-09-19 15:42:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 定期删除listenkey 并重启ws
|
|
|
|
|
|
// func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) {
|
|
|
|
|
|
// time.Sleep(30 * time.Minute)
|
|
|
|
|
|
|
|
|
|
|
|
// select {
|
|
|
|
|
|
// case <-ctx.Done():
|
|
|
|
|
|
// return
|
|
|
|
|
|
// default:
|
|
|
|
|
|
// if err := wm.deleteListenKey(listenKey); err != nil {
|
|
|
|
|
|
// log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err)
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// log.Debug("Successfully delete listenKey")
|
|
|
|
|
|
// wm.triggerReconnect()
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
2025-02-06 11:14:33 +08:00
|
|
|
|
// 定时续期
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
|
|
|
|
|
|
ticker := time.NewTicker(30 * time.Minute)
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
log.Debug("定时续期任务退出 key:", wm.apiKey)
|
|
|
|
|
|
ticker.Stop()
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := wm.renewListenKey(wm.listenKey); err != nil {
|
|
|
|
|
|
log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
删除listenkey
|
|
|
|
|
|
*/
|
2025-09-19 15:42:51 +08:00
|
|
|
|
// func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error {
|
|
|
|
|
|
// client, err := wm.createBinanceClient()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// var resp []byte
|
|
|
|
|
|
|
|
|
|
|
|
// switch wm.wsType {
|
|
|
|
|
|
// case 0:
|
|
|
|
|
|
// path := fmt.Sprintf("/api/v3/userDataStream")
|
|
|
|
|
|
// params := map[string]interface{}{
|
|
|
|
|
|
// "listenKey": listenKey,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// resp, _, err = client.SendSpotRequestByKey(path, "DELETE", params)
|
|
|
|
|
|
|
|
|
|
|
|
// log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
|
|
|
|
|
|
// case 1:
|
|
|
|
|
|
// resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "DELETE", nil)
|
|
|
|
|
|
// log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp)))
|
|
|
|
|
|
// default:
|
|
|
|
|
|
// return errors.New("unknown ws type")
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
2025-02-06 11:14:33 +08:00
|
|
|
|
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) renewListenKey(listenKey string) error {
|
|
|
|
|
|
client, err := wm.createBinanceClient()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var resp []byte
|
|
|
|
|
|
|
|
|
|
|
|
switch wm.wsType {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
path := fmt.Sprintf("/api/v3/userDataStream?listenKey=%s", listenKey)
|
|
|
|
|
|
resp, _, err = client.SendSpotRequestByKey(path, "PUT", nil)
|
|
|
|
|
|
log.Debug(fmt.Sprintf("renewListenKey resp: %s", string(resp)))
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
// path := fmt.Sprintf("/fapi/v1/listenKey", listenKey)
|
|
|
|
|
|
resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "PUT", nil)
|
|
|
|
|
|
log.Debug(fmt.Sprintf("renewListenKey resp: %s", string(resp)))
|
|
|
|
|
|
default:
|
|
|
|
|
|
return errors.New("unknown ws type")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getWsTypeName(wsType int) string {
|
|
|
|
|
|
switch wsType {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
return "spot"
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
return "futures"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "unknown"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
func getUUID() string {
|
|
|
|
|
|
return fmt.Sprintf("%s-%s-%s-%s-%s", randomHex(8), randomHex(4), randomHex(4), randomHex(4), randomHex(12))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func randomHex(n int) string {
|
|
|
|
|
|
rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
|
|
hexChars := "0123456789abcdef"
|
|
|
|
|
|
bytes := make([]byte, n)
|
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
|
bytes[i] = hexChars[rand.Intn(len(hexChars))]
|
|
|
|
|
|
}
|
|
|
|
|
|
return string(bytes)
|
|
|
|
|
|
}
|
2025-10-14 19:58:59 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动24小时强制重连定时器
|
|
|
|
|
|
* @param ctx 上下文
|
|
|
|
|
|
*/
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) start24HourReconnectTimer(ctx context.Context) {
|
|
|
|
|
|
// Binance要求不到24小时就需要主动断开重连
|
|
|
|
|
|
// 设置为23小时50分钟,留出一些缓冲时间
|
|
|
|
|
|
duration := 23*time.Hour + 50*time.Minute
|
|
|
|
|
|
|
|
|
|
|
|
wm.forceReconnectTimer = time.NewTimer(duration)
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
if wm.forceReconnectTimer != nil {
|
|
|
|
|
|
wm.forceReconnectTimer.Stop()
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-wm.forceReconnectTimer.C:
|
|
|
|
|
|
if !wm.isStopped {
|
|
|
|
|
|
log.Warnf("24小时强制重连触发 key:%s wsType:%v", wm.apiKey, wm.wsType)
|
|
|
|
|
|
wm.triggerReconnect(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 改进的假死检测机制
|
|
|
|
|
|
* @param ctx 上下文
|
|
|
|
|
|
*/
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) startDeadCheck(ctx context.Context) {
|
|
|
|
|
|
// 缩短检测间隔,提高响应速度
|
|
|
|
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
|
if wm.isStopped {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
wm.DeadCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 改进的假死检测逻辑
|
|
|
|
|
|
*/
|
|
|
|
|
|
func (wm *BinanceWebSocketManager) DeadCheck() {
|
|
|
|
|
|
// 检查最后ping时间
|
|
|
|
|
|
lastPing := wm.lastPingTime.Load().(time.Time)
|
|
|
|
|
|
|
|
|
|
|
|
// 定义最大静默时间(根据WebSocket类型调整)
|
|
|
|
|
|
var timeout time.Duration
|
|
|
|
|
|
if wm.wsType == 0 {
|
|
|
|
|
|
timeout = 3 * time.Minute // 现货:3分钟无ping视为假死
|
|
|
|
|
|
} else {
|
|
|
|
|
|
timeout = 6 * time.Minute // 合约:6分钟无ping视为假死
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if time.Since(lastPing) > timeout {
|
|
|
|
|
|
log.Warnf("检测到假死连接(基于ping时间) key:%s type:%v, 距离上次ping: %v, 触发重连",
|
|
|
|
|
|
wm.apiKey, wm.wsType, time.Since(lastPing))
|
|
|
|
|
|
wm.triggerReconnect(true)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 同时检查Redis中的记录作为备用检测
|
|
|
|
|
|
subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey)
|
|
|
|
|
|
val, _ := helper.DefaultRedis.GetString(subKey)
|
|
|
|
|
|
if val == "" {
|
|
|
|
|
|
log.Debugf("没有订阅信息,跳过Redis假死检测")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var data binancedto.UserSubscribeState
|
|
|
|
|
|
_ = sonic.Unmarshal([]byte(val), &data)
|
|
|
|
|
|
|
|
|
|
|
|
var lastTime *time.Time
|
|
|
|
|
|
if wm.wsType == 0 {
|
|
|
|
|
|
lastTime = data.SpotLastTime
|
|
|
|
|
|
} else {
|
|
|
|
|
|
lastTime = data.FuturesLastTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Redis记录的超时时间设置得更长一些
|
|
|
|
|
|
var redisTimeout time.Duration
|
|
|
|
|
|
if wm.wsType == 0 {
|
|
|
|
|
|
redisTimeout = 3 * time.Minute
|
|
|
|
|
|
} else {
|
|
|
|
|
|
redisTimeout = 6 * time.Minute
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if lastTime != nil && time.Since(*lastTime) > redisTimeout {
|
|
|
|
|
|
log.Warnf("检测到假死连接(基于Redis记录) key:%s type:%v, 距离上次通信: %v, 触发重连",
|
|
|
|
|
|
wm.apiKey, wm.wsType, time.Since(*lastTime))
|
|
|
|
|
|
wm.triggerReconnect(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 优化的重连处理逻辑
|
|
|
|
|
|
* @param ctx 上下文
|
|
|
|
|
|
*/
|