1、反向下单 暂时提交

This commit is contained in:
2025-07-26 09:09:09 +08:00
parent 3013486dd4
commit 771c617da4
44 changed files with 2018 additions and 614 deletions

View File

@ -20,6 +20,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/bytedance/sonic"
@ -42,7 +43,9 @@ type BinanceWebSocketManager struct {
isStopped bool // 标记 WebSocket 是否已主动停止
mu sync.Mutex // 用于控制并发访问 isStopped
cancelFunc context.CancelFunc
listenKey string // 新增字段
listenKey string // 新增字段
reconnecting atomic.Bool // 防止重复重连
ConnectTime time.Time // 当前连接建立时间
}
// 已有连接
@ -72,6 +75,10 @@ func NewBinanceWebSocketManager(wsType int, apiKey, apiSecret, proxyType, proxyA
}
}
func (wm *BinanceWebSocketManager) GetKey() string {
return wm.apiKey
}
func (wm *BinanceWebSocketManager) Start() {
utility.SafeGo(wm.run)
// wm.run()
@ -91,14 +98,32 @@ func (wm *BinanceWebSocketManager) Restart(apiKey, apiSecret, proxyType, proxyAd
wm.isStopped = false
utility.SafeGo(wm.run)
} else {
wm.reconnect <- struct{}{}
log.Warnf("调用restart")
wm.triggerReconnect(true)
}
return wm
}
// 触发重连
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:
// 防止阻塞,如果通道满了就跳过
}
}
}
func Restart(wm *BinanceWebSocketManager) {
wm.reconnect <- struct{}{}
log.Warnf("调用restart")
wm.triggerReconnect(true)
}
func (wm *BinanceWebSocketManager) run() {
ctx, cancel := context.WithCancel(context.Background())
@ -201,6 +226,8 @@ func (wm *BinanceWebSocketManager) connect(ctx context.Context) error {
return err
}
// 连接成功,更新连接时间
wm.ConnectTime = time.Now()
log.Info(fmt.Sprintf("已连接到 Binance %s WebSocket【%s】 key:%s", getWsTypeName(wm.wsType), wm.apiKey, listenKey))
// Ping处理
@ -222,10 +249,88 @@ func (wm *BinanceWebSocketManager) connect(ctx context.Context) error {
return nil
})
// utility.SafeGoParam(wm.restartConnect, ctx)
utility.SafeGo(func() { wm.startListenKeyRenewal2(ctx) })
utility.SafeGo(func() { wm.readMessages(ctx) })
utility.SafeGo(func() { wm.handleReconnect(ctx) })
utility.SafeGo(func() { wm.startPingLoop(ctx) })
// utility.SafeGo(func() { wm.startDeadCheck(ctx) })
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)
}
dialer, err := wm.getDialer()
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)
}
return nil
}
@ -251,6 +356,7 @@ func setLastTime(wm *BinanceWebSocketManager) {
if val != "" {
helper.DefaultRedis.SetString(subKey, val)
}
}
func (wm *BinanceWebSocketManager) getDialer() (*websocket.Dialer, error) {
@ -357,7 +463,8 @@ func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) {
_, msg, err := wm.ws.ReadMessage()
if err != nil && strings.Contains(err.Error(), "websocket: close") {
if !wm.isStopped {
wm.reconnect <- struct{}{}
log.Error("收到关闭消息", err.Error())
wm.triggerReconnect(false)
}
log.Error("websocket 关闭")
@ -375,8 +482,9 @@ func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) {
func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) {
setLastTime(wm)
if reconnect, _ := ReceiveListen(msg, wm.wsType); reconnect {
wm.reconnect <- struct{}{}
if reconnect, _ := ReceiveListen(msg, wm.wsType, wm.apiKey); reconnect {
log.Errorf("收到重连请求")
wm.triggerReconnect(false)
}
}
@ -447,6 +555,7 @@ func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) {
retryCount++
if retryCount >= maxRetries {
wm.reconnecting.Store(false)
log.Error("重连失败次数过多,退出重连逻辑")
return
}
@ -455,51 +564,88 @@ func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) {
continue
}
// 重连成功,清除标记
wm.reconnecting.Store(false)
retryCount = 0
return
}
}
}
}
// 定期删除listenkey 并重启ws
func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) {
time.Sleep(30 * time.Minute)
select {
case <-ctx.Done():
// 假死检测
func (wm *BinanceWebSocketManager) DeadCheck() {
subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey)
val, _ := helper.DefaultRedis.GetString(subKey)
if val == "" {
log.Warnf("没有订阅信息,无法进行假死检测")
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.reconnect <- struct{}{}
}
}
// ticker := time.NewTicker(5 * time.Minute)
// defer ticker.Stop()
var data binancedto.UserSubscribeState
_ = sonic.Unmarshal([]byte(val), &data)
// for {
// select {
// case <-ticker.C:
// if wm.isStopped {
// return
// }
var lastTime *time.Time
if wm.wsType == 0 {
lastTime = data.SpotLastTime
} else {
lastTime = data.FuturesLastTime
}
// 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.reconnect <- struct{}{}
// return
// }
// case <-ctx.Done():
// return
// }
// }
// 定义最大静默时间(超出视为假死)
var timeout time.Duration
if wm.wsType == 0 {
timeout = 40 * time.Second // Spot 每 20s ping40s 足够
} else {
timeout = 6 * time.Minute // Futures 每 3 分钟 ping
}
if lastTime != nil && time.Since(*lastTime) > timeout {
log.Warnf("检测到假死连接 key:%s type:%v, 距离上次通信: %v, 触发重连", wm.apiKey, wm.wsType, time.Since(*lastTime))
wm.triggerReconnect(true)
}
}
// 主动心跳发送机制
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
}
err := wm.ws.WriteMessage(websocket.PingMessage, []byte("ping"))
if err != nil {
log.Error("主动 Ping Binance 失败:", err)
wm.triggerReconnect(false)
}
}
}
}
// 定期删除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()
// }
// }
// }
// 定时续期
func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
ticker := time.NewTicker(30 * time.Minute)
@ -527,49 +673,34 @@ func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) {
/*
删除listenkey
*/
func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error {
client, err := wm.createBinanceClient()
if err != nil {
return err
}
// func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error {
// client, err := wm.createBinanceClient()
// if err != nil {
// return err
// }
var resp []byte
// 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)
// 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")
}
// 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
}
// return err
// }
func (wm *BinanceWebSocketManager) renewListenKey(listenKey string) error {
// payloadParam := map[string]interface{}{
// "listenKey": listenKey,
// "apiKey": wm.apiKey,
// }
// params := map[string]interface{}{
// "id": getUUID(),
// "method": "userDataStream.ping",
// "params": payloadParam,
// }
// if err := wm.ws.WriteJSON(params); err != nil {
// return err
// }
// wm.ws.WriteJSON()
client, err := wm.createBinanceClient()
if err != nil {
return err