Files
exchange_go/cmd/binance/binance.go
2025-10-14 19:58:59 +08:00

265 lines
6.9 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}