265 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package binance
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"fmt"
 | ||
| 	"go-admin/app/admin/models"
 | ||
| 	"go-admin/app/admin/service"
 | ||
| 	"go-admin/common/const/rediskey"
 | ||
| 	"go-admin/common/database"
 | ||
| 	"go-admin/common/global"
 | ||
| 	"go-admin/common/helper"
 | ||
| 	"go-admin/common/storage"
 | ||
| 	"go-admin/config/serverinit"
 | ||
| 	"go-admin/pkg/httputils"
 | ||
| 	"go-admin/pkg/utility"
 | ||
| 	"go-admin/services/excservice"
 | ||
| 	"go-admin/services/fileservice"
 | ||
| 	"os"
 | ||
| 	"os/signal"
 | ||
| 	"strconv"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	ext "go-admin/config"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| 	"github.com/go-admin-team/go-admin-core/config/source/file"
 | ||
| 	"github.com/go-admin-team/go-admin-core/logger"
 | ||
| 	"github.com/go-admin-team/go-admin-core/sdk"
 | ||
| 	"github.com/go-admin-team/go-admin-core/sdk/config"
 | ||
| 	"github.com/go-admin-team/go-admin-core/sdk/pkg"
 | ||
| 	"github.com/spf13/cobra"
 | ||
| 	"gorm.io/gorm"
 | ||
| )
 | ||
| 
 | ||
| var (
 | ||
| 	configYml string
 | ||
| 	StartCmd  = &cobra.Command{
 | ||
| 		Use:          "binance",
 | ||
| 		Short:        "Start Binance service (market data + user private)",
 | ||
| 		Example:      "go-admin binance -c config/settings.yml",
 | ||
| 		SilenceUsage: true,
 | ||
| 		PreRun: func(cmd *cobra.Command, args []string) {
 | ||
| 			setup()
 | ||
| 		},
 | ||
| 		RunE: func(cmd *cobra.Command, args []string) error {
 | ||
| 			return run()
 | ||
| 		},
 | ||
| 	}
 | ||
| )
 | ||
| 
 | ||
| func init() {
 | ||
| 	StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file")
 | ||
| }
 | ||
| 
 | ||
| func setup() {
 | ||
| 	// 注入配置扩展项
 | ||
| 	config.ExtendConfig = &ext.ExtConfig
 | ||
| 	//1. 读取配置
 | ||
| 	config.Setup(
 | ||
| 		file.NewSource(file.WithPath(configYml)),
 | ||
| 		database.Setup,
 | ||
| 		storage.Setup,
 | ||
| 	)
 | ||
| 	//注册监听函数
 | ||
| 	queue := sdk.Runtime.GetMemoryQueue("")
 | ||
| 	queue.Register(global.LoginLog, models.SaveLoginLog)
 | ||
| 	queue.Register(global.OperateLog, models.SaveOperaLog)
 | ||
| 	go queue.Run()
 | ||
| 
 | ||
| 	usageStr := `starting Binance service (market data + user private)...`
 | ||
| 	logger.Info(usageStr)
 | ||
| }
 | ||
| 
 | ||
| func run() error {
 | ||
| 	ctx, cancel := context.WithCancel(context.Background())
 | ||
| 	defer cancel()
 | ||
| 
 | ||
| 	var db *gorm.DB
 | ||
| 	dbs := sdk.Runtime.GetDb()
 | ||
| 
 | ||
| 	for _, item := range dbs {
 | ||
| 		db = item
 | ||
| 		break
 | ||
| 	}
 | ||
| 
 | ||
| 	defaultInit(db, ctx)
 | ||
| 
 | ||
| 	utility.SafeGo(func() {
 | ||
| 		// 启动定时任务
 | ||
| 		clearLogJob(db, ctx)
 | ||
| 	})
 | ||
| 
 | ||
| 	//自动重启websocket
 | ||
| 	// utility.SafeGo(func() {
 | ||
| 	// 	reconnect(ctx)
 | ||
| 	// })
 | ||
| 
 | ||
| 	//动态用户连接管理
 | ||
| 	utility.SafeGo(func() {
 | ||
| 		manageBinanceUserConnections(ctx)
 | ||
| 	})
 | ||
| 
 | ||
| 	// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
 | ||
| 	quit := make(chan os.Signal, 1)
 | ||
| 	signal.Notify(quit, os.Interrupt)
 | ||
| 	<-quit
 | ||
| 
 | ||
| 	fmt.Printf("%s Shutdown Server ... \r\n", pkg.GetCurrentTimeStr())
 | ||
| 	logger.Infof("Server exiting")
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| func defaultInit(db *gorm.DB, ctx context.Context) {
 | ||
| 	httputils.InitProxy(ext.ExtConfig.ProxyUrl)
 | ||
| 	//初始化 默认redis
 | ||
| 	helper.InitDefaultRedis(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, ext.ExtConfig.Redis.Db)
 | ||
| 	helper.InitLockRedisConn(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, strconv.Itoa(ext.ExtConfig.Redis.Db))
 | ||
| 
 | ||
| 	err := helper.DefaultRedis.Ping()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		logger.Info("初始化redis失败!请检查配置")
 | ||
| 		_, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | ||
| 		cancel()
 | ||
| 	} else {
 | ||
| 		logger.Info("redis初始化成功")
 | ||
| 	}
 | ||
| 
 | ||
| 	//初始化Binance服务(行情 + 用户私有)
 | ||
| 	logger.Info("=== 启动Binance完整服务 ===")
 | ||
| 
 | ||
| 	// 启动Binance行情服务
 | ||
| 	logger.Info("启动Binance行情服务...")
 | ||
| 	if err := serverinit.BinanceMarketInit(); err != nil {
 | ||
| 		logger.Infof("Binance行情服务启动失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 初始化Binance用户连接
 | ||
| 	logger.Info("启动Binance用户服务...")
 | ||
| 	initBinanceUserConnections(db, ctx)
 | ||
| 	logger.Info("=== Binance完整服务已启动 ===")
 | ||
| }
 | ||
| 
 | ||
| // 初始化Binance用户连接
 | ||
| func initBinanceUserConnections(db *gorm.DB, ctx context.Context) {
 | ||
| 	var list []models.LineApiUser
 | ||
| 	err := db.Model(&models.LineApiUser{}).Where("open_status=1 AND exchange_type =?", global.EXCHANGE_BINANCE).Find(&list).Error
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		logger.Info("获取Binance用户api失败: %v", err)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	logger.Infof("找到 %d 个Binance用户,开始建立连接...", len(list))
 | ||
| 	for _, item := range list {
 | ||
| 		service.OpenUserBinanceWebsocket(item)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 动态Binance用户连接管理
 | ||
| func manageBinanceUserConnections(ctx context.Context) {
 | ||
| 	ticker := time.NewTicker(time.Second * 5)
 | ||
| 	defer ticker.Stop()
 | ||
| 
 | ||
| 	for {
 | ||
| 		select {
 | ||
| 		case <-ctx.Done():
 | ||
| 			logger.Info("Binanceç¨æ·åç»åç§°åé")
 | ||
| 			return
 | ||
| 		case <-ticker.C:
 | ||
| 			deleteKeys, _ := helper.DefaultRedis.GetAllList(rediskey.ApiUserDeleteList)
 | ||
| 			activeApis, _ := helper.DefaultRedis.GetAllList(rediskey.ApiUserActiveList)
 | ||
| 
 | ||
| 			//移除Binance连接
 | ||
| 			for _, item := range deleteKeys {
 | ||
| 				logger.Infof("移除Binance用户连接: %s", item)
 | ||
| 
 | ||
| 				if wm, ok := excservice.SpotSockets[item]; ok {
 | ||
| 					wm.Stop()
 | ||
| 					delete(excservice.SpotSockets, item)
 | ||
| 				}
 | ||
| 				if wm, ok := excservice.FutureSockets[item]; ok {
 | ||
| 					wm.Stop()
 | ||
| 					delete(excservice.FutureSockets, item)
 | ||
| 				}
 | ||
| 
 | ||
| 				if _, err := helper.DefaultRedis.LRem(rediskey.ApiUserDeleteList, item); err != nil {
 | ||
| 					logger.Errorf("移除待关闭websocket失败: %v", err)
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			//连接Binance用户websocket
 | ||
| 			var apiUser models.LineApiUser
 | ||
| 			for _, item := range activeApis {
 | ||
| 				if item == "" {
 | ||
| 					continue
 | ||
| 				}
 | ||
| 
 | ||
| 				if err := sonic.Unmarshal([]byte(item), &apiUser); err != nil {
 | ||
| 					logger.Errorf("解析用户信息失败: %v", err)
 | ||
| 					continue
 | ||
| 				}
 | ||
| 
 | ||
| 				// 只处理Binance用户
 | ||
| 				if apiUser.Id > 0 && apiUser.ExchangeType == global.EXCHANGE_BINANCE {
 | ||
| 					logger.Infof("为Binance用户 %d 建立连接", apiUser.Id)
 | ||
| 					service.OpenUserBinanceWebsocket(apiUser)
 | ||
| 
 | ||
| 					if _, err := helper.DefaultRedis.LRem(rediskey.ApiUserActiveList, item); err != nil {
 | ||
| 						logger.Errorf("删除待触发的apiUser失败: %v", err)
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 定时清理日志
 | ||
| func clearLogJob(db *gorm.DB, ctx context.Context) {
 | ||
| 	ticker := time.NewTicker(time.Hour * 1)
 | ||
| 	defer ticker.Stop()
 | ||
| 
 | ||
| 	select {
 | ||
| 	case <-ctx.Done():
 | ||
| 		return
 | ||
| 	case <-ticker.C:
 | ||
| 		fileservice.ClearLogs(db)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 定时重连websocket
 | ||
| func reconnect(ctx context.Context) error {
 | ||
| 	ticker := time.NewTicker(time.Hour * 1)
 | ||
| 	defer ticker.Stop()
 | ||
| 
 | ||
| 	select {
 | ||
| 	case <-ctx.Done():
 | ||
| 		return nil
 | ||
| 	case <-ticker.C:
 | ||
| 		// 重启Binance连接
 | ||
| 		spotSockets := excservice.SpotSockets
 | ||
| 		futuresSockets := excservice.FutureSockets
 | ||
| 		timeOut := 22 * time.Hour
 | ||
| 
 | ||
| 		for _, item := range spotSockets {
 | ||
| 			if time.Since(item.ConnectTime) > timeOut {
 | ||
| 				if err := item.ReplaceConnection(); err != nil {
 | ||
| 					logger.Errorf("Binance现货重启连接失败 key:%s,error:%s", item.GetKey(), err)
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		for _, item := range futuresSockets {
 | ||
| 			if time.Since(item.ConnectTime) > timeOut {
 | ||
| 				if err := item.ReplaceConnection(); err != nil {
 | ||
| 					logger.Errorf("Binance合约重启连接失败 key:%s,error:%s", item.GetKey(), err)
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 |