diff --git a/app/admin/apis/line_reverse_position.go b/app/admin/apis/line_reverse_position.go index 284a02e..175f59c 100644 --- a/app/admin/apis/line_reverse_position.go +++ b/app/admin/apis/line_reverse_position.go @@ -253,3 +253,30 @@ func (e LineReversePosition) ClosePositionBatch(c *gin.Context) { } e.OK(nil, "批量平仓成功") } + +// 清除所有 +func (e LineReversePosition) CleanAll(c *gin.Context) { + s := service.LineReversePosition{} + + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + userId := user.GetUserId(c) + p := actions.GetPermissionFromContext(c) + + err = s.CleanAll(p, userId) + + if err != nil { + e.Error(500, err, fmt.Sprintf("清除所有仓位失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "清除所有仓位成功") +} diff --git a/app/admin/models/line_reverse_position.go b/app/admin/models/line_reverse_position.go index 04f5fdd..f23f1c0 100644 --- a/app/admin/models/line_reverse_position.go +++ b/app/admin/models/line_reverse_position.go @@ -23,6 +23,7 @@ type LineReversePosition struct { ReverseStatus int `json:"reverseStatus" gorm:"type:tinyint;comment:反单仓位状态 1-已开仓 2-已平仓"` AveragePrice decimal.Decimal `json:"averagePrice" gorm:"type:decimal(18,8);comment:主单平均价格"` ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice" gorm:"type:decimal(18,8);comment:反单平均价格"` + Version int `json:"version" gorm:"type:int;default:0;comment:版本号,用于乐观锁控制"` models.ModelTime models.ControlBy } diff --git a/app/admin/router/line_reverse_position.go b/app/admin/router/line_reverse_position.go index 0c14edd..f17f2d0 100644 --- a/app/admin/router/line_reverse_position.go +++ b/app/admin/router/line_reverse_position.go @@ -22,6 +22,9 @@ func registerLineReversePositionRouter(v1 *gin.RouterGroup, authMiddleware *jwt. r.GET("/:id", actions.PermissionAction(), api.Get) r.POST("", api.Insert) r.PUT("/:id", actions.PermissionAction(), api.Update) + + //清理所有 + r.DELETE("/clean-all", actions.PermissionAction(), api.CleanAll) r.DELETE("", api.Delete) r.PUT("close/:id", actions.PermissionAction(), api.ClosePosition) diff --git a/app/admin/service/line_reverse_position.go b/app/admin/service/line_reverse_position.go index b4b3b47..0ace102 100644 --- a/app/admin/service/line_reverse_position.go +++ b/app/admin/service/line_reverse_position.go @@ -25,6 +25,27 @@ type LineReversePosition struct { service.Service } +// 清除仓位记录 +func (e LineReversePosition) CleanAll(p *actions.DataPermission, userId int) error { + var count int64 + + if err := e.Orm.Model(&models.LineReversePosition{}). + Where("status =1 or reverse_status = 1").Count(&count).Error; err != nil { + return err + } + + if count > 0 { + e.Log.Errorf("还有仓位无法清除") + return errors.New("有仓位正在进行中,不能清除所有") + } + + if err := e.Orm.Exec("TRUNCATE TABLE line_reverse_position").Error; err != nil { + return err + } + + return nil +} + // 批量关闭仓位 func (e LineReversePosition) ClosePositionBatch(req *dto.LineReversePositionCloseBatchReq, p *actions.DataPermission, userId int, errs *[]string) error { var positions []models.LineReversePosition diff --git a/go.mod b/go.mod index 1099a6b..4cec50c 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/shirou/gopsutil/v3 v3.23.10 github.com/shopspring/decimal v1.2.0 github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.2 @@ -82,6 +83,7 @@ require ( github.com/cloudwego/iasm v0.2.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/color v1.15.0 // indirect github.com/fatih/structs v1.1.0 // indirect @@ -143,6 +145,7 @@ require ( github.com/nsqio/go-nsq v1.1.0 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect diff --git a/services/binanceservice/binanceservice_test.go b/services/binanceservice/binanceservice_test.go index 2d3bbf5..5ab4e3a 100644 --- a/services/binanceservice/binanceservice_test.go +++ b/services/binanceservice/binanceservice_test.go @@ -16,7 +16,7 @@ import ( ) func TestCancelFutClosePosition(t *testing.T) { - dsn := "root:root@tcp(192.168.123.216:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + dsn := "root:123456@tcp(127.0.0.1:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) var apiUserInfo models.LineApiUser db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) diff --git a/services/binanceservice/config_optimized.go b/services/binanceservice/config_optimized.go new file mode 100644 index 0000000..87d98e4 --- /dev/null +++ b/services/binanceservice/config_optimized.go @@ -0,0 +1,358 @@ +package binanceservice + +import ( + "fmt" + "time" + "github.com/shopspring/decimal" +) + +// OptimizedConfig 优化配置结构 +type OptimizedConfig struct { + // 锁配置 + LockConfig LockConfig `json:"lock_config"` + + // 重试配置 + RetryConfig RetryConfig `json:"retry_config"` + + // 熔断器配置 + CircuitBreakerConfig CircuitBreakerConfig `json:"circuit_breaker_config"` + + // 同步配置 + SyncConfig SyncConfig `json:"sync_config"` + + // 性能配置 + PerformanceConfig PerformanceConfig `json:"performance_config"` + + // 监控配置 + MonitorConfig MonitorConfig `json:"monitor_config"` +} + +// LockConfig 锁配置 +type LockConfig struct { + // Redis分布式锁超时时间 + RedisLockTimeout time.Duration `json:"redis_lock_timeout"` + + // 获取锁等待时间 + LockWaitTimeout time.Duration `json:"lock_wait_timeout"` + + // 订单处理锁前缀 + OrderLockPrefix string `json:"order_lock_prefix"` + + // 持仓更新锁前缀 + PositionLockPrefix string `json:"position_lock_prefix"` + + // 止盈止损锁前缀 + TakeProfitLockPrefix string `json:"take_profit_lock_prefix"` +} + +// CircuitBreakerConfig 熔断器配置 +type CircuitBreakerConfig struct { + Enabled bool `json:"enabled"` + FailureThreshold int `json:"failure_threshold"` + SuccessThreshold int `json:"success_threshold"` + Timeout time.Duration `json:"timeout"` + HalfOpenMaxCalls int `json:"half_open_max_calls"` +} + +// RetryConfig 重试配置 +type RetryConfig struct { + // 最大重试次数 + MaxRetries int `json:"max_retries"` + + // 重试延迟 + RetryDelay time.Duration `json:"retry_delay"` + + // 指数退避因子 + BackoffFactor float64 `json:"backoff_factor"` + + // 最大重试延迟 + MaxRetryDelay time.Duration `json:"max_retry_delay"` + + // API调用重试次数 + ApiRetryCount int `json:"api_retry_count"` + + // 数据库操作重试次数 + DbRetryCount int `json:"db_retry_count"` +} + +// SyncConfig 同步配置 +type SyncConfig struct { + // 持仓同步检查间隔 + PositionSyncInterval time.Duration `json:"position_sync_interval"` + + // 持仓差异阈值 + PositionDiffThreshold decimal.Decimal `json:"position_diff_threshold"` + + // 强制同步阈值 + ForceSyncThreshold decimal.Decimal `json:"force_sync_threshold"` + + // 同步超时时间 + SyncTimeout time.Duration `json:"sync_timeout"` + + // 是否启用自动同步 + AutoSyncEnabled bool `json:"auto_sync_enabled"` +} + +// PerformanceConfig 性能配置 +type PerformanceConfig struct { + // 批量操作大小 + BatchSize int `json:"batch_size"` + + // 异步处理队列大小 + AsyncQueueSize int `json:"async_queue_size"` + + // 工作协程数量 + WorkerCount int `json:"worker_count"` + + // 数据库连接池大小 + DbPoolSize int `json:"db_pool_size"` + + // 缓存过期时间 + CacheExpiration time.Duration `json:"cache_expiration"` + + // 是否启用缓存 + CacheEnabled bool `json:"cache_enabled"` +} + +// MonitorConfig 监控配置 +type MonitorConfig struct { + // 是否启用监控 + Enabled bool `json:"enabled"` + + // 监控数据收集间隔 + CollectInterval time.Duration `json:"collect_interval"` + + // 告警阈值配置 + AlertThresholds AlertThresholds `json:"alert_thresholds"` + + // 日志级别 + LogLevel string `json:"log_level"` + + // 是否启用性能分析 + ProfileEnabled bool `json:"profile_enabled"` +} + +// AlertThresholds 告警阈值配置 +type AlertThresholds struct { + // 订单处理失败率阈值 (百分比) + OrderFailureRate float64 `json:"order_failure_rate"` + + // 持仓同步失败率阈值 (百分比) + PositionSyncFailureRate float64 `json:"position_sync_failure_rate"` + + // API调用失败率阈值 (百分比) + ApiFailureRate float64 `json:"api_failure_rate"` + + // 响应时间阈值 (毫秒) + ResponseTimeThreshold time.Duration `json:"response_time_threshold"` + + // 内存使用率阈值 (百分比) + MemoryUsageThreshold float64 `json:"memory_usage_threshold"` + + // CPU使用率阈值 (百分比) + CpuUsageThreshold float64 `json:"cpu_usage_threshold"` +} + +// GetDefaultOptimizedConfig 获取默认优化配置 +func GetDefaultOptimizedConfig() *OptimizedConfig { + return &OptimizedConfig{ + LockConfig: LockConfig{ + RedisLockTimeout: 30 * time.Second, + LockWaitTimeout: 5 * time.Second, + OrderLockPrefix: "reverse_order_lock:", + PositionLockPrefix: "position_update_lock:", + TakeProfitLockPrefix: "take_profit_lock:", + }, + RetryConfig: RetryConfig{ + MaxRetries: 3, + RetryDelay: time.Second, + BackoffFactor: 2.0, + MaxRetryDelay: 10 * time.Second, + ApiRetryCount: 3, + DbRetryCount: 3, + }, + CircuitBreakerConfig: CircuitBreakerConfig{ + Enabled: true, + FailureThreshold: 5, + SuccessThreshold: 3, + Timeout: 60 * time.Second, + HalfOpenMaxCalls: 3, + }, + SyncConfig: SyncConfig{ + PositionSyncInterval: 30 * time.Second, + PositionDiffThreshold: decimal.NewFromFloat(0.001), + ForceSyncThreshold: decimal.NewFromFloat(0.01), + SyncTimeout: 10 * time.Second, + AutoSyncEnabled: true, + }, + PerformanceConfig: PerformanceConfig{ + BatchSize: 10, + AsyncQueueSize: 1000, + WorkerCount: 5, + DbPoolSize: 20, + CacheExpiration: 5 * time.Minute, + CacheEnabled: true, + }, + MonitorConfig: MonitorConfig{ + Enabled: true, + CollectInterval: time.Minute, + AlertThresholds: AlertThresholds{ + OrderFailureRate: 5.0, // 5% + PositionSyncFailureRate: 2.0, // 2% + ApiFailureRate: 10.0, // 10% + ResponseTimeThreshold: 5 * time.Second, + MemoryUsageThreshold: 80.0, // 80% + CpuUsageThreshold: 70.0, // 70% + }, + LogLevel: "info", + ProfileEnabled: false, + }, + } +} + +// ValidateConfig 验证配置有效性 +func (c *OptimizedConfig) ValidateConfig() error { + // 验证锁配置 + if c.LockConfig.RedisLockTimeout <= 0 { + return fmt.Errorf("Redis锁超时时间必须大于0") + } + if c.LockConfig.LockWaitTimeout <= 0 { + return fmt.Errorf("锁等待超时时间必须大于0") + } + + // 验证重试配置 + if c.RetryConfig.MaxRetries < 0 { + return fmt.Errorf("最大重试次数不能为负数") + } + if c.RetryConfig.RetryDelay <= 0 { + return fmt.Errorf("重试延迟必须大于0") + } + if c.RetryConfig.BackoffFactor <= 1.0 { + return fmt.Errorf("退避因子必须大于1.0") + } + + // 验证同步配置 + if c.SyncConfig.PositionSyncInterval <= 0 { + return fmt.Errorf("持仓同步间隔必须大于0") + } + if c.SyncConfig.PositionDiffThreshold.IsNegative() { + return fmt.Errorf("持仓差异阈值不能为负数") + } + + // 验证性能配置 + if c.PerformanceConfig.BatchSize <= 0 { + return fmt.Errorf("批量操作大小必须大于0") + } + if c.PerformanceConfig.WorkerCount <= 0 { + return fmt.Errorf("工作协程数量必须大于0") + } + if c.PerformanceConfig.DbPoolSize <= 0 { + return fmt.Errorf("数据库连接池大小必须大于0") + } + + // 验证监控配置 + if c.MonitorConfig.Enabled { + if c.MonitorConfig.CollectInterval <= 0 { + return fmt.Errorf("监控数据收集间隔必须大于0") + } + if c.MonitorConfig.AlertThresholds.OrderFailureRate < 0 || c.MonitorConfig.AlertThresholds.OrderFailureRate > 100 { + return fmt.Errorf("订单失败率阈值必须在0-100之间") + } + if c.MonitorConfig.AlertThresholds.MemoryUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.MemoryUsageThreshold > 100 { + return fmt.Errorf("内存使用率阈值必须在0-100之间") + } + if c.MonitorConfig.AlertThresholds.CpuUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.CpuUsageThreshold > 100 { + return fmt.Errorf("CPU使用率阈值必须在0-100之间") + } + } + + return nil +} + +// GetLockKey 生成锁键名 +func (c *OptimizedConfig) GetLockKey(lockType, identifier string) string { + switch lockType { + case "order": + return c.LockConfig.OrderLockPrefix + identifier + case "position": + return c.LockConfig.PositionLockPrefix + identifier + case "take_profit": + return c.LockConfig.TakeProfitLockPrefix + identifier + default: + return "unknown_lock:" + identifier + } +} + +// IsRetryableError 判断错误是否可重试 +func (c *OptimizedConfig) IsRetryableError(err error) bool { + if err == nil { + return false + } + + errStr := err.Error() + // 定义不可重试的错误类型 + nonRetryableErrors := []string{ + "余额不足", + "订单重复", + "API-key", + "无效", + "权限", + "签名", + "参数错误", + } + + for _, nonRetryable := range nonRetryableErrors { + if contains(errStr, []string{nonRetryable}) { + return false + } + } + + return true +} + +// GetRetryDelay 计算重试延迟时间 +func (c *OptimizedConfig) GetRetryDelay(attempt int) time.Duration { + if attempt <= 0 { + return c.RetryConfig.RetryDelay + } + + // 指数退避算法 + delay := c.RetryConfig.RetryDelay + for i := 0; i < attempt; i++ { + delay = time.Duration(float64(delay) * c.RetryConfig.BackoffFactor) + if delay > c.RetryConfig.MaxRetryDelay { + return c.RetryConfig.MaxRetryDelay + } + } + + return delay +} + +// ShouldSync 判断是否需要同步持仓 +func (c *OptimizedConfig) ShouldSync(exchangePosition, systemPosition decimal.Decimal) bool { + diff := exchangePosition.Sub(systemPosition).Abs() + return diff.GreaterThan(c.SyncConfig.PositionDiffThreshold) +} + +// ShouldForceSync 判断是否需要强制同步 +func (c *OptimizedConfig) ShouldForceSync(exchangePosition, systemPosition decimal.Decimal) bool { + diff := exchangePosition.Sub(systemPosition).Abs() + return diff.GreaterThan(c.SyncConfig.ForceSyncThreshold) +} + +// 全局配置实例 +var GlobalOptimizedConfig *OptimizedConfig + +// InitOptimizedConfig 初始化优化配置 +func InitOptimizedConfig() error { + GlobalOptimizedConfig = GetDefaultOptimizedConfig() + return GlobalOptimizedConfig.ValidateConfig() +} + +// GetOptimizedConfig 获取全局优化配置 +func GetOptimizedConfig() *OptimizedConfig { + if GlobalOptimizedConfig == nil { + InitOptimizedConfig() + } + return GlobalOptimizedConfig +} \ No newline at end of file diff --git a/services/binanceservice/error_handler_optimized.go b/services/binanceservice/error_handler_optimized.go new file mode 100644 index 0000000..07e585e --- /dev/null +++ b/services/binanceservice/error_handler_optimized.go @@ -0,0 +1,676 @@ +package binanceservice + +import ( + "context" + "errors" + "fmt" + "net" + "strings" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "gorm.io/gorm" +) + +// ErrorType 错误类型 +type ErrorType string + +const ( + ErrorTypeNetwork ErrorType = "network" + ErrorTypeDatabase ErrorType = "database" + ErrorTypeBusiness ErrorType = "business" + ErrorTypeSystem ErrorType = "system" + ErrorTypeValidation ErrorType = "validation" + ErrorTypeTimeout ErrorType = "timeout" + ErrorTypeRateLimit ErrorType = "rate_limit" + ErrorTypeAuth ErrorType = "auth" +) + +// ErrorSeverity 错误严重程度 +type ErrorSeverity string + +const ( + SeverityLow ErrorSeverity = "low" + SeverityMedium ErrorSeverity = "medium" + SeverityHigh ErrorSeverity = "high" + SeverityCritical ErrorSeverity = "critical" +) + +// ErrorAction 错误处理动作 +type ErrorAction string + +const ( + ActionRetry ErrorAction = "retry" + ActionFallback ErrorAction = "fallback" + ActionAlert ErrorAction = "alert" + ActionIgnore ErrorAction = "ignore" + ActionCircuit ErrorAction = "circuit" + ActionDegrade ErrorAction = "degrade" +) + +// ErrorInfo 错误信息 +type ErrorInfo struct { + Type ErrorType `json:"type"` + Severity ErrorSeverity `json:"severity"` + Message string `json:"message"` + OriginalErr error `json:"-"` + Context string `json:"context"` + Timestamp time.Time `json:"timestamp"` + Retryable bool `json:"retryable"` + Action ErrorAction `json:"action"` +} + +// RetryConfig 和 CircuitBreakerConfig 已在 config_optimized.go 中定义 + +// ErrorHandler 错误处理器 +type ErrorHandler struct { + config *OptimizedConfig + metricsCollector *MetricsCollector + circuitBreakers map[string]*CircuitBreaker +} + +// CircuitBreaker 熔断器 +type CircuitBreaker struct { + name string + config CircuitBreakerConfig + state CircuitState + failureCount int + successCount int + lastFailTime time.Time + nextRetry time.Time + halfOpenCalls int +} + +// CircuitState 熔断器状态 +type CircuitState string + +const ( + StateClosed CircuitState = "closed" + StateOpen CircuitState = "open" + StateHalfOpen CircuitState = "half_open" +) + +// NewErrorHandler 创建错误处理器 +func NewErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) *ErrorHandler { + return &ErrorHandler{ + config: config, + metricsCollector: metricsCollector, + circuitBreakers: make(map[string]*CircuitBreaker), + } +} + +// ClassifyError 分类错误 +func (eh *ErrorHandler) ClassifyError(err error, context string) *ErrorInfo { + if err == nil { + return nil + } + + errorInfo := &ErrorInfo{ + OriginalErr: err, + Message: err.Error(), + Context: context, + Timestamp: time.Now(), + } + + // 根据错误类型分类 + switch { + case isNetworkError(err): + errorInfo.Type = ErrorTypeNetwork + errorInfo.Severity = SeverityMedium + errorInfo.Retryable = true + errorInfo.Action = ActionRetry + + case isDatabaseError(err): + errorInfo.Type = ErrorTypeDatabase + errorInfo.Severity = SeverityHigh + errorInfo.Retryable = isRetryableDatabaseError(err) + errorInfo.Action = ActionRetry + + case isTimeoutError(err): + errorInfo.Type = ErrorTypeTimeout + errorInfo.Severity = SeverityMedium + errorInfo.Retryable = true + errorInfo.Action = ActionRetry + + case isRateLimitError(err): + errorInfo.Type = ErrorTypeRateLimit + errorInfo.Severity = SeverityMedium + errorInfo.Retryable = true + errorInfo.Action = ActionRetry + + case isAuthError(err): + errorInfo.Type = ErrorTypeAuth + errorInfo.Severity = SeverityHigh + errorInfo.Retryable = false + errorInfo.Action = ActionAlert + + case isValidationError(err): + errorInfo.Type = ErrorTypeValidation + errorInfo.Severity = SeverityLow + errorInfo.Retryable = false + errorInfo.Action = ActionIgnore + + case isBusinessError(err): + errorInfo.Type = ErrorTypeBusiness + errorInfo.Severity = SeverityMedium + errorInfo.Retryable = false + errorInfo.Action = ActionFallback + + default: + errorInfo.Type = ErrorTypeSystem + errorInfo.Severity = SeverityHigh + errorInfo.Retryable = true + errorInfo.Action = ActionRetry + } + + return errorInfo +} + +// HandleError 处理错误 +func (eh *ErrorHandler) HandleError(err error, context string) *ErrorInfo { + errorInfo := eh.ClassifyError(err, context) + if errorInfo == nil { + return nil + } + + // 记录错误指标 + if eh.metricsCollector != nil { + eh.metricsCollector.RecordError(string(errorInfo.Type), errorInfo.Message, context) + } + + // 根据错误动作处理 + switch errorInfo.Action { + case ActionAlert: + eh.sendAlert(errorInfo) + case ActionCircuit: + eh.triggerCircuitBreaker(context) + case ActionDegrade: + eh.enableDegradedMode(context) + } + + // 记录日志 + eh.logError(errorInfo) + + return errorInfo +} + +// RetryWithBackoff 带退避的重试 +func (eh *ErrorHandler) RetryWithBackoff(ctx context.Context, operation func() error, config RetryConfig, context string) error { + var lastErr error + delay := config.RetryDelay + + for attempt := 1; attempt <= config.MaxRetries; attempt++ { + // 检查上下文是否已取消 + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // 检查熔断器状态 + if !eh.canExecute(context) { + return fmt.Errorf("circuit breaker is open for %s", context) + } + + // 执行操作 + // 注意:config中没有TimeoutPerTry字段,这里暂时注释掉 + // if config.TimeoutPerTry > 0 { + // var cancel context.CancelFunc + // operationCtx, cancel = context.WithTimeout(ctx, config.TimeoutPerTry) + // defer cancel() + // } + + err := operation() + if err == nil { + // 成功,记录成功指标 + if eh.metricsCollector != nil { + eh.metricsCollector.RecordRetry(true) + } + eh.recordSuccess(context) + return nil + } + + lastErr = err + errorInfo := eh.ClassifyError(err, context) + + // 记录失败 + eh.recordFailure(context) + + // 如果不可重试,直接返回 + if errorInfo != nil && !errorInfo.Retryable { + logger.Warnf("不可重试的错误 [%s]: %v", context, err) + return err + } + + // 最后一次尝试,不再等待 + if attempt == config.MaxRetries { + break + } + + // 计算下次重试延迟 + nextDelay := eh.calculateDelay(delay, config) + logger.Infof("重试 %d/%d [%s] 在 %v 后,错误: %v", attempt, config.MaxRetries, context, nextDelay, err) + + // 等待重试 + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(nextDelay): + delay = nextDelay + } + } + + // 记录重试失败指标 + if eh.metricsCollector != nil { + eh.metricsCollector.RecordRetry(false) + } + + return fmt.Errorf("重试 %d 次后仍然失败 [%s]: %w", config.MaxRetries, context, lastErr) +} + +// calculateDelay 计算重试延迟 +func (eh *ErrorHandler) calculateDelay(currentDelay time.Duration, config RetryConfig) time.Duration { + nextDelay := time.Duration(float64(currentDelay) * config.BackoffFactor) + + // 限制最大延迟 + if nextDelay > config.MaxRetryDelay { + nextDelay = config.MaxRetryDelay + } + + // 添加10%抖动(简化版本,不依赖JitterEnabled字段) + jitter := time.Duration(float64(nextDelay) * 0.1) + if jitter > 0 { + nextDelay += time.Duration(time.Now().UnixNano() % int64(jitter)) + } + + return nextDelay +} + +// canExecute 检查是否可以执行操作(熔断器检查) +func (eh *ErrorHandler) canExecute(context string) bool { + cb := eh.getCircuitBreaker(context) + if cb == nil || !cb.config.Enabled { + return true + } + + now := time.Now() + + switch cb.state { + case StateClosed: + return true + + case StateOpen: + if now.After(cb.nextRetry) { + cb.state = StateHalfOpen + cb.halfOpenCalls = 0 + return true + } + return false + + case StateHalfOpen: + return cb.halfOpenCalls < cb.config.HalfOpenMaxCalls + + default: + return true + } +} + +// recordSuccess 记录成功 +func (eh *ErrorHandler) recordSuccess(context string) { + cb := eh.getCircuitBreaker(context) + if cb == nil || !cb.config.Enabled { + return + } + + switch cb.state { + case StateHalfOpen: + cb.successCount++ + if cb.successCount >= cb.config.SuccessThreshold { + cb.state = StateClosed + cb.failureCount = 0 + cb.successCount = 0 + } + + case StateClosed: + cb.failureCount = 0 + } +} + +// recordFailure 记录失败 +func (eh *ErrorHandler) recordFailure(context string) { + cb := eh.getCircuitBreaker(context) + if cb == nil || !cb.config.Enabled { + return + } + + cb.failureCount++ + cb.lastFailTime = time.Now() + + switch cb.state { + case StateClosed: + if cb.failureCount >= cb.config.FailureThreshold { + cb.state = StateOpen + cb.nextRetry = time.Now().Add(cb.config.Timeout) + logger.Warnf("熔断器开启 [%s]: 失败次数 %d", context, cb.failureCount) + } + + case StateHalfOpen: + cb.state = StateOpen + cb.nextRetry = time.Now().Add(cb.config.Timeout) + cb.successCount = 0 + logger.Warnf("熔断器重新开启 [%s]", context) + } +} + +// getCircuitBreaker 获取熔断器 +func (eh *ErrorHandler) getCircuitBreaker(context string) *CircuitBreaker { + cb, exists := eh.circuitBreakers[context] + if !exists { + cb = &CircuitBreaker{ + name: context, + config: eh.config.CircuitBreakerConfig, + state: StateClosed, + } + eh.circuitBreakers[context] = cb + } + return cb +} + +// triggerCircuitBreaker 触发熔断器 +func (eh *ErrorHandler) triggerCircuitBreaker(context string) { + cb := eh.getCircuitBreaker(context) + if cb != nil && cb.config.Enabled { + cb.state = StateOpen + cb.nextRetry = time.Now().Add(cb.config.Timeout) + logger.Warnf("手动触发熔断器 [%s]", context) + } +} + +// sendAlert 发送告警 +func (eh *ErrorHandler) sendAlert(errorInfo *ErrorInfo) { + // 这里可以集成告警系统,如钉钉、邮件等 + logger.Errorf("告警: [%s] %s - %s", errorInfo.Type, errorInfo.Context, errorInfo.Message) +} + +// enableDegradedMode 启用降级模式 +func (eh *ErrorHandler) enableDegradedMode(context string) { + logger.Warnf("启用降级模式 [%s]", context) + // 这里可以实现具体的降级逻辑 +} + +// logError 记录错误日志 +func (eh *ErrorHandler) logError(errorInfo *ErrorInfo) { + switch errorInfo.Severity { + case SeverityLow: + logger.Infof("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message) + case SeverityMedium: + logger.Warnf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message) + case SeverityHigh, SeverityCritical: + logger.Errorf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message) + } +} + +// GetCircuitBreakerStatus 获取熔断器状态 +func (eh *ErrorHandler) GetCircuitBreakerStatus() map[string]interface{} { + status := make(map[string]interface{}) + for name, cb := range eh.circuitBreakers { + status[name] = map[string]interface{}{ + "state": cb.state, + "failure_count": cb.failureCount, + "success_count": cb.successCount, + "last_fail_time": cb.lastFailTime, + "next_retry": cb.nextRetry, + } + } + return status +} + +// 错误分类辅助函数 + +// isNetworkError 判断是否为网络错误 +func isNetworkError(err error) bool { + if err == nil { + return false + } + + // 检查网络相关错误 + var netErr net.Error + if errors.As(err, &netErr) { + return true + } + + // 检查常见网络错误消息 + errorMsg := strings.ToLower(err.Error()) + networkKeywords := []string{ + "connection refused", + "connection reset", + "connection timeout", + "network unreachable", + "no route to host", + "dns", + "socket", + } + + for _, keyword := range networkKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// isDatabaseError 判断是否为数据库错误 +func isDatabaseError(err error) bool { + if err == nil { + return false + } + + // 检查GORM错误 + if errors.Is(err, gorm.ErrRecordNotFound) || + errors.Is(err, gorm.ErrInvalidTransaction) || + errors.Is(err, gorm.ErrNotImplemented) { + return true + } + + // 检查数据库相关错误消息 + errorMsg := strings.ToLower(err.Error()) + dbKeywords := []string{ + "database", + "sql", + "mysql", + "postgres", + "connection pool", + "deadlock", + "constraint", + "duplicate key", + } + + for _, keyword := range dbKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// isRetryableDatabaseError 判断是否为可重试的数据库错误 +func isRetryableDatabaseError(err error) bool { + if err == nil { + return false + } + + // 不可重试的错误 + if errors.Is(err, gorm.ErrRecordNotFound) { + return false + } + + errorMsg := strings.ToLower(err.Error()) + nonRetryableKeywords := []string{ + "constraint", + "duplicate key", + "foreign key", + "syntax error", + } + + for _, keyword := range nonRetryableKeywords { + if strings.Contains(errorMsg, keyword) { + return false + } + } + + return true +} + +// isTimeoutError 判断是否为超时错误 +func isTimeoutError(err error) bool { + if err == nil { + return false + } + + // 检查超时错误 + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return true + } + + if errors.Is(err, context.DeadlineExceeded) { + return true + } + + errorMsg := strings.ToLower(err.Error()) + return strings.Contains(errorMsg, "timeout") || + strings.Contains(errorMsg, "deadline exceeded") +} + +// isRateLimitError 判断是否为限流错误 +func isRateLimitError(err error) bool { + if err == nil { + return false + } + + errorMsg := strings.ToLower(err.Error()) + rateLimitKeywords := []string{ + "rate limit", + "too many requests", + "429", + "quota exceeded", + "throttle", + } + + for _, keyword := range rateLimitKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// isAuthError 判断是否为认证错误 +func isAuthError(err error) bool { + if err == nil { + return false + } + + errorMsg := strings.ToLower(err.Error()) + authKeywords := []string{ + "unauthorized", + "authentication", + "invalid signature", + "api key", + "401", + "403", + "forbidden", + } + + for _, keyword := range authKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// isValidationError 判断是否为验证错误 +func isValidationError(err error) bool { + if err == nil { + return false + } + + errorMsg := strings.ToLower(err.Error()) + validationKeywords := []string{ + "validation", + "invalid parameter", + "bad request", + "400", + "missing required", + "invalid format", + } + + for _, keyword := range validationKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// isBusinessError 判断是否为业务错误 +func isBusinessError(err error) bool { + if err == nil { + return false + } + + errorMsg := strings.ToLower(err.Error()) + businessKeywords := []string{ + "insufficient balance", + "order not found", + "position not found", + "invalid order status", + "market closed", + "symbol not found", + } + + for _, keyword := range businessKeywords { + if strings.Contains(errorMsg, keyword) { + return true + } + } + + return false +} + +// 全局错误处理器实例 +var GlobalErrorHandler *ErrorHandler + +// InitErrorHandler 初始化错误处理器 +func InitErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) { + GlobalErrorHandler = NewErrorHandler(config, metricsCollector) +} + +// GetErrorHandler 获取全局错误处理器 +func GetErrorHandler() *ErrorHandler { + return GlobalErrorHandler +} + +// HandleErrorWithRetry 处理错误并重试的便捷函数 +func HandleErrorWithRetry(ctx context.Context, operation func() error, context string) error { + if GlobalErrorHandler == nil { + return operation() + } + + config := RetryConfig{ + MaxRetries: 3, + RetryDelay: 100 * time.Millisecond, + MaxRetryDelay: 5 * time.Second, + BackoffFactor: 2.0, + ApiRetryCount: 3, + DbRetryCount: 3, + } + + return GlobalErrorHandler.RetryWithBackoff(ctx, operation, config, context) +} \ No newline at end of file diff --git a/services/binanceservice/integration_test_optimized.go b/services/binanceservice/integration_test_optimized.go new file mode 100644 index 0000000..dd7c2eb --- /dev/null +++ b/services/binanceservice/integration_test_optimized.go @@ -0,0 +1,587 @@ +package binanceservice + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-redis/redis/v8" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// OptimizedSystemTestSuite 优化系统测试套件 +type OptimizedSystemTestSuite struct { + suite.Suite + db *gorm.DB + redisClient *redis.Client + config *OptimizedConfig + metricsCollector *MetricsCollector + errorHandler *ErrorHandler + lockManager *LockManager + txManager *TransactionManager + reverseService *ReverseServiceOptimized +} + +// SetupSuite 设置测试套件 +func (suite *OptimizedSystemTestSuite) SetupSuite() { + // 初始化数据库连接(测试环境) + dsn := "root:password@tcp(localhost:3306)/test_exchange?charset=utf8mb4&parseTime=True&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + suite.T().Skip("跳过集成测试:无法连接数据库") + return + } + suite.db = db + + // 初始化Redis连接(测试环境) + suite.redisClient = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", + DB: 1, // 使用测试数据库 + }) + + // 测试Redis连接 + ctx := context.Background() + if err := suite.redisClient.Ping(ctx).Err(); err != nil { + suite.T().Skip("跳过集成测试:无法连接Redis") + return + } + + // 初始化配置 + suite.config = GetDefaultOptimizedConfig() + suite.config.MonitorConfig.Enabled = true + + // 初始化组件 + suite.metricsCollector = NewMetricsCollector(suite.config) + suite.errorHandler = NewErrorHandler(suite.config, suite.metricsCollector) + suite.lockManager = NewLockManager(suite.config, suite.redisClient, suite.metricsCollector) + suite.txManager = NewTransactionManager(suite.db, suite.metricsCollector, suite.config) + + // 启动指标收集 + suite.metricsCollector.Start() + + // 创建测试表 + suite.createTestTables() +} + +// TearDownSuite 清理测试套件 +func (suite *OptimizedSystemTestSuite) TearDownSuite() { + if suite.metricsCollector != nil { + suite.metricsCollector.Stop() + } + if suite.redisClient != nil { + suite.redisClient.Close() + } +} + +// SetupTest 设置每个测试 +func (suite *OptimizedSystemTestSuite) SetupTest() { + // 清理Redis测试数据 + ctx := context.Background() + suite.redisClient.FlushDB(ctx) + + // 清理数据库测试数据 + suite.db.Exec("DELETE FROM test_orders") + suite.db.Exec("DELETE FROM test_positions") +} + +// createTestTables 创建测试表 +func (suite *OptimizedSystemTestSuite) createTestTables() { + // 创建测试订单表 + suite.db.Exec(` + CREATE TABLE IF NOT EXISTS test_orders ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + order_id VARCHAR(100) UNIQUE NOT NULL, + user_id VARCHAR(50) NOT NULL, + symbol VARCHAR(20) NOT NULL, + side VARCHAR(10) NOT NULL, + quantity DECIMAL(20,8) NOT NULL, + price DECIMAL(20,8), + status VARCHAR(20) NOT NULL, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_user_symbol (user_id, symbol), + INDEX idx_status (status), + INDEX idx_version (version) + ) + `) + + // 创建测试持仓表 + suite.db.Exec(` + CREATE TABLE IF NOT EXISTS test_positions ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + user_id VARCHAR(50) NOT NULL, + symbol VARCHAR(20) NOT NULL, + side VARCHAR(10) NOT NULL, + quantity DECIMAL(20,8) NOT NULL, + avg_price DECIMAL(20,8), + status VARCHAR(20) NOT NULL, + version INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_user_symbol_side (user_id, symbol, side), + INDEX idx_version (version) + ) + `) +} + +// TestLockManager 测试锁管理器 +func (suite *OptimizedSystemTestSuite) TestLockManager() { + ctx := context.Background() + + // 测试分布式锁 + suite.Run("DistributedLock", func() { + config := LockManagerConfig{ + Type: LockTypeDistributed, + Scope: ScopeOrder, + Key: "test_order_123", + Expiration: 10 * time.Second, + Timeout: 5 * time.Second, + RetryDelay: 100 * time.Millisecond, + } + + // 获取锁 + lock, err := suite.lockManager.AcquireLock(ctx, config) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), lock) + assert.True(suite.T(), lock.IsLocked()) + + // 尝试获取同一个锁(应该失败) + config.Timeout = 1 * time.Second + _, err = suite.lockManager.AcquireLock(ctx, config) + assert.Error(suite.T(), err) + + // 释放锁 + err = suite.lockManager.ReleaseLock(ctx, lock) + assert.NoError(suite.T(), err) + assert.False(suite.T(), lock.IsLocked()) + }) + + // 测试本地锁 + suite.Run("LocalLock", func() { + config := LockManagerConfig{ + Type: LockTypeLocal, + Scope: ScopeUser, + Key: "test_user_456", + Expiration: 10 * time.Second, + Timeout: 5 * time.Second, + RetryDelay: 100 * time.Millisecond, + } + + lock, err := suite.lockManager.AcquireLock(ctx, config) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), lock) + assert.True(suite.T(), lock.IsLocked()) + + err = suite.lockManager.ReleaseLock(ctx, lock) + assert.NoError(suite.T(), err) + }) + + // 测试并发锁 + suite.Run("ConcurrentLocks", func() { + var wg sync.WaitGroup + successCount := 0 + var mu sync.Mutex + + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + config := LockManagerConfig{ + Type: LockTypeDistributed, + Scope: ScopeGlobal, + Key: "concurrent_test", + Expiration: 2 * time.Second, + Timeout: 1 * time.Second, + RetryDelay: 50 * time.Millisecond, + } + + lock, err := suite.lockManager.AcquireLock(ctx, config) + if err == nil { + mu.Lock() + successCount++ + mu.Unlock() + + time.Sleep(100 * time.Millisecond) + suite.lockManager.ReleaseLock(ctx, lock) + } + }(i) + } + + wg.Wait() + assert.Equal(suite.T(), 1, successCount, "只有一个goroutine应该获得锁") + }) +} + +// TestTransactionManager 测试事务管理器 +func (suite *OptimizedSystemTestSuite) TestTransactionManager() { + ctx := context.Background() + + // 测试基本事务 + suite.Run("BasicTransaction", func() { + config := GetDefaultTransactionConfig() + result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + // 插入测试数据 + return txCtx.Tx.Exec(` + INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) + VALUES (?, ?, ?, ?, ?, ?) + `, "order_001", "user_001", "BTCUSDT", "BUY", 1.0, "NEW").Error + }) + + assert.True(suite.T(), result.Success) + assert.NoError(suite.T(), result.Error) + + // 验证数据已插入 + var count int64 + suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_001").Scan(&count) + assert.Equal(suite.T(), int64(1), count) + }) + + // 测试事务回滚 + suite.Run("TransactionRollback", func() { + config := GetDefaultTransactionConfig() + result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + // 插入数据 + if err := txCtx.Tx.Exec(` + INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) + VALUES (?, ?, ?, ?, ?, ?) + `, "order_002", "user_002", "ETHUSDT", "SELL", 2.0, "NEW").Error; err != nil { + return err + } + + // 故意返回错误以触发回滚 + return fmt.Errorf("测试回滚") + }) + + assert.False(suite.T(), result.Success) + assert.Error(suite.T(), result.Error) + + // 验证数据未插入 + var count int64 + suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_002").Scan(&count) + assert.Equal(suite.T(), int64(0), count) + }) + + // 测试乐观锁 + suite.Run("OptimisticLock", func() { + // 先插入测试数据 + suite.db.Exec(` + INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status, version) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, "user_003", "BTCUSDT", "LONG", 1.0, 50000.0, "OPEN", 0) + + // 测试正常更新 + updates := map[string]interface{}{ + "quantity": 2.0, + } + err := suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates, + "user_id = ? AND symbol = ? AND side = ? AND version = ?", + "user_003", "BTCUSDT", "LONG", 0) + assert.NoError(suite.T(), err) + + // 测试版本冲突 + updates["quantity"] = 3.0 + err = suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates, + "user_id = ? AND symbol = ? AND side = ? AND version = ?", + "user_003", "BTCUSDT", "LONG", 0) // 使用旧版本号 + assert.Error(suite.T(), err) + assert.Contains(suite.T(), err.Error(), "乐观锁冲突") + }) +} + +// TestErrorHandler 测试错误处理器 +func (suite *OptimizedSystemTestSuite) TestErrorHandler() { + ctx := context.Background() + + // 测试错误分类 + suite.Run("ErrorClassification", func() { + testCases := []struct { + error error + expected ErrorType + }{ + {fmt.Errorf("connection refused"), ErrorTypeNetwork}, + {fmt.Errorf("database connection failed"), ErrorTypeDatabase}, + {fmt.Errorf("context deadline exceeded"), ErrorTypeTimeout}, + {fmt.Errorf("too many requests"), ErrorTypeRateLimit}, + {fmt.Errorf("unauthorized access"), ErrorTypeAuth}, + {fmt.Errorf("invalid parameter"), ErrorTypeValidation}, + {fmt.Errorf("insufficient balance"), ErrorTypeBusiness}, + } + + for _, tc := range testCases { + errorInfo := suite.errorHandler.ClassifyError(tc.error, "test") + assert.Equal(suite.T(), tc.expected, errorInfo.Type) + } + }) + + // 测试重试机制 + suite.Run("RetryMechanism", func() { + attempts := 0 + config := RetryConfig{ + MaxRetries: 3, + RetryDelay: 10 * time.Millisecond, + MaxRetryDelay: 100 * time.Millisecond, + BackoffFactor: 2.0, + ApiRetryCount: 3, + DbRetryCount: 3, + } + + // 测试重试成功 + err := suite.errorHandler.RetryWithBackoff(ctx, func() error { + attempts++ + if attempts < 3 { + return fmt.Errorf("temporary error") + } + return nil + }, config, "test_retry") + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 3, attempts) + + // 测试重试失败 + attempts = 0 + err = suite.errorHandler.RetryWithBackoff(ctx, func() error { + attempts++ + return fmt.Errorf("persistent error") + }, config, "test_retry_fail") + + assert.Error(suite.T(), err) + assert.Equal(suite.T(), 3, attempts) + }) +} + +// TestMetricsCollector 测试指标收集器 +func (suite *OptimizedSystemTestSuite) TestMetricsCollector() { + // 测试订单指标 + suite.Run("OrderMetrics", func() { + // 记录订单处理 + suite.metricsCollector.RecordOrderProcessed(true, 100*time.Millisecond) + suite.metricsCollector.RecordOrderProcessed(false, 200*time.Millisecond) + suite.metricsCollector.RecordOrderCanceled() + suite.metricsCollector.RecordReverseOrder(true) + + metrics := suite.metricsCollector.GetMetrics() + orderMetrics := metrics["order_metrics"].(*OrderMetrics) + + assert.Equal(suite.T(), int64(2), orderMetrics.TotalProcessed) + assert.Equal(suite.T(), int64(1), orderMetrics.SuccessfulOrders) + assert.Equal(suite.T(), int64(1), orderMetrics.FailedOrders) + assert.Equal(suite.T(), int64(1), orderMetrics.CanceledOrders) + assert.Equal(suite.T(), int64(1), orderMetrics.ReverseOrdersCreated) + }) + + // 测试持仓指标 + suite.Run("PositionMetrics", func() { + difference := decimal.NewFromFloat(0.1) + suite.metricsCollector.RecordPositionSync(true, difference) + suite.metricsCollector.RecordPositionSync(false, decimal.Zero) + suite.metricsCollector.RecordClosePosition(false) + suite.metricsCollector.RecordClosePosition(true) + + metrics := suite.metricsCollector.GetMetrics() + positionMetrics := metrics["position_metrics"].(*PositionMetrics) + + assert.Equal(suite.T(), int64(2), positionMetrics.SyncAttempts) + assert.Equal(suite.T(), int64(1), positionMetrics.SyncSuccesses) + assert.Equal(suite.T(), int64(1), positionMetrics.SyncFailures) + assert.Equal(suite.T(), int64(2), positionMetrics.ClosePositions) + assert.Equal(suite.T(), int64(1), positionMetrics.ForceClosePositions) + }) + + // 测试性能指标 + suite.Run("PerformanceMetrics", func() { + suite.metricsCollector.RecordLockOperation(true, 50*time.Millisecond) + suite.metricsCollector.RecordDbOperation(false, 30*time.Millisecond, nil) + suite.metricsCollector.RecordApiCall(100*time.Millisecond, nil, 1) + suite.metricsCollector.RecordConcurrency(5) + + metrics := suite.metricsCollector.GetMetrics() + perfMetrics := metrics["performance_metrics"].(*PerformanceMetrics) + + assert.Equal(suite.T(), int64(1), perfMetrics.LockAcquisitions) + assert.Equal(suite.T(), int64(1), perfMetrics.DbQueries) + assert.Equal(suite.T(), int64(1), perfMetrics.ApiCalls) + assert.Equal(suite.T(), int64(5), perfMetrics.ConcurrentRequests) + }) +} + +// TestConcurrentOperations 测试并发操作 +func (suite *OptimizedSystemTestSuite) TestConcurrentOperations() { + ctx := context.Background() + + suite.Run("ConcurrentOrderProcessing", func() { + var wg sync.WaitGroup + successCount := 0 + var mu sync.Mutex + + // 并发处理订单 + for i := 0; i < 10; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // 使用锁保护订单处理 + lockConfig := GetOrderLockConfig(fmt.Sprintf("order_%d", id)) + err := suite.lockManager.WithLock(ctx, lockConfig, func() error { + // 模拟订单处理 + config := GetDefaultTransactionConfig() + result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + return txCtx.Tx.Exec(` + INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) + VALUES (?, ?, ?, ?, ?, ?) + `, fmt.Sprintf("concurrent_order_%d", id), fmt.Sprintf("user_%d", id), + "BTCUSDT", "BUY", 1.0, "NEW").Error + }) + + if result.Success { + mu.Lock() + successCount++ + mu.Unlock() + } + + return result.Error + }) + + if err != nil { + logger.Errorf("并发订单处理失败 [%d]: %v", id, err) + } + }(i) + } + + wg.Wait() + assert.Equal(suite.T(), 10, successCount, "所有并发订单都应该成功处理") + + // 验证数据库中的记录数 + var count int64 + suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id LIKE 'concurrent_order_%'").Scan(&count) + assert.Equal(suite.T(), int64(10), count) + }) +} + +// TestSystemIntegration 测试系统集成 +func (suite *OptimizedSystemTestSuite) TestSystemIntegration() { + ctx := context.Background() + + suite.Run("CompleteOrderFlow", func() { + userID := "integration_user" + symbol := "BTCUSDT" + orderID := "integration_order_001" + + // 步骤1:创建订单(使用锁和事务) + err := WithOrderLock(ctx, orderID, func() error { + return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error { + return txCtx.Tx.Exec(` + INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, price, status) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, orderID, userID, symbol, "BUY", 1.0, 50000.0, "NEW").Error + }) + }) + assert.NoError(suite.T(), err) + + // 步骤2:更新订单状态(使用乐观锁) + updates := map[string]interface{}{ + "status": "FILLED", + } + err = UpdateWithOptimisticLockGlobal(ctx, nil, updates, + "order_id = ? AND version = ?", orderID, 0) + assert.NoError(suite.T(), err) + + // 步骤3:创建持仓(使用锁和事务) + err = WithPositionLock(ctx, userID, symbol, func() error { + return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error { + return txCtx.Tx.Exec(` + INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + quantity = quantity + VALUES(quantity), + avg_price = (avg_price * quantity + VALUES(avg_price) * VALUES(quantity)) / (quantity + VALUES(quantity)), + version = version + 1 + `, userID, symbol, "LONG", 1.0, 50000.0, "OPEN").Error + }) + }) + assert.NoError(suite.T(), err) + + // 验证最终状态 + var orderStatus string + suite.db.Raw("SELECT status FROM test_orders WHERE order_id = ?", orderID).Scan(&orderStatus) + assert.Equal(suite.T(), "FILLED", orderStatus) + + var positionQuantity float64 + suite.db.Raw("SELECT quantity FROM test_positions WHERE user_id = ? AND symbol = ?", + userID, symbol).Scan(&positionQuantity) + assert.Equal(suite.T(), 1.0, positionQuantity) + }) +} + +// TestPerformance 测试性能 +func (suite *OptimizedSystemTestSuite) TestPerformance() { + ctx := context.Background() + + suite.Run("LockPerformance", func() { + startTime := time.Now() + iterations := 1000 + + for i := 0; i < iterations; i++ { + lockConfig := GetOrderLockConfig(fmt.Sprintf("perf_order_%d", i)) + lock, err := suite.lockManager.AcquireLock(ctx, lockConfig) + assert.NoError(suite.T(), err) + suite.lockManager.ReleaseLock(ctx, lock) + } + + duration := time.Since(startTime) + avgDuration := duration / time.Duration(iterations) + logger.Infof("锁性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration) + + // 平均每次操作应该在合理范围内 + assert.Less(suite.T(), avgDuration, 10*time.Millisecond) + }) + + suite.Run("TransactionPerformance", func() { + startTime := time.Now() + iterations := 100 + + for i := 0; i < iterations; i++ { + config := GetDefaultTransactionConfig() + result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + return txCtx.Tx.Exec(` + INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status) + VALUES (?, ?, ?, ?, ?, ?) + `, fmt.Sprintf("perf_order_%d", i), "perf_user", "BTCUSDT", "BUY", 1.0, "NEW").Error + }) + assert.True(suite.T(), result.Success) + } + + duration := time.Since(startTime) + avgDuration := duration / time.Duration(iterations) + logger.Infof("事务性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration) + + // 平均每次事务应该在合理范围内 + assert.Less(suite.T(), avgDuration, 100*time.Millisecond) + }) +} + +// TestSuite 运行测试套件 +func TestOptimizedSystemSuite(t *testing.T) { + suite.Run(t, new(OptimizedSystemTestSuite)) +} + +// BenchmarkLockManager 锁管理器基准测试 +func BenchmarkLockManager(b *testing.B) { + // 这里可以添加基准测试 + b.Skip("基准测试需要Redis连接") +} + +// BenchmarkTransactionManager 事务管理器基准测试 +func BenchmarkTransactionManager(b *testing.B) { + // 这里可以添加基准测试 + b.Skip("基准测试需要数据库连接") +} \ No newline at end of file diff --git a/services/binanceservice/lock_manager_optimized.go b/services/binanceservice/lock_manager_optimized.go new file mode 100644 index 0000000..b59e944 --- /dev/null +++ b/services/binanceservice/lock_manager_optimized.go @@ -0,0 +1,575 @@ +package binanceservice + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-redis/redis/v8" +) + +// LockManagerConfig 锁管理器专用配置 +type LockManagerConfig struct { + Type LockType `json:"type"` + Scope LockScope `json:"scope"` + Key string `json:"key"` + Expiration time.Duration `json:"expiration"` + Timeout time.Duration `json:"timeout"` + RetryDelay time.Duration `json:"retry_delay"` +} + +// LockType 锁类型 +type LockType string + +const ( + LockTypeLocal LockType = "local" + LockTypeDistributed LockType = "distributed" +) + +// LockScope 锁范围 +type LockScope string + +const ( + ScopeOrder LockScope = "order" + ScopePosition LockScope = "position" + ScopeUser LockScope = "user" + ScopeSymbol LockScope = "symbol" + ScopeGlobal LockScope = "global" +) + +// Lock 锁接口 +type Lock interface { + Lock(ctx context.Context) error + Unlock(ctx context.Context) error + TryLock(ctx context.Context) (bool, error) + IsLocked() bool + GetKey() string + GetExpiration() time.Duration +} + +// LocalLock 本地锁 +type LocalLock struct { + key string + mu *sync.RWMutex + locked bool + expiration time.Duration + acquiredAt time.Time +} + +// DistributedLock 分布式锁 +type DistributedLock struct { + key string + value string + expiration time.Duration + redisClient *redis.Client + locked bool + acquiredAt time.Time + refreshTicker *time.Ticker + stopRefresh chan struct{} +} + +// LockManager 锁管理器 +type LockManager struct { + config *OptimizedConfig + redisClient *redis.Client + localLocks map[string]*LocalLock + localMutex sync.RWMutex + metrics *MetricsCollector +} + +// LockConfig 已在 config_optimized.go 中定义 + +// NewLockManager 创建锁管理器 +func NewLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) *LockManager { + return &LockManager{ + config: config, + redisClient: redisClient, + localLocks: make(map[string]*LocalLock), + metrics: metrics, + } +} + +// CreateLock 创建锁 +func (lm *LockManager) CreateLock(config LockManagerConfig) Lock { + switch config.Type { + case LockTypeDistributed: + return lm.createDistributedLock(config) + case LockTypeLocal: + return lm.createLocalLock(config) + default: + // 默认使用分布式锁 + return lm.createDistributedLock(config) + } +} + +// createLocalLock 创建本地锁 +func (lm *LockManager) createLocalLock(config LockManagerConfig) *LocalLock { + lm.localMutex.Lock() + defer lm.localMutex.Unlock() + + key := lm.buildLockKey(config.Scope, config.Key) + if lock, exists := lm.localLocks[key]; exists { + return lock + } + + lock := &LocalLock{ + key: key, + mu: &sync.RWMutex{}, + expiration: config.Expiration, + } + + lm.localLocks[key] = lock + return lock +} + +// createDistributedLock 创建分布式锁 +func (lm *LockManager) createDistributedLock(config LockManagerConfig) *DistributedLock { + key := lm.buildLockKey(config.Scope, config.Key) + value := fmt.Sprintf("%d_%s", time.Now().UnixNano(), generateRandomString(8)) + + return &DistributedLock{ + key: key, + value: value, + expiration: config.Expiration, + redisClient: lm.redisClient, + stopRefresh: make(chan struct{}), + } +} + +// buildLockKey 构建锁键名 +func (lm *LockManager) buildLockKey(scope LockScope, key string) string { + return fmt.Sprintf("lock:%s:%s", scope, key) +} + +// AcquireLock 获取锁(带超时和重试) +func (lm *LockManager) AcquireLock(ctx context.Context, config LockManagerConfig) (Lock, error) { + lock := lm.CreateLock(config) + startTime := time.Now() + + // 设置超时上下文 + lockCtx := ctx + if config.Timeout > 0 { + var cancel context.CancelFunc + lockCtx, cancel = context.WithTimeout(ctx, config.Timeout) + defer cancel() + } + + // 重试获取锁 + for { + select { + case <-lockCtx.Done(): + waitTime := time.Since(startTime) + if lm.metrics != nil { + lm.metrics.RecordLockOperation(false, waitTime) + } + return nil, fmt.Errorf("获取锁超时: %s", config.Key) + + default: + err := lock.Lock(lockCtx) + if err == nil { + waitTime := time.Since(startTime) + if lm.metrics != nil { + lm.metrics.RecordLockOperation(true, waitTime) + } + return lock, nil + } + + // 等待重试 + select { + case <-lockCtx.Done(): + return nil, lockCtx.Err() + case <-time.After(config.RetryDelay): + continue + } + } + } +} + +// ReleaseLock 释放锁 +func (lm *LockManager) ReleaseLock(ctx context.Context, lock Lock) error { + if lock == nil { + return nil + } + return lock.Unlock(ctx) +} + +// WithLock 使用锁执行操作 +func (lm *LockManager) WithLock(ctx context.Context, config LockManagerConfig, operation func() error) error { + lock, err := lm.AcquireLock(ctx, config) + if err != nil { + return fmt.Errorf("获取锁失败: %w", err) + } + + defer func() { + if unlockErr := lm.ReleaseLock(ctx, lock); unlockErr != nil { + logger.Errorf("释放锁失败 [%s]: %v", config.Key, unlockErr) + } + }() + + return operation() +} + +// CleanupExpiredLocks 清理过期的本地锁 +func (lm *LockManager) CleanupExpiredLocks() { + lm.localMutex.Lock() + defer lm.localMutex.Unlock() + + now := time.Now() + for key, lock := range lm.localLocks { + if lock.locked && lock.expiration > 0 && now.Sub(lock.acquiredAt) > lock.expiration { + lock.locked = false + logger.Warnf("本地锁已过期并被清理: %s", key) + } + } +} + +// GetLockStatus 获取锁状态 +func (lm *LockManager) GetLockStatus() map[string]interface{} { + lm.localMutex.RLock() + defer lm.localMutex.RUnlock() + + status := make(map[string]interface{}) + localLocks := make(map[string]interface{}) + + for key, lock := range lm.localLocks { + localLocks[key] = map[string]interface{}{ + "locked": lock.locked, + "acquired_at": lock.acquiredAt, + "expiration": lock.expiration, + } + } + + status["local_locks"] = localLocks + status["total_local_locks"] = len(lm.localLocks) + + return status +} + +// LocalLock 实现 + +// Lock 获取本地锁 +func (ll *LocalLock) Lock(ctx context.Context) error { + ll.mu.Lock() + defer ll.mu.Unlock() + + if ll.locked { + return fmt.Errorf("锁已被占用: %s", ll.key) + } + + ll.locked = true + ll.acquiredAt = time.Now() + return nil +} + +// Unlock 释放本地锁 +func (ll *LocalLock) Unlock(ctx context.Context) error { + ll.mu.Lock() + defer ll.mu.Unlock() + + if !ll.locked { + return fmt.Errorf("锁未被占用: %s", ll.key) + } + + ll.locked = false + return nil +} + +// TryLock 尝试获取本地锁 +func (ll *LocalLock) TryLock(ctx context.Context) (bool, error) { + ll.mu.Lock() + defer ll.mu.Unlock() + + if ll.locked { + return false, nil + } + + ll.locked = true + ll.acquiredAt = time.Now() + return true, nil +} + +// IsLocked 检查本地锁是否被占用 +func (ll *LocalLock) IsLocked() bool { + ll.mu.RLock() + defer ll.mu.RUnlock() + return ll.locked +} + +// GetKey 获取锁键名 +func (ll *LocalLock) GetKey() string { + return ll.key +} + +// GetExpiration 获取过期时间 +func (ll *LocalLock) GetExpiration() time.Duration { + return ll.expiration +} + +// DistributedLock 实现 + +// Lock 获取分布式锁 +func (dl *DistributedLock) Lock(ctx context.Context) error { + // 使用 SET key value EX seconds NX 命令 + result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration) + if result.Err() != nil { + return fmt.Errorf("获取分布式锁失败: %w", result.Err()) + } + + if !result.Val() { + return fmt.Errorf("分布式锁已被占用: %s", dl.key) + } + + dl.locked = true + dl.acquiredAt = time.Now() + + // 启动锁续期 + dl.startRefresh() + + return nil +} + +// Unlock 释放分布式锁 +func (dl *DistributedLock) Unlock(ctx context.Context) error { + if !dl.locked { + return fmt.Errorf("分布式锁未被占用: %s", dl.key) + } + + // 停止续期 + dl.stopRefreshProcess() + + // 使用 Lua 脚本确保只删除自己的锁 + luaScript := ` + if redis.call("get", KEYS[1]) == ARGV[1] then + return redis.call("del", KEYS[1]) + else + return 0 + end + ` + + result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value) + if result.Err() != nil { + return fmt.Errorf("释放分布式锁失败: %w", result.Err()) + } + + dl.locked = false + return nil +} + +// TryLock 尝试获取分布式锁 +func (dl *DistributedLock) TryLock(ctx context.Context) (bool, error) { + result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration) + if result.Err() != nil { + return false, fmt.Errorf("尝试获取分布式锁失败: %w", result.Err()) + } + + if result.Val() { + dl.locked = true + dl.acquiredAt = time.Now() + dl.startRefresh() + return true, nil + } + + return false, nil +} + +// IsLocked 检查分布式锁是否被占用 +func (dl *DistributedLock) IsLocked() bool { + return dl.locked +} + +// GetKey 获取锁键名 +func (dl *DistributedLock) GetKey() string { + return dl.key +} + +// GetExpiration 获取过期时间 +func (dl *DistributedLock) GetExpiration() time.Duration { + return dl.expiration +} + +// startRefresh 启动锁续期 +func (dl *DistributedLock) startRefresh() { + if dl.expiration <= 0 { + return + } + + // 每隔过期时间的1/3进行续期 + refreshInterval := dl.expiration / 3 + dl.refreshTicker = time.NewTicker(refreshInterval) + + go func() { + defer dl.refreshTicker.Stop() + + for { + select { + case <-dl.stopRefresh: + return + case <-dl.refreshTicker.C: + if !dl.locked { + return + } + + // 使用 Lua 脚本续期 + luaScript := ` + if redis.call("get", KEYS[1]) == ARGV[1] then + return redis.call("expire", KEYS[1], ARGV[2]) + else + return 0 + end + ` + + ctx := context.Background() + result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value, int(dl.expiration.Seconds())) + if result.Err() != nil { + logger.Errorf("分布式锁续期失败 [%s]: %v", dl.key, result.Err()) + return + } + + if result.Val().(int64) == 0 { + logger.Warnf("分布式锁已被其他进程占用,停止续期 [%s]", dl.key) + dl.locked = false + return + } + } + } + }() +} + +// stopRefreshProcess 停止续期进程 +func (dl *DistributedLock) stopRefreshProcess() { + if dl.refreshTicker != nil { + close(dl.stopRefresh) + dl.refreshTicker.Stop() + dl.refreshTicker = nil + dl.stopRefresh = make(chan struct{}) + } +} + +// 辅助函数 + +// generateRandomString 生成随机字符串 +func generateRandomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, length) + for i := range b { + b[i] = charset[time.Now().UnixNano()%int64(len(charset))] + } + return string(b) +} + +// 预定义的锁配置 + +// GetOrderLockConfig 获取订单锁配置 +func GetOrderLockConfig(orderID string) LockManagerConfig { + return LockManagerConfig{ + Type: LockTypeDistributed, + Scope: ScopeOrder, + Key: orderID, + Expiration: 30 * time.Second, + Timeout: 5 * time.Second, + RetryDelay: 100 * time.Millisecond, + } +} + +// GetPositionLockConfig 获取持仓锁配置 +func GetPositionLockConfig(userID, symbol string) LockManagerConfig { + return LockManagerConfig{ + Type: LockTypeDistributed, + Scope: ScopePosition, + Key: fmt.Sprintf("%s_%s", userID, symbol), + Expiration: 60 * time.Second, + Timeout: 10 * time.Second, + RetryDelay: 200 * time.Millisecond, + } +} + +// GetUserLockConfig 获取用户锁配置 +func GetUserLockConfig(userID string) LockManagerConfig { + return LockManagerConfig{ + Type: LockTypeLocal, + Scope: ScopeUser, + Key: userID, + Expiration: 30 * time.Second, + Timeout: 3 * time.Second, + RetryDelay: 50 * time.Millisecond, + } +} + +// GetSymbolLockConfig 获取交易对锁配置 +func GetSymbolLockConfig(symbol string) LockManagerConfig { + return LockManagerConfig{ + Type: LockTypeLocal, + Scope: ScopeSymbol, + Key: symbol, + Expiration: 15 * time.Second, + Timeout: 2 * time.Second, + RetryDelay: 25 * time.Millisecond, + } +} + +// GetGlobalLockConfig 获取全局锁配置 +func GetGlobalLockConfig(operation string) LockManagerConfig { + return LockManagerConfig{ + Type: LockTypeDistributed, + Scope: ScopeGlobal, + Key: operation, + Expiration: 120 * time.Second, + Timeout: 30 * time.Second, + RetryDelay: 500 * time.Millisecond, + } +} + +// 全局锁管理器实例 +var GlobalLockManager *LockManager + +// InitLockManager 初始化锁管理器 +func InitLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) { + GlobalLockManager = NewLockManager(config, redisClient, metrics) + + // 启动清理过期锁的定时任务 + go func() { + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + + for range ticker.C { + GlobalLockManager.CleanupExpiredLocks() + } + }() +} + +// GetLockManager 获取全局锁管理器 +func GetLockManager() *LockManager { + return GlobalLockManager +} + +// WithOrderLock 使用订单锁执行操作 +func WithOrderLock(ctx context.Context, orderID string, operation func() error) error { + if GlobalLockManager == nil { + return operation() + } + + config := GetOrderLockConfig(orderID) + return GlobalLockManager.WithLock(ctx, config, operation) +} + +// WithPositionLock 使用持仓锁执行操作 +func WithPositionLock(ctx context.Context, userID, symbol string, operation func() error) error { + if GlobalLockManager == nil { + return operation() + } + + config := GetPositionLockConfig(userID, symbol) + return GlobalLockManager.WithLock(ctx, config, operation) +} + +// WithUserLock 使用用户锁执行操作 +func WithUserLock(ctx context.Context, userID string, operation func() error) error { + if GlobalLockManager == nil { + return operation() + } + + config := GetUserLockConfig(userID) + return GlobalLockManager.WithLock(ctx, config, operation) +} \ No newline at end of file diff --git a/services/binanceservice/monitor_optimized.go b/services/binanceservice/monitor_optimized.go new file mode 100644 index 0000000..dfb44a7 --- /dev/null +++ b/services/binanceservice/monitor_optimized.go @@ -0,0 +1,594 @@ +package binanceservice + +import ( + "context" + "fmt" + "runtime" + "sync" + "sync/atomic" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" +) + +// MetricsCollector 指标收集器 +type MetricsCollector struct { + mu sync.RWMutex + + // 业务指标 + OrderMetrics *OrderMetrics + PositionMetrics *PositionMetrics + TakeProfitMetrics *TakeProfitMetrics + + // 技术指标 + PerformanceMetrics *PerformanceMetrics + ErrorMetrics *ErrorMetrics + + // 系统指标 + SystemMetrics *SystemMetrics + + // 配置 + config *OptimizedConfig + + // 控制 + ctx context.Context + cancel context.CancelFunc + startTime time.Time +} + +// OrderMetrics 订单相关指标 +type OrderMetrics struct { + // 订单处理统计 + TotalProcessed int64 `json:"total_processed"` + SuccessfulOrders int64 `json:"successful_orders"` + FailedOrders int64 `json:"failed_orders"` + CanceledOrders int64 `json:"canceled_orders"` + + // 反向订单统计 + ReverseOrdersCreated int64 `json:"reverse_orders_created"` + ReverseOrdersFailed int64 `json:"reverse_orders_failed"` + + // 响应时间统计 + TotalResponseTime time.Duration `json:"total_response_time"` + MaxResponseTime time.Duration `json:"max_response_time"` + MinResponseTime time.Duration `json:"min_response_time"` + + // 最近更新时间 + LastUpdated time.Time `json:"last_updated"` +} + +// PositionMetrics 持仓相关指标 +type PositionMetrics struct { + // 持仓同步统计 + SyncAttempts int64 `json:"sync_attempts"` + SyncSuccesses int64 `json:"sync_successes"` + SyncFailures int64 `json:"sync_failures"` + + // 持仓差异统计 + PositionMismatches int64 `json:"position_mismatches"` + MaxDifference decimal.Decimal `json:"max_difference"` + TotalDifference decimal.Decimal `json:"total_difference"` + + // 平仓统计 + ClosePositions int64 `json:"close_positions"` + ForceClosePositions int64 `json:"force_close_positions"` + + LastUpdated time.Time `json:"last_updated"` +} + +// TakeProfitMetrics 止盈止损相关指标 +type TakeProfitMetrics struct { + // 止盈止损创建统计 + TakeProfitCreated int64 `json:"take_profit_created"` + StopLossCreated int64 `json:"stop_loss_created"` + + // 止盈止损执行统计 + TakeProfitExecuted int64 `json:"take_profit_executed"` + StopLossExecuted int64 `json:"stop_loss_executed"` + + // 取消统计 + OrdersCanceled int64 `json:"orders_canceled"` + BatchCancelAttempts int64 `json:"batch_cancel_attempts"` + BatchCancelSuccesses int64 `json:"batch_cancel_successes"` + + LastUpdated time.Time `json:"last_updated"` +} + +// PerformanceMetrics 性能相关指标 +type PerformanceMetrics struct { + // 并发统计 + ConcurrentRequests int64 `json:"concurrent_requests"` + MaxConcurrency int64 `json:"max_concurrency"` + + // 锁统计 + LockAcquisitions int64 `json:"lock_acquisitions"` + LockWaitTime time.Duration `json:"lock_wait_time"` + LockTimeouts int64 `json:"lock_timeouts"` + + // 数据库操作统计 + DbQueries int64 `json:"db_queries"` + DbTransactions int64 `json:"db_transactions"` + DbQueryTime time.Duration `json:"db_query_time"` + DbErrors int64 `json:"db_errors"` + + // API调用统计 + ApiCalls int64 `json:"api_calls"` + ApiCallTime time.Duration `json:"api_call_time"` + ApiErrors int64 `json:"api_errors"` + ApiRetries int64 `json:"api_retries"` + + LastUpdated time.Time `json:"last_updated"` +} + +// ErrorMetrics 错误相关指标 +type ErrorMetrics struct { + // 错误分类统计 + NetworkErrors int64 `json:"network_errors"` + DatabaseErrors int64 `json:"database_errors"` + BusinessErrors int64 `json:"business_errors"` + SystemErrors int64 `json:"system_errors"` + + // 重试统计 + RetryAttempts int64 `json:"retry_attempts"` + RetrySuccesses int64 `json:"retry_successes"` + RetryFailures int64 `json:"retry_failures"` + + // 最近错误 + RecentErrors []ErrorRecord `json:"recent_errors"` + + LastUpdated time.Time `json:"last_updated"` +} + +// SystemMetrics 系统相关指标 +type SystemMetrics struct { + // 内存使用 + MemoryUsage uint64 `json:"memory_usage"` + MemoryPercent float64 `json:"memory_percent"` + + // CPU使用 + CpuPercent float64 `json:"cpu_percent"` + + // Goroutine统计 + GoroutineCount int `json:"goroutine_count"` + + // GC统计 + GcCount uint32 `json:"gc_count"` + GcPauseMs uint64 `json:"gc_pause_ms"` + + LastUpdated time.Time `json:"last_updated"` +} + +// ErrorRecord 错误记录 +type ErrorRecord struct { + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` + Message string `json:"message"` + Context string `json:"context"` +} + +// NewMetricsCollector 创建新的指标收集器 +func NewMetricsCollector(config *OptimizedConfig) *MetricsCollector { + ctx, cancel := context.WithCancel(context.Background()) + + return &MetricsCollector{ + OrderMetrics: &OrderMetrics{ + MinResponseTime: time.Hour, // 初始化为一个大值 + LastUpdated: time.Now(), + }, + PositionMetrics: &PositionMetrics{ + MaxDifference: decimal.Zero, + TotalDifference: decimal.Zero, + LastUpdated: time.Now(), + }, + TakeProfitMetrics: &TakeProfitMetrics{ + LastUpdated: time.Now(), + }, + PerformanceMetrics: &PerformanceMetrics{ + LastUpdated: time.Now(), + }, + ErrorMetrics: &ErrorMetrics{ + RecentErrors: make([]ErrorRecord, 0, 100), + LastUpdated: time.Now(), + }, + SystemMetrics: &SystemMetrics{ + LastUpdated: time.Now(), + }, + config: config, + ctx: ctx, + cancel: cancel, + startTime: time.Now(), + } +} + +// Start 启动指标收集 +func (mc *MetricsCollector) Start() { + if !mc.config.MonitorConfig.Enabled { + return + } + + logger.Info("启动指标收集器") + + // 启动定期收集 + go mc.collectLoop() + + // 启动告警检查 + go mc.alertLoop() +} + +// Stop 停止指标收集 +func (mc *MetricsCollector) Stop() { + logger.Info("停止指标收集器") + mc.cancel() +} + +// collectLoop 收集循环 +func (mc *MetricsCollector) collectLoop() { + ticker := time.NewTicker(mc.config.MonitorConfig.CollectInterval) + defer ticker.Stop() + + for { + select { + case <-mc.ctx.Done(): + return + case <-ticker.C: + mc.collectSystemMetrics() + } + } +} + +// alertLoop 告警检查循环 +func (mc *MetricsCollector) alertLoop() { + ticker := time.NewTicker(time.Minute) // 每分钟检查一次 + defer ticker.Stop() + + for { + select { + case <-mc.ctx.Done(): + return + case <-ticker.C: + mc.checkAlerts() + } + } +} + +// RecordOrderProcessed 记录订单处理 +func (mc *MetricsCollector) RecordOrderProcessed(success bool, responseTime time.Duration) { + mc.mu.Lock() + defer mc.mu.Unlock() + + atomic.AddInt64(&mc.OrderMetrics.TotalProcessed, 1) + + if success { + atomic.AddInt64(&mc.OrderMetrics.SuccessfulOrders, 1) + } else { + atomic.AddInt64(&mc.OrderMetrics.FailedOrders, 1) + } + + // 更新响应时间统计 + mc.OrderMetrics.TotalResponseTime += responseTime + if responseTime > mc.OrderMetrics.MaxResponseTime { + mc.OrderMetrics.MaxResponseTime = responseTime + } + if responseTime < mc.OrderMetrics.MinResponseTime { + mc.OrderMetrics.MinResponseTime = responseTime + } + + mc.OrderMetrics.LastUpdated = time.Now() +} + +// RecordOrderCanceled 记录订单取消 +func (mc *MetricsCollector) RecordOrderCanceled() { + atomic.AddInt64(&mc.OrderMetrics.CanceledOrders, 1) + mc.OrderMetrics.LastUpdated = time.Now() +} + +// RecordReverseOrder 记录反向订单 +func (mc *MetricsCollector) RecordReverseOrder(success bool) { + if success { + atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersCreated, 1) + } else { + atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersFailed, 1) + } + mc.OrderMetrics.LastUpdated = time.Now() +} + +// RecordPositionSync 记录持仓同步 +func (mc *MetricsCollector) RecordPositionSync(success bool, difference decimal.Decimal) { + mc.mu.Lock() + defer mc.mu.Unlock() + + atomic.AddInt64(&mc.PositionMetrics.SyncAttempts, 1) + + if success { + atomic.AddInt64(&mc.PositionMetrics.SyncSuccesses, 1) + } else { + atomic.AddInt64(&mc.PositionMetrics.SyncFailures, 1) + } + + // 更新差异统计 + if !difference.IsZero() { + atomic.AddInt64(&mc.PositionMetrics.PositionMismatches, 1) + mc.PositionMetrics.TotalDifference = mc.PositionMetrics.TotalDifference.Add(difference.Abs()) + + if difference.Abs().GreaterThan(mc.PositionMetrics.MaxDifference) { + mc.PositionMetrics.MaxDifference = difference.Abs() + } + } + + mc.PositionMetrics.LastUpdated = time.Now() +} + +// RecordClosePosition 记录平仓 +func (mc *MetricsCollector) RecordClosePosition(forced bool) { + atomic.AddInt64(&mc.PositionMetrics.ClosePositions, 1) + if forced { + atomic.AddInt64(&mc.PositionMetrics.ForceClosePositions, 1) + } + mc.PositionMetrics.LastUpdated = time.Now() +} + +// RecordTakeProfitCreated 记录止盈止损创建 +func (mc *MetricsCollector) RecordTakeProfitCreated(orderType string) { + switch orderType { + case "TAKE_PROFIT", "TAKE_PROFIT_MARKET": + atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitCreated, 1) + case "STOP", "STOP_MARKET": + atomic.AddInt64(&mc.TakeProfitMetrics.StopLossCreated, 1) + } + mc.TakeProfitMetrics.LastUpdated = time.Now() +} + +// RecordTakeProfitExecuted 记录止盈止损执行 +func (mc *MetricsCollector) RecordTakeProfitExecuted(orderType string) { + switch orderType { + case "TAKE_PROFIT", "TAKE_PROFIT_MARKET": + atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitExecuted, 1) + case "STOP", "STOP_MARKET": + atomic.AddInt64(&mc.TakeProfitMetrics.StopLossExecuted, 1) + } + mc.TakeProfitMetrics.LastUpdated = time.Now() +} + +// RecordOrdersCanceled 记录订单取消 +func (mc *MetricsCollector) RecordOrdersCanceled(count int) { + atomic.AddInt64(&mc.TakeProfitMetrics.OrdersCanceled, int64(count)) + mc.TakeProfitMetrics.LastUpdated = time.Now() +} + +// RecordBatchCancel 记录批量取消 +func (mc *MetricsCollector) RecordBatchCancel(success bool) { + atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelAttempts, 1) + if success { + atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelSuccesses, 1) + } + mc.TakeProfitMetrics.LastUpdated = time.Now() +} + +// RecordLockOperation 记录锁操作 +func (mc *MetricsCollector) RecordLockOperation(acquired bool, waitTime time.Duration) { + atomic.AddInt64(&mc.PerformanceMetrics.LockAcquisitions, 1) + mc.PerformanceMetrics.LockWaitTime += waitTime + + if !acquired { + atomic.AddInt64(&mc.PerformanceMetrics.LockTimeouts, 1) + } + + mc.PerformanceMetrics.LastUpdated = time.Now() +} + +// RecordDbOperation 记录数据库操作 +func (mc *MetricsCollector) RecordDbOperation(isTransaction bool, duration time.Duration, err error) { + if isTransaction { + atomic.AddInt64(&mc.PerformanceMetrics.DbTransactions, 1) + } else { + atomic.AddInt64(&mc.PerformanceMetrics.DbQueries, 1) + } + + mc.PerformanceMetrics.DbQueryTime += duration + + if err != nil { + atomic.AddInt64(&mc.PerformanceMetrics.DbErrors, 1) + mc.RecordError("database", err.Error(), "db_operation") + } + + mc.PerformanceMetrics.LastUpdated = time.Now() +} + +// RecordApiCall 记录API调用 +func (mc *MetricsCollector) RecordApiCall(duration time.Duration, err error, retryCount int) { + atomic.AddInt64(&mc.PerformanceMetrics.ApiCalls, 1) + mc.PerformanceMetrics.ApiCallTime += duration + + if err != nil { + atomic.AddInt64(&mc.PerformanceMetrics.ApiErrors, 1) + mc.RecordError("api", err.Error(), "api_call") + } + + if retryCount > 0 { + atomic.AddInt64(&mc.PerformanceMetrics.ApiRetries, int64(retryCount)) + } + + mc.PerformanceMetrics.LastUpdated = time.Now() +} + +// RecordConcurrency 记录并发数 +func (mc *MetricsCollector) RecordConcurrency(current int64) { + atomic.StoreInt64(&mc.PerformanceMetrics.ConcurrentRequests, current) + + if current > atomic.LoadInt64(&mc.PerformanceMetrics.MaxConcurrency) { + atomic.StoreInt64(&mc.PerformanceMetrics.MaxConcurrency, current) + } + + mc.PerformanceMetrics.LastUpdated = time.Now() +} + +// RecordError 记录错误 +func (mc *MetricsCollector) RecordError(errorType, message, context string) { + mc.mu.Lock() + defer mc.mu.Unlock() + + // 分类统计 + switch errorType { + case "network": + atomic.AddInt64(&mc.ErrorMetrics.NetworkErrors, 1) + case "database": + atomic.AddInt64(&mc.ErrorMetrics.DatabaseErrors, 1) + case "business": + atomic.AddInt64(&mc.ErrorMetrics.BusinessErrors, 1) + default: + atomic.AddInt64(&mc.ErrorMetrics.SystemErrors, 1) + } + + // 记录最近错误 + errorRecord := ErrorRecord{ + Timestamp: time.Now(), + Type: errorType, + Message: message, + Context: context, + } + + // 保持最近100个错误 + if len(mc.ErrorMetrics.RecentErrors) >= 100 { + mc.ErrorMetrics.RecentErrors = mc.ErrorMetrics.RecentErrors[1:] + } + mc.ErrorMetrics.RecentErrors = append(mc.ErrorMetrics.RecentErrors, errorRecord) + + mc.ErrorMetrics.LastUpdated = time.Now() +} + +// RecordRetry 记录重试 +func (mc *MetricsCollector) RecordRetry(success bool) { + atomic.AddInt64(&mc.ErrorMetrics.RetryAttempts, 1) + if success { + atomic.AddInt64(&mc.ErrorMetrics.RetrySuccesses, 1) + } else { + atomic.AddInt64(&mc.ErrorMetrics.RetryFailures, 1) + } + mc.ErrorMetrics.LastUpdated = time.Now() +} + +// collectSystemMetrics 收集系统指标 +func (mc *MetricsCollector) collectSystemMetrics() { + mc.mu.Lock() + defer mc.mu.Unlock() + + // 获取内存统计 + var m runtime.MemStats + runtime.ReadMemStats(&m) + + mc.SystemMetrics.MemoryUsage = m.Alloc + mc.SystemMetrics.GoroutineCount = runtime.NumGoroutine() + mc.SystemMetrics.GcCount = m.NumGC + mc.SystemMetrics.GcPauseMs = uint64(m.PauseNs[(m.NumGC+255)%256]) / 1000000 + + mc.SystemMetrics.LastUpdated = time.Now() +} + +// checkAlerts 检查告警 +func (mc *MetricsCollector) checkAlerts() { + thresholds := mc.config.MonitorConfig.AlertThresholds + + // 检查订单失败率 + if mc.OrderMetrics.TotalProcessed > 0 { + failureRate := float64(mc.OrderMetrics.FailedOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100 + if failureRate > thresholds.OrderFailureRate { + logger.Warnf("订单失败率告警: %.2f%% (阈值: %.2f%%)", failureRate, thresholds.OrderFailureRate) + } + } + + // 检查持仓同步失败率 + if mc.PositionMetrics.SyncAttempts > 0 { + syncFailureRate := float64(mc.PositionMetrics.SyncFailures) / float64(mc.PositionMetrics.SyncAttempts) * 100 + if syncFailureRate > thresholds.PositionSyncFailureRate { + logger.Warnf("持仓同步失败率告警: %.2f%% (阈值: %.2f%%)", syncFailureRate, thresholds.PositionSyncFailureRate) + } + } + + // 检查API失败率 + if mc.PerformanceMetrics.ApiCalls > 0 { + apiFailureRate := float64(mc.PerformanceMetrics.ApiErrors) / float64(mc.PerformanceMetrics.ApiCalls) * 100 + if apiFailureRate > thresholds.ApiFailureRate { + logger.Warnf("API失败率告警: %.2f%% (阈值: %.2f%%)", apiFailureRate, thresholds.ApiFailureRate) + } + } + + // 检查内存使用率 + if mc.SystemMetrics.MemoryPercent > thresholds.MemoryUsageThreshold { + logger.Warnf("内存使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.MemoryPercent, thresholds.MemoryUsageThreshold) + } + + // 检查CPU使用率 + if mc.SystemMetrics.CpuPercent > thresholds.CpuUsageThreshold { + logger.Warnf("CPU使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.CpuPercent, thresholds.CpuUsageThreshold) + } +} + +// GetMetrics 获取所有指标 +func (mc *MetricsCollector) GetMetrics() map[string]interface{} { + mc.mu.RLock() + defer mc.mu.RUnlock() + + return map[string]interface{}{ + "order_metrics": mc.OrderMetrics, + "position_metrics": mc.PositionMetrics, + "take_profit_metrics": mc.TakeProfitMetrics, + "performance_metrics": mc.PerformanceMetrics, + "error_metrics": mc.ErrorMetrics, + "system_metrics": mc.SystemMetrics, + "uptime": time.Since(mc.startTime).String(), + "collected_at": time.Now(), + } +} + +// GetSummary 获取指标摘要 +func (mc *MetricsCollector) GetSummary() map[string]interface{} { + mc.mu.RLock() + defer mc.mu.RUnlock() + + summary := make(map[string]interface{}) + + // 订单摘要 + if mc.OrderMetrics.TotalProcessed > 0 { + successRate := float64(mc.OrderMetrics.SuccessfulOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100 + avgResponseTime := mc.OrderMetrics.TotalResponseTime / time.Duration(mc.OrderMetrics.TotalProcessed) + + summary["order_success_rate"] = fmt.Sprintf("%.2f%%", successRate) + summary["avg_response_time"] = avgResponseTime.String() + summary["total_orders"] = mc.OrderMetrics.TotalProcessed + } + + // 持仓摘要 + if mc.PositionMetrics.SyncAttempts > 0 { + syncSuccessRate := float64(mc.PositionMetrics.SyncSuccesses) / float64(mc.PositionMetrics.SyncAttempts) * 100 + summary["position_sync_rate"] = fmt.Sprintf("%.2f%%", syncSuccessRate) + summary["position_mismatches"] = mc.PositionMetrics.PositionMismatches + } + + // 性能摘要 + summary["current_concurrency"] = mc.PerformanceMetrics.ConcurrentRequests + summary["max_concurrency"] = mc.PerformanceMetrics.MaxConcurrency + summary["total_api_calls"] = mc.PerformanceMetrics.ApiCalls + summary["total_db_queries"] = mc.PerformanceMetrics.DbQueries + + // 系统摘要 + summary["memory_usage_mb"] = mc.SystemMetrics.MemoryUsage / 1024 / 1024 + summary["goroutine_count"] = mc.SystemMetrics.GoroutineCount + summary["uptime"] = time.Since(mc.startTime).String() + + return summary +} + +// 全局指标收集器实例 +var GlobalMetricsCollector *MetricsCollector + +// InitMetricsCollector 初始化指标收集器 +func InitMetricsCollector(config *OptimizedConfig) { + GlobalMetricsCollector = NewMetricsCollector(config) + GlobalMetricsCollector.Start() +} + +// GetMetricsCollector 获取全局指标收集器 +func GetMetricsCollector() *MetricsCollector { + return GlobalMetricsCollector +} \ No newline at end of file diff --git a/services/binanceservice/reverse_service.go b/services/binanceservice/reverse_service.go index 881e004..d50524a 100644 --- a/services/binanceservice/reverse_service.go +++ b/services/binanceservice/reverse_service.go @@ -170,13 +170,15 @@ func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interfac switch { case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { - if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, false); err1 != nil { + if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, false); err1 != nil { + e.Log.Errorf("反向订单持仓保存失败: %v", err1) return true, err1 } } - case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SrgetELL": + case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { - if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, true); err1 != nil { + if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, true); err1 != nil { + e.Log.Errorf("反向订单持仓保存失败: %v", err1) return true, err1 } } @@ -307,6 +309,20 @@ func (e *ReverseService) SaveMainOrder(mapData map[string]interface{}, apiInfo D return reverseOrder, nil } +// savePositionWithRetry 带重试机制的持仓保存 +func (e *ReverseService) savePositionWithRetry(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) { + const maxRetries = 3 + for i := 0; i < maxRetries; i++ { + needReverseOrder, closePosition, err := e.savePosition(order, apiInfo, isMain, reducePosition) + if err == nil { + return needReverseOrder, closePosition, nil + } + e.Log.Warnf("持仓保存失败,重试 %d/%d: %v", i+1, maxRetries, err) + time.Sleep(time.Millisecond * 100 * time.Duration(i+1)) + } + return false, false, fmt.Errorf("持仓保存失败,已重试 %d 次", maxRetries) +} + // 更新仓位信息 // apiInfo: 当前下单api信息 // return @@ -364,25 +380,27 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo //平仓 if closePosition { totalNum = decimal.Zero - sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2 where id =@id and status!=2 " + sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2,version=version+1 where id =@id and status!=2 and version=@version" } else if reducePosition { //只减仓 - sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2 " + sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" } else { - sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2" + sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" } } else { querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" //减仓 判断是否为平仓 closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain) + // 关键修复:确保反向持仓状态正确更新 if closePosition { totalNum = decimal.Zero - sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2 where id =@id and reverse_status !=2" + // 增强的平仓更新逻辑,确保状态正确设置为2 + sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2,version=version+1 where id =@id and reverse_status !=2 and version=@version" } else if reducePosition { - sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice where id =@id and reverse_status !=2" + sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" } else { - sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice where id =@id and reverse_status !=2" + sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" } } @@ -390,22 +408,23 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo var remainQuantity decimal.Decimal err := e.Orm.Transaction(func(tx *gorm.DB) error { - err1 := tx.Model(&position).Where(querySql, - order.ApiId, positionSide, order.Symbol).First(&position).Error + err1 := tx.Model(&position).Where(querySql, + order.ApiId, positionSide, order.Symbol).First(&position).Error - if err1 != nil { - //主单仓位不存在,创建新仓位 - if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition { + if err1 != nil { + //主单仓位不存在,创建新仓位 + if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition { + // 初始化版本号 + position.Version = 0 + if err2 := tx.Create(&position).Error; err2 != nil { + return err2 + } - if err2 := tx.Create(&position).Error; err2 != nil { - return err2 + averagePrice = position.AveragePrice + } else { + return err1 } - - averagePrice = position.AveragePrice } else { - return err1 - } - } else { var totalAmount decimal.Decimal var totalPrice decimal.Decimal if !position.Amount.IsZero() && !position.AveragePrice.IsZero() { @@ -457,16 +476,17 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo return err2 } - dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id), sql.Named("averagePrice", averagePrice)) - if dbResult.Error != nil { - return dbResult.Error - } + // 使用乐观锁执行更新 + dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id), sql.Named("averagePrice", averagePrice), sql.Named("version", position.Version)) + if dbResult.Error != nil { + return dbResult.Error + } - order.PositionId = position.Id - if dbResult.RowsAffected == 0 { - e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, order) - return errors.New("没有找到对应的持仓信息") - } + order.PositionId = position.Id + if dbResult.RowsAffected == 0 { + e.Log.Errorf("减仓数据 是否平仓单:%v :%v, 可能存在并发冲突", closePosition, order) + return fmt.Errorf("没有找到对应的持仓信息或版本冲突,position_id:%d, version:%d", position.Id, position.Version) + } if reducePosition && !isMain { remainQuantity = position.ReverseAmount.Sub(totalNum) @@ -1052,145 +1072,3 @@ func getOrderType(t string) int { } return 2 } - -// // 重下止盈止损 -// // mapData: 主单止盈止损回调 -// func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error { -// orderType := 0 //订单类型 1-止盈 2-止损 -// side, err := maphelper.GetString(*mapData, "S") - -// if err != nil { -// return err -// } - -// ot, err := maphelper.GetString(*mapData, "ot") - -// if err != nil { -// return err -// } - -// //反单止盈止损方向相反 -// if side == "SELL" { -// side = "BUY" -// } else { -// side = "SELL" -// } - -// positionSide, err := maphelper.GetString(*mapData, "ps") - -// if err != nil { -// return err -// } - -// if positionSide == "LONG" { -// positionSide = "SHORT" -// } else { -// positionSide = "LONG" -// } - -// close := maphelper.GetBool(*mapData, "cp") -// stopPrice := maphelper.GetDecimal(*mapData, "sp") -// if stopPrice.IsZero() { -// e.Log.Errorf("获取止盈止损单触发价失败 symbol:%s custom:%s :%v", symbol, orderSn, err) -// return err -// } -// apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) - -// if err != nil { -// e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) -// return err -// } - -// switch ot { -// case "STOP_MARKET", "STOP": -// orderType = 2 -// case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": -// orderType = 1 -// default: -// return fmt.Errorf("不支持的订单类型 ot:%s", ot) -// } - -// var reversePosition DbModels.LineReversePosition - -// e.Orm.Model(&reversePosition). -// Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide). -// First(&reversePosition) - -// if reversePosition.Id == 0 { -// e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err) -// return err -// } - -// mainPercent := decimal.NewFromInt(1) - -// if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() { -// mainPercent = stopPrice.Div(reversePosition.AveragePrice) -// mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4) -// } - -// var percent decimal.Decimal -// switch { -// //做多止损 -// case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT": -// percent = decimal.NewFromInt(1).Sub(mainPercent) -// case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG": -// percent = decimal.NewFromInt(1).Add(mainPercent) -// default: -// return fmt.Errorf("不支持的订单类型 ot:%s, ps:%s", ot, positionSide) -// } - -// now := time.Now() -// price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit)) -// lastPrice, _ := decimal.NewFromString(symbol.LastPrice) -// newOrder := DbModels.LineReverseOrder{ -// PositionId: reversePosition.Id, -// OrderSn: helper.GetOrderNo(), -// OrderType: orderType, -// Status: 1, -// Price: price, -// TotalNum: reversePosition.TotalReverseAmount, -// Symbol: symbol.GetSymbol(), -// Side: side, -// PositionSide: positionSide, -// FollowOrderSn: orderSn, -// Type: ot, -// SignPrice: lastPrice, -// Category: 1, -// ApiId: apiInfo.Id, -// IsAddPosition: 2, -// TriggerTime: &now, -// BuyPrice: reversePosition.TotalReverseAmount.Mul(price).Truncate(int32(symbol.PriceDigit)), -// } - -// if err1 := e.Orm.Create(&newOrder).Error; err1 != nil { -// e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", symbol, orderSn, err1) -// return err1 -// } -// params := FutOrderPlace{ -// ApiId: apiInfo.Id, -// Symbol: symbol.GetSymbol(), -// PositionSide: newOrder.PositionSide, -// Side: newOrder.Side, -// OrderType: ot, -// Quantity: newOrder.TotalNum, -// Price: price, -// StopPrice: price, -// Profit: price, -// NewClientOrderId: newOrder.OrderSn, -// ClosePosition: close, -// } -// futApiV2 := FuturesResetV2{Service: e.Service} -// err = futApiV2.OrderPlaceLoop(&apiInfo, params) - -// if err != nil { -// e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", symbol.GetSymbol(), orderSn, err) - -// if err1 := e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil { -// e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", newOrder.Symbol, newOrder.OrderSn, err1) -// } -// return err -// } - -// e.DoCancelTakeProfitBatch(symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, &apiInfo) -// return nil -// } diff --git a/services/binanceservice/reverse_service_optimized.go b/services/binanceservice/reverse_service_optimized.go new file mode 100644 index 0000000..1c8b81b --- /dev/null +++ b/services/binanceservice/reverse_service_optimized.go @@ -0,0 +1,1121 @@ +package binanceservice + +import ( + "context" + "database/sql" + "errors" + "fmt" + DbModels "go-admin/app/admin/models" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/pkg/maphelper" + "go-admin/pkg/retryhelper" + "go-admin/pkg/utility/snowflakehelper" + "go-admin/services/cacheservice" + "strconv" + "sync" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +// ReverseServiceOptimized 优化后的反向下单服务 +type ReverseServiceOptimized struct { + service.Service + // 订单状态更新锁,防止并发状态覆盖 + orderStatusMutex sync.RWMutex + // 持仓更新锁,防止持仓数据竞态 + positionMutex sync.RWMutex + // 止盈止损订单取消锁 + takeProfitMutex sync.RWMutex +} + +// PositionSyncResult 持仓同步结果 +type PositionSyncResult struct { + ExchangePosition decimal.Decimal // 交易所实际持仓 + SystemPosition decimal.Decimal // 系统记录持仓 + NeedSync bool // 是否需要同步 + SyncError error // 同步错误 +} + +// OrderStatusUpdate 订单状态更新结构 +type OrderStatusUpdate struct { + OrderSn string + Status int + UpdateData map[string]interface{} + RetryCount int + LastAttempt time.Time +} + +// ReverseOrderOptimized 优化后的反向下单处理 +// 增强并发安全性和错误处理 +func (e *ReverseServiceOptimized) ReverseOrderOptimized(apiKey string, mapData map[string]interface{}) (bool, error) { + apiInfo := GetApiInfoByKey(apiKey) + if apiInfo.Id == 0 { + e.Log.Errorf("获取apiInfo失败 %s", apiKey) + return false, nil + } + + status, _ := maphelper.GetString(mapData, "X") + orderSn, err := maphelper.GetString(mapData, "c") + if err != nil { + return true, err + } + + // 使用Redis分布式锁确保同一订单号的处理原子性 + lockKey := fmt.Sprintf("reverse_order_lock:%s", orderSn) + lock := helper.NewRedisLock(lockKey, 30, 5, 100*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if acquired, err := lock.AcquireWait(ctx); err != nil || !acquired { + return false, fmt.Errorf("获取订单处理锁失败: %s, err: %v", orderSn, err) + } + defer lock.Release() + + switch status { + case "NEW", "CANCELED", "EXPIRED", "EXPIRED_IN_MATCH": + return e.handleSimpleStatusChangeOptimized(status, orderSn, mapData) + case "FILLED": + return e.handleFilledOrderOptimized(apiInfo, mapData) + default: + return false, fmt.Errorf("不支持的订单状态 %s", status) + } +} + +// handleSimpleStatusChangeOptimized 优化的简单状态变更处理 +func (e *ReverseServiceOptimized) handleSimpleStatusChangeOptimized(status string, orderSn string, mapData map[string]interface{}) (bool, error) { + statusMap := map[string]int{ + "NEW": 2, + "CANCELED": 6, + "EXPIRED": 7, + "EXPIRED_IN_MATCH": 7, + } + + if newStatus, ok := statusMap[status]; ok { + // 使用重试机制确保状态更新成功 + update := &OrderStatusUpdate{ + OrderSn: orderSn, + Status: newStatus, + UpdateData: mapData, + } + return false, e.updateOrderStatusWithRetry(update) + } + return false, fmt.Errorf("不支持的订单状态 %s", status) +} + +// handleFilledOrderOptimized 优化的成交订单处理 +func (e *ReverseServiceOptimized) handleFilledOrderOptimized(apiInfo DbModels.LineApiUser, mapData map[string]interface{}) (bool, error) { + if apiInfo.ReverseStatus != 1 || apiInfo.OpenStatus != 1 || apiInfo.ReverseApiId <= 0 { + return false, nil + } + + reverseApiInfo, err := GetApiInfo(apiInfo.ReverseApiId) + if err != nil { + e.Log.Errorf("获取反向api信息失败 reverseApiId:%d, err:%v", apiInfo.ReverseApiId, err) + return true, err + } + + mainOrder, err := e.SaveMainOrderOptimized(mapData, apiInfo) + if err != nil { + return false, err + } + + // 使用事务确保持仓和订单状态的一致性 + return e.processMainOrderWithTransaction(&mainOrder, &apiInfo, &reverseApiInfo) +} + +// SaveMainOrderOptimized 优化的主单保存 +func (e *ReverseServiceOptimized) SaveMainOrderOptimized(mapData map[string]interface{}, apiInfo DbModels.LineApiUser) (DbModels.LineReverseOrder, error) { + now := time.Now() + symbol, err := maphelper.GetString(mapData, "s") + var reverseOrder DbModels.LineReverseOrder + if err != nil { + return reverseOrder, err + } + + orderSn, err := maphelper.GetString(mapData, "c") + if err != nil { + return reverseOrder, err + } + + // 检查订单是否已存在,防止重复处理 + existingOrder := DbModels.LineReverseOrder{} + if err := e.Orm.Where("order_sn = ?", orderSn).First(&existingOrder).Error; err == nil { + e.Log.Warnf("订单已存在,跳过处理: %s", orderSn) + return existingOrder, nil + } + + side, err := maphelper.GetString(mapData, "S") + if err != nil { + return reverseOrder, err + } + + positionSide, err := maphelper.GetString(mapData, "ps") + if err != nil { + return reverseOrder, err + } + + mainType, _ := maphelper.GetString(mapData, "ot") + + reverseOrder = DbModels.LineReverseOrder{ + ApiId: apiInfo.Id, + Category: 0, + OrderSn: orderSn, + TriggerTime: &now, + Status: 3, + Symbol: symbol, + FollowOrderSn: "", + Type: mainType, + Price: maphelper.GetDecimal(mapData, "p"), + FinalPrice: maphelper.GetDecimal(mapData, "ap"), + TotalNum: maphelper.GetDecimal(mapData, "z"), + Side: side, + PositionSide: positionSide, + } + + reverseOrder.PriceU = reverseOrder.Price + + if !reverseOrder.Price.IsZero() && !reverseOrder.TotalNum.IsZero() { + reverseOrder.BuyPrice = reverseOrder.Price.Mul(reverseOrder.TotalNum) + } + + if id, err := maphelper.GetFloat64(mapData, "i"); err == nil { + reverseOrder.OrderId = strconv.FormatFloat(id, 'f', -1, 64) + } + + orderType, _ := maphelper.GetString(mapData, "ot") + switch orderType { + case "LIMIT", "MARKET": + reverseOrder.OrderType = 0 + case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": + reverseOrder.OrderType = 1 + case "STOP_MARKET", "STOP", "TRAILING_STOP_MARKET": + reverseOrder.OrderType = 2 + default: + return reverseOrder, fmt.Errorf("不支持的订单类型: %s", orderType) + } + + if reverseOrder.PositionSide == "BOTH" { + return reverseOrder, errors.New("不支持的持仓类型,必须为双向持仓") + } + + // 使用事务保存订单 + err = e.Orm.Transaction(func(tx *gorm.DB) error { + return tx.Create(&reverseOrder).Error + }) + + if err != nil { + e.Log.Errorf("保存主单失败:%v", err) + return reverseOrder, err + } + + return reverseOrder, nil +} + +// processMainOrderWithTransaction 在事务中处理主单 +func (e *ReverseServiceOptimized) processMainOrderWithTransaction(mainOrder *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, reverseApiInfo *DbModels.LineApiUser) (bool, error) { + var needReverseOrder bool + var closePosition bool + var err error + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + // 在事务中处理持仓更新 + switch { + case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", + mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": + if mainOrder.Category == 0 { + needReverseOrder, closePosition, err = e.savePositionOptimized(tx, mainOrder, apiInfo, true, false) + if err != nil { + return err + } + } + case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", + mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": + if mainOrder.Category == 0 { + needReverseOrder, closePosition, err = e.savePositionOptimized(tx, mainOrder, apiInfo, true, true) + if err != nil { + e.Log.Errorf("保存主订单失败: %v", err) + return err + } + } + default: + return errors.New("不支持的订单类型") + } + return nil + }) + + if err != nil { + return false, err + } + + // 事务成功后,异步处理反向下单 + if needReverseOrder { + go func() { + if err := e.DoAddReverseOrderOptimized(mainOrder, reverseApiInfo, apiInfo.OrderProportion, false, closePosition); err != nil { + e.Log.Errorf("异步反向下单失败: %v", err) + } + }() + } + + return true, nil +} + +// savePositionOptimized 优化的持仓保存,增强并发安全性 +func (e *ReverseServiceOptimized) savePositionOptimized(tx *gorm.DB, order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) { + e.positionMutex.Lock() + defer e.positionMutex.Unlock() + + position := DbModels.LineReversePosition{} + positionSide := order.PositionSide + side := order.Side + totalNum := order.TotalNum + closePosition := false + needReverseOrder := false + + symbol, err1 := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) + if err1 != nil { + e.Log.Errorf("获取交易对失败 symbol:%s err:%v", order.Symbol, err1) + } + + var querySql string + sqlStr := "" + + // 持仓同步检查 + syncResult := e.syncPositionWithExchange(apiInfo, order.Symbol, order.PositionSide) + if syncResult.NeedSync { + e.Log.Warnf("检测到持仓不同步,交易所持仓: %s, 系统持仓: %s", + syncResult.ExchangePosition.String(), syncResult.SystemPosition.String()) + } + + // 如果是主单,存储仓位则是反单的持仓方向 + if isMain { + if order.PositionSide == "LONG" { + positionSide = "SHORT" + } else { + positionSide = "LONG" + } + + // 减仓判断是否为平仓 + closePosition = e.getClosePositionOptimized(reducePosition, apiInfo, order, order.PositionSide, isMain) + + if !reducePosition { + // 反单止盈止损方向相反 + if side == "SELL" { + side = "BUY" + } else { + side = "SELL" + } + + position.ReverseApiId = apiInfo.ReverseApiId + position.Side = side + position.ApiId = order.ApiId + position.Symbol = order.Symbol + position.Status = 1 + position.ReverseStatus = 0 + position.PositionSide = positionSide + position.AveragePrice = order.FinalPrice + position.PositionNo = snowflakehelper.GetOrderNo() + } + querySql = "api_id =? and position_side =? and symbol =? and status =1" + + // 使用乐观锁防止并发更新冲突 + if closePosition { + totalNum = decimal.Zero + sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2,version=version+1 where id =@id and status!=2 and version=@version" + } else if reducePosition { + sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" + } else { + sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" + } + } else { + querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" + closePosition = e.getClosePositionOptimized(reducePosition, apiInfo, order, order.PositionSide, isMain) + + if closePosition { + totalNum = decimal.Zero + sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2,version=version+1 where id =@id and reverse_status !=2 and version=@version" + } else if reducePosition { + sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" + } else { + sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" + } + } + + var averagePrice decimal.Decimal + var remainQuantity decimal.Decimal + var currentVersion int + + // 获取当前持仓记录 + err1 = tx.Model(&position).Where(querySql, order.ApiId, positionSide, order.Symbol).First(&position).Error + if err1 != nil { + // 主单仓位不存在,创建新仓位 + if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition { + if err2 := tx.Create(&position).Error; err2 != nil { + return false, false, err2 + } + averagePrice = position.AveragePrice + } else { + return false, false, err1 + } + } else { + currentVersion = position.Version + // 计算平均价格逻辑保持不变 + var totalAmount decimal.Decimal + var totalPrice decimal.Decimal + if !position.Amount.IsZero() && !position.AveragePrice.IsZero() { + if isMain { + totalPrice = position.Amount.Mul(position.AveragePrice) + totalAmount = position.Amount + } else { + totalPrice = position.ReverseAmount.Mul(position.ReverseAveragePrice) + totalAmount = position.ReverseAmount + } + + if !reducePosition { + totalPrice = totalPrice.Add(order.Price.Mul(order.TotalNum)) + totalAmount = totalAmount.Add(order.TotalNum) + } else if reducePosition && !closePosition { + totalPrice = totalPrice.Sub(order.Price.Mul(order.TotalNum)) + totalAmount = totalAmount.Sub(order.TotalNum) + } + } + + if totalAmount.IsZero() || totalPrice.IsZero() { + if isMain { + averagePrice = position.AveragePrice + } else { + if position.ReverseAveragePrice.IsZero() { + averagePrice = order.Price + } else { + averagePrice = position.ReverseAveragePrice + } + } + } else { + if closePosition { + if isMain { + averagePrice = position.AveragePrice + } else { + averagePrice = position.ReverseAveragePrice + } + } else { + averagePrice = totalPrice.Div(totalAmount).Truncate(int32(symbol.PriceDigit)) + } + } + } + + // 关联订单的仓位id + if err2 := tx.Exec("UPDATE line_reverse_order set position_id=@positionId where id=@orderId and position_id = 0", + sql.Named("positionId", position.Id), sql.Named("orderId", order.Id)).Error; err2 != nil { + return false, false, err2 + } + + // 使用版本号进行乐观锁更新 + dbResult := tx.Exec(sqlStr, + sql.Named("totalNum", totalNum), + sql.Named("id", position.Id), + sql.Named("averagePrice", averagePrice), + sql.Named("version", currentVersion)) + + if dbResult.Error != nil { + return false, false, dbResult.Error + } + + order.PositionId = position.Id + if dbResult.RowsAffected == 0 { + e.Log.Errorf("持仓更新失败,可能存在并发冲突 closePosition:%v order:%v", closePosition, order) + return false, false, errors.New("持仓更新失败,可能存在并发冲突") + } + + if reducePosition && !isMain { + remainQuantity = position.ReverseAmount.Sub(totalNum) + } else if !isMain { + remainQuantity = position.ReverseAmount.Add(totalNum) + } + + // 主单且对手单没有平仓 + if isMain && position.ReverseStatus != 2 { + needReverseOrder = true + } + + // 异步处理止盈止损 + if !isMain && !closePosition { + go func() { + if err := e.doDefaultTakeStopOptimized(order, apiInfo, remainQuantity); err != nil { + e.Log.Errorf("设置默认止盈止损失败: %v", err) + } + }() + } else if !isMain && closePosition { + // 异步取消剩余委托 + go func() { + if err := e.DoCancelTakeAndStopOptimized(order.Symbol, position.PositionSide, apiInfo); err != nil { + e.Log.Errorf("取消止盈止损订单失败: %v", err) + } + }() + } + + return needReverseOrder, closePosition, nil +} + +// syncPositionWithExchange 同步交易所持仓数据 +func (e *ReverseServiceOptimized) syncPositionWithExchange(apiInfo *DbModels.LineApiUser, symbol, positionSide string) *PositionSyncResult { + result := &PositionSyncResult{} + + // 获取交易所实际持仓 + futApi := FutRestApi{} + holdData := HoldeData{} + err := futApi.GetPositionData(apiInfo, symbol, positionSide, &holdData) + if err != nil { + result.SyncError = err + return result + } + result.ExchangePosition = holdData.TotalQuantity + + // 获取系统记录的持仓 + position := DbModels.LineReversePosition{} + err = e.Orm.Where("api_id = ? AND position_side = ? AND symbol = ? AND status = 1", + apiInfo.Id, positionSide, symbol).First(&position).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + result.SystemPosition = decimal.Zero + } else { + result.SyncError = err + return result + } + } else { + result.SystemPosition = position.Amount + } + + // 判断是否需要同步(允许小幅误差) + diff := result.ExchangePosition.Sub(result.SystemPosition).Abs() + threshold := decimal.NewFromFloat(0.001) // 0.001的误差阈值 + result.NeedSync = diff.GreaterThan(threshold) + + return result +} + +// getClosePositionOptimized 优化的平仓判断 +func (e *ReverseServiceOptimized) getClosePositionOptimized(reducePosition bool, apiInfo *DbModels.LineApiUser, order *DbModels.LineReverseOrder, positionSide string, isMain bool) bool { + closePosition := false + + if reducePosition { + // 使用重试机制获取持仓数据 + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = time.Second + + holdData, err := retryhelper.RetryWithResult(func() (*HoldeData, error) { + futApi := FutRestApi{} + holdData := &HoldeData{} + err := futApi.GetPositionData(apiInfo, order.Symbol, positionSide, holdData) + return holdData, err + }, opts) + + if err != nil { + e.Log.Errorf("获取剩余持仓信息失败 symbol:%s err:%v", order.Symbol, err) + // 降级到数据库查询 + lastPosition := DbModels.LineReversePosition{} + if err2 := e.Orm.Model(&DbModels.LineReversePosition{}).Where("position_side =? and symbol =? and status =1", positionSide, order.Symbol).First(&lastPosition).Error; err2 != nil { + e.Log.Errorf("获取上一次持仓信息失败 symbol:%s err:%v", order.Symbol, err2) + } else if isMain && lastPosition.Amount.Cmp(order.TotalNum) <= 0 { + closePosition = true + } else if !isMain && lastPosition.ReverseAmount.Cmp(order.TotalNum) <= 0 { + closePosition = true + } + } else if holdData.TotalQuantity.IsZero() { + closePosition = true + } + } + + return closePosition +} + +// updateOrderStatusWithRetry 带重试的订单状态更新 +func (e *ReverseServiceOptimized) updateOrderStatusWithRetry(update *OrderStatusUpdate) error { + e.orderStatusMutex.Lock() + defer e.orderStatusMutex.Unlock() + + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = 500 * time.Millisecond + + return retryhelper.Retry(func() error { + data := map[string]interface{}{ + "status": update.Status, + "updated_at": time.Now(), + } + + if update.Status == 3 { + now := time.Now() + if orderId, ok := update.UpdateData["i"].(float64); ok { + data["order_id"] = orderId + } + if ap, ok := update.UpdateData["ap"].(string); ok { + data["final_price"], _ = decimal.NewFromString(ap) + } + if num, ok := update.UpdateData["z"].(string); ok { + data["total_num"], _ = decimal.NewFromString(num) + } + data["trigger_time"] = &now + } else if update.Status == 2 { + if orderId, ok := update.UpdateData["i"].(float64); ok { + data["order_id"] = orderId + } + } + + db := e.Orm.Model(&DbModels.LineReverseOrder{}). + Where("order_sn = ? and status != 3", update.OrderSn). + Updates(data) + + if db.Error != nil { + e.Log.Errorf("修改订单状态失败 orderSn:%s, err:%v", update.OrderSn, db.Error) + return db.Error + } + + if db.RowsAffected == 0 { + e.Log.Warnf("修改订单状态失败 orderSn:%s, 未找到订单或状态未变更", update.OrderSn) + } + return nil + }, opts) +} + +// DoAddReverseOrderOptimized 优化的反向下单 +func (e *ReverseServiceOptimized) DoAddReverseOrderOptimized(mainOrder *DbModels.LineReverseOrder, reverseApiInfo *DbModels.LineApiUser, orderProportion decimal.Decimal, reducePosition, closePosition bool) error { + // 使用分布式锁防止重复下单 + lockKey := fmt.Sprintf("reverse_add_order:%s", mainOrder.OrderSn) + lock := helper.NewRedisLock(lockKey, 30, 5, 100*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if acquired, err := lock.AcquireWait(ctx); err != nil || !acquired { + return fmt.Errorf("获取反向下单锁失败: %s, err: %v", mainOrder.OrderSn, err) + } + defer lock.Release() + + // 检查是否已经存在反向订单 + existingOrder := DbModels.LineReverseOrder{} + if err := e.Orm.Where("follow_order_sn = ? AND category = 1", mainOrder.OrderSn).First(&existingOrder).Error; err == nil { + e.Log.Warnf("反向订单已存在,跳过处理: %s", mainOrder.OrderSn) + return nil + } + + order := DbModels.LineReverseOrder{} + order.ApiId = reverseApiInfo.Id + order.Category = 1 + order.OrderSn = helper.GetOrderNo() + order.FollowOrderSn = mainOrder.OrderSn + order.Symbol = mainOrder.Symbol + order.OrderType = 0 + + // 设置持仓方向和交易方向 + switch mainOrder.PositionSide { + case "LONG": + order.PositionSide = "SHORT" + case "SHORT": + order.PositionSide = "LONG" + } + + switch mainOrder.Side { + case "SELL": + order.Side = "BUY" + case "BUY": + order.Side = "SELL" + default: + return fmt.Errorf("不支持的订单类型 side:%s", mainOrder.Side) + } + + if reducePosition && closePosition { + order.OrderType = 4 + } else if reducePosition { + order.OrderType = 3 + } + + symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, mainOrder.Symbol, 1) + if err != nil { + e.Log.Errorf("获取交易对信息失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) + return err + } + + setting, err := cacheservice.GetReverseSetting(e.Orm) + if err != nil { + e.Log.Errorf("获取反单设置失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) + return err + } + + // 获取最新价格 + signPrice, _ := decimal.NewFromString(symbol.LastPrice) + price := signPrice.Truncate(int32(symbol.PriceDigit)) + + if price.Cmp(decimal.Zero) <= 0 { + e.Log.Errorf("获取最新价格失败 symbol:%s custom:%s 单价小于0", mainOrder.Symbol, mainOrder.OrderSn) + return errors.New("获取最新价格失败") + } + + // 计算溢价 + var percent decimal.Decimal + switch { + case order.PositionSide == "LONG" && order.Side == "BUY", order.PositionSide == "SHORT" && order.Side == "BUY": + percent = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio) + case order.PositionSide == "SHORT" && order.Side == "SELL", order.PositionSide == "LONG" && order.Side == "SELL": + percent = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio) + default: + return fmt.Errorf("不支持的订单类型 ps:%s, side:%s", order.PositionSide, order.Side) + } + + percent = percent.Div(decimal.NewFromInt(100)).Truncate(4) + price = price.Mul(percent).Truncate(int32(symbol.PriceDigit)) + + var amount decimal.Decimal + + // 计算下单数量 + if closePosition { + var position DbModels.LineReversePosition + if err1 := e.Orm.Model(position). + Where("position_side = ? and reverse_api_id = ? and reverse_status = 1", order.PositionSide, order.ApiId). + Select("reverse_amount"). + Find(&position).Error; err1 != nil { + e.Log.Errorf("获取剩余仓位失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err1) + if len(err1.Error()) < 255 { + order.Remark = err1.Error() + } else { + order.Remark = err1.Error()[:254] + } + } + amount = position.ReverseAmount + if amount.IsZero() { + order.Remark = "没有持仓数量" + } + } else { + proportion := decimal.NewFromInt(100) + if orderProportion.Cmp(decimal.Zero) > 0 { + proportion = orderProportion + } + proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(4) + amount = mainOrder.TotalNum.Mul(proportion).Truncate(int32(symbol.AmountDigit)) + + logger.Info("反向下单比例 %s ,原始数量:%s,反向下单数量:%s", + proportion.String(), mainOrder.TotalNum.String(), amount.String()) + + if amount.Cmp(decimal.Zero) <= 0 { + e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn) + return errors.New("计算数量失败") + } + } + + order.TotalNum = amount + order.Price = price + order.PriceU = price + order.BuyPrice = amount.Mul(price).Truncate(int32(symbol.PriceDigit)) + order.Status = 1 + order.Type = setting.ReverseOrderType + order.SignPrice = signPrice + order.PositionId = mainOrder.PositionId + + if order.Remark != "" { + order.Status = 8 + } + + // 保存反向订单 + if err := e.Orm.Model(&order).Create(&order).Error; err != nil { + e.Log.Errorf("保存反单失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) + return err + } + + // 如果状态正常,进行下单 + if order.Status == 1 { + err = e.DoBianceOrderOptimized(&order, reverseApiInfo, &setting, reducePosition, closePosition) + // 下单成功且为平仓单时,取消止盈止损 + if err == nil && closePosition { + go func() { + if err := e.DoCancelTakeProfitBatchOptimized(symbol.GetSymbol(), order.PositionSide, order.Side, 1, reverseApiInfo); err != nil { + e.Log.Errorf("取消止盈止损订单失败: %v", err) + } + }() + } + } + + return err +} + +// DoBianceOrderOptimized 优化的币安下单 +func (e *ReverseServiceOptimized) DoBianceOrderOptimized(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, setting *DbModels.LineReverseSetting, reducePosition, closePosition bool) error { + // 使用重试机制进行下单 + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = time.Second + opts.RetryableErrFn = func(err error) bool { + // 某些错误不需要重试 + if err != nil { + errStr := err.Error() + // 余额不足、订单重复等错误不重试 + if contains(errStr, []string{"余额不足", "订单重复", "API-key", "无效"}) { + return false + } + } + return true + } + + return retryhelper.Retry(func() error { + futApiV2 := FutRestApi{} + params := FutOrderPlace{ + ApiId: apiInfo.Id, + Symbol: order.Symbol, + Side: order.Side, + PositionSide: order.PositionSide, + OrderType: setting.ReverseOrderType, + Quantity: order.TotalNum, + Price: order.Price, + NewClientOrderId: order.OrderSn, + } + + if err := futApiV2.OrderPlaceLoop(e.Orm, params, 3); err != nil { + e.Log.Errorf("币安下单失败 orderSn:%s, err:%v", order.OrderSn, err) + // 更新订单状态为失败 + e.Orm.Model(&DbModels.LineReverseOrder{}). + Where("id = ?", order.Id). + Updates(map[string]interface{}{ + "status": 8, + "remark": err.Error()[:min(len(err.Error()), 254)], + "updated_at": time.Now(), + }) + return err + } + return nil + }, opts) +} + +// DoCancelTakeProfitBatchOptimized 优化的批量取消止盈止损订单 +func (e *ReverseServiceOptimized) DoCancelTakeProfitBatchOptimized(symbol, positionSide, side string, orderType int, apiInfo *DbModels.LineApiUser) error { + e.takeProfitMutex.Lock() + defer e.takeProfitMutex.Unlock() + + // 获取需要取消的订单 + orderSnList := e.GetTakeProfitBatchOptimized(symbol, positionSide, side, apiInfo.Id, orderType) + if len(orderSnList) == 0 { + return nil + } + + // 分批取消,每次最多10个 + batchSize := 10 + for i := 0; i < len(orderSnList); i += batchSize { + end := i + batchSize + if end > len(orderSnList) { + end = len(orderSnList) + } + batch := orderSnList[i:end] + + // 使用重试机制取消订单 + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = time.Second + + err := retryhelper.Retry(func() error { + futApi := FutRestApi{} + return futApi.CancelBatchFutOrder(*apiInfo, symbol, batch) + }, opts) + + if err != nil { + e.Log.Errorf("批量取消止盈止损订单失败 symbol:%s, batch:%v, err:%v", symbol, batch, err) + // 继续处理下一批,不因为一批失败而中断 + continue + } + + e.Log.Infof("成功取消止盈止损订单 symbol:%s, count:%d", symbol, len(batch)) + } + + return nil +} + +// GetTakeProfitBatchOptimized 优化的获取止盈止损订单列表 +func (e *ReverseServiceOptimized) GetTakeProfitBatchOptimized(symbol, positionSide, side string, apiId int, orderType int) []string { + var orders []DbModels.LineReverseOrder + query := e.Orm.Model(&DbModels.LineReverseOrder{}). + Where("symbol = ? AND position_side = ? AND side = ? AND api_id = ? AND order_type = ? AND status = 2", + symbol, positionSide, side, apiId, orderType). + Select("order_sn") + + if err := query.Find(&orders).Error; err != nil { + e.Log.Errorf("查询止盈止损订单失败: %v", err) + return nil + } + + orderSnList := make([]string, 0, len(orders)) + for _, order := range orders { + orderSnList = append(orderSnList, order.OrderSn) + } + + return orderSnList +} + +// DoCancelTakeAndStopOptimized 优化的取消止盈止损订单 +func (e *ReverseServiceOptimized) DoCancelTakeAndStopOptimized(symbol, positionSide string, apiInfo *DbModels.LineApiUser) error { + e.takeProfitMutex.Lock() + defer e.takeProfitMutex.Unlock() + + var orders []DbModels.LineReverseOrder + if err := e.Orm.Model(&DbModels.LineReverseOrder{}). + Where("symbol = ? AND position_side = ? AND api_id = ? AND order_type IN (1,2) AND status = 2", + symbol, positionSide, apiInfo.Id). + Select("order_sn"). + Find(&orders).Error; err != nil { + e.Log.Errorf("查询止盈止损订单失败: %v", err) + return err + } + + if len(orders) == 0 { + return nil + } + + orderSnList := make([]string, 0, len(orders)) + for _, order := range orders { + orderSnList = append(orderSnList, order.OrderSn) + } + + // 分批取消 + batchSize := 10 + for i := 0; i < len(orderSnList); i += batchSize { + end := i + batchSize + if end > len(orderSnList) { + end = len(orderSnList) + } + batch := orderSnList[i:end] + + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = time.Second + + err := retryhelper.Retry(func() error { + futApi := FutRestApi{} + return futApi.CancelBatchFutOrder(*apiInfo, symbol, batch) + }, opts) + + if err != nil { + e.Log.Errorf("批量取消止盈止损订单失败 symbol:%s, batch:%v, err:%v", symbol, batch, err) + } + } + + return nil +} + +// doDefaultTakeStopOptimized 优化的默认止盈止损设置 +func (e *ReverseServiceOptimized) doDefaultTakeStopOptimized(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error { + // 异步处理,避免阻塞主流程 + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // 使用重试机制 + opts := retryhelper.DefaultRetryOptions() + opts.MaxRetries = 3 + opts.InitialInterval = 2 * time.Second + + err := retryhelper.Retry(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return e.processDefaultTakeStop(order, apiInfo, totalNum) + } + }, opts) + + if err != nil { + e.Log.Errorf("设置默认止盈止损失败: %v", err) + } + }() + + return nil +} + +// processDefaultTakeStop 处理默认止盈止损逻辑 +func (e *ReverseServiceOptimized) processDefaultTakeStop(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error { + // 获取反单设置 + setting, err := cacheservice.GetReverseSetting(e.Orm) + if err != nil { + return fmt.Errorf("获取反单设置失败: %v", err) + } + + // 获取交易对信息 + symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) + if err != nil { + return fmt.Errorf("获取交易对信息失败: %v", err) + } + + // 获取当前价格 + lastPrice, _ := decimal.NewFromString(symbol.LastPrice) + if lastPrice.IsZero() { + return errors.New("获取最新价格失败") + } + + now := time.Now() + + // 设置止盈订单 + if setting.TakeProfitRatio.GreaterThan(decimal.Zero) { + takeProfitPrice := e.calculatePriceOptimized(order.PositionSide, lastPrice, setting.TakeProfitRatio, true) + takeProfitParams := CreateOrderParams{ + ApiInfo: apiInfo, + Order: order, + Symbol: &symbol, + Side: e.getOppositeSideOptimized(order.Side), + OrderType: "TAKE_PROFIT_MARKET", + Price: takeProfitPrice, + TotalNum: totalNum, + Now: now, + LastPrice: lastPrice, + Close: false, + PositionId: order.PositionId, + OrderSn: helper.GetOrderNo(), + PositionSide: order.PositionSide, + } + + if err := e.createReverseOrderOptimized(takeProfitParams); err != nil { + e.Log.Errorf("创建止盈订单失败: %v", err) + } + } + + // 设置止损订单 + if setting.StopLossRatio.GreaterThan(decimal.Zero) { + stopLossPrice := e.calculatePriceOptimized(order.PositionSide, lastPrice, setting.StopLossRatio, false) + stopLossParams := CreateOrderParams{ + ApiInfo: apiInfo, + Order: order, + Symbol: &symbol, + Side: e.getOppositeSideOptimized(order.Side), + OrderType: "STOP_MARKET", + Price: stopLossPrice, + TotalNum: totalNum, + Now: now, + LastPrice: lastPrice, + Close: false, + PositionId: order.PositionId, + OrderSn: helper.GetOrderNo(), + PositionSide: order.PositionSide, + } + + if err := e.createReverseOrderOptimized(stopLossParams); err != nil { + e.Log.Errorf("创建止损订单失败: %v", err) + } + } + + return nil +} + +// calculatePriceOptimized 优化的价格计算 +func (e *ReverseServiceOptimized) calculatePriceOptimized(positionSide string, base decimal.Decimal, ratio decimal.Decimal, isTakeProfit bool) decimal.Decimal { + var multiplier decimal.Decimal + if isTakeProfit { + if positionSide == "LONG" { + multiplier = decimal.NewFromInt(1).Add(ratio.Div(decimal.NewFromInt(100))) + } else { + multiplier = decimal.NewFromInt(1).Sub(ratio.Div(decimal.NewFromInt(100))) + } + } else { + if positionSide == "LONG" { + multiplier = decimal.NewFromInt(1).Sub(ratio.Div(decimal.NewFromInt(100))) + } else { + multiplier = decimal.NewFromInt(1).Add(ratio.Div(decimal.NewFromInt(100))) + } + } + return base.Mul(multiplier) +} + +// getOppositeSideOptimized 获取相反的交易方向 +func (e *ReverseServiceOptimized) getOppositeSideOptimized(side string) string { + if side == "BUY" { + return "SELL" + } + return "BUY" +} + +// CreateOrderParams 创建订单参数 +// CreateOrderParams 已在 reverse_service.go 中定义 + +// createReverseOrderOptimized 优化的创建反向订单 +func (e *ReverseServiceOptimized) createReverseOrderOptimized(params CreateOrderParams) error { + // 使用事务确保数据一致性 + return e.Orm.Transaction(func(tx *gorm.DB) error { + reverseOrder := DbModels.LineReverseOrder{ + ApiId: params.ApiInfo.Id, + Category: 1, + OrderSn: params.OrderSn, + FollowOrderSn: params.Order.OrderSn, + Symbol: params.Order.Symbol, + Side: params.Side, + PositionSide: params.PositionSide, + Price: params.Price, + PriceU: params.Price, + TotalNum: params.TotalNum, + BuyPrice: params.Price.Mul(params.TotalNum), + SignPrice: params.LastPrice, + Status: 1, + PositionId: params.PositionId, + TriggerTime: ¶ms.Now, + } + + // 设置订单类型 + switch params.OrderType { + case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": + reverseOrder.OrderType = 1 + reverseOrder.Type = "TAKE_PROFIT_MARKET" + case "STOP_MARKET", "STOP": + reverseOrder.OrderType = 2 + reverseOrder.Type = "STOP_MARKET" + default: + return fmt.Errorf("不支持的订单类型: %s", params.OrderType) + } + + // 保存订单到数据库 + if err := tx.Create(&reverseOrder).Error; err != nil { + e.Log.Errorf("保存反向订单失败: %v", err) + return err + } + + // 下单到交易所 + futApi := FutRestApi{} + orderParams := FutOrderPlace{ + ApiId: params.ApiInfo.Id, + Symbol: reverseOrder.Symbol, + Side: reverseOrder.Side, + PositionSide: reverseOrder.PositionSide, + OrderType: "MARKET", + Quantity: reverseOrder.TotalNum, + StopPrice: reverseOrder.Price, + NewClientOrderId: reverseOrder.OrderSn, + } + + if err := futApi.OrderPlaceLoop(tx, orderParams, 3); err != nil { + e.Log.Errorf("下单失败: %v", err) + // 更新订单状态为失败 + tx.Model(&reverseOrder).Updates(map[string]interface{}{ + "status": 8, + "remark": err.Error()[:min(len(err.Error()), 254)], + "updated_at": time.Now(), + }) + return err + } + + return nil + }) +} + +// contains 检查字符串是否包含任意一个子字符串 +func contains(s string, substrs []string) bool { + for _, substr := range substrs { + if len(substr) > 0 && len(s) >= len(substr) { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + } + } + return false +} + +// min 返回两个整数中的较小值 +func min(a, b int) int { + if a < b { + return a + } + return b +} \ No newline at end of file diff --git a/services/binanceservice/transaction_manager_optimized.go b/services/binanceservice/transaction_manager_optimized.go new file mode 100644 index 0000000..e97c3cd --- /dev/null +++ b/services/binanceservice/transaction_manager_optimized.go @@ -0,0 +1,457 @@ +package binanceservice + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/go-admin-team/go-admin-core/logger" + "gorm.io/gorm" +) + +// TransactionManager 事务管理器 +type TransactionManager struct { + db *gorm.DB + metrics *MetricsCollector + config *OptimizedConfig +} + +// TransactionConfig 事务配置 +type TransactionConfig struct { + Timeout time.Duration `json:"timeout"` + RetryAttempts int `json:"retry_attempts"` + RetryDelay time.Duration `json:"retry_delay"` + IsolationLevel string `json:"isolation_level"` + ReadOnly bool `json:"read_only"` +} + +// TransactionContext 事务上下文 +type TransactionContext struct { + Tx *gorm.DB + StartTime time.Time + Config TransactionConfig + Context context.Context +} + +// TransactionResult 事务结果 +type TransactionResult struct { + Success bool `json:"success"` + Duration time.Duration `json:"duration"` + Error error `json:"error"` + Retries int `json:"retries"` + Timestamp time.Time `json:"timestamp"` +} + +// NewTransactionManager 创建事务管理器 +func NewTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) *TransactionManager { + return &TransactionManager{ + db: db, + metrics: metrics, + config: config, + } +} + +// WithTransaction 在事务中执行操作 +func (tm *TransactionManager) WithTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult { + startTime := time.Now() + result := &TransactionResult{ + Timestamp: startTime, + } + + // 设置默认配置 + if config.Timeout == 0 { + config.Timeout = 30 * time.Second // 默认事务超时时间 + } + if config.RetryAttempts == 0 { + config.RetryAttempts = tm.config.RetryConfig.MaxRetries + } + if config.RetryDelay == 0 { + config.RetryDelay = tm.config.RetryConfig.RetryDelay + } + + // 执行事务(带重试) + for attempt := 1; attempt <= config.RetryAttempts; attempt++ { + err := tm.executeTransaction(ctx, config, operation) + if err == nil { + result.Success = true + result.Duration = time.Since(startTime) + result.Retries = attempt - 1 + break + } + + result.Error = err + result.Retries = attempt + + // 检查是否可重试 + if !tm.isRetryableError(err) || attempt == config.RetryAttempts { + break + } + + // 等待重试 + logger.Warnf("事务执行失败,第 %d 次重试: %v", attempt, err) + time.Sleep(config.RetryDelay * time.Duration(attempt)) + } + + result.Duration = time.Since(startTime) + + // 记录指标 + if tm.metrics != nil { + tm.metrics.RecordDbOperation(true, result.Duration, result.Error) + } + + return result +} + +// executeTransaction 执行单次事务 +func (tm *TransactionManager) executeTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) error { + // 设置事务超时 + txCtx := ctx + if config.Timeout > 0 { + var cancel context.CancelFunc + txCtx, cancel = context.WithTimeout(ctx, config.Timeout) + defer cancel() + } + + // 开始事务 + tx := tm.db.WithContext(txCtx).Begin() + if tx.Error != nil { + return fmt.Errorf("开始事务失败: %w", tx.Error) + } + + // 设置事务隔离级别 + if config.IsolationLevel != "" { + if err := tx.Exec(fmt.Sprintf("SET TRANSACTION ISOLATION LEVEL %s", config.IsolationLevel)).Error; err != nil { + tx.Rollback() + return fmt.Errorf("设置事务隔离级别失败: %w", err) + } + } + + // 设置只读模式 + if config.ReadOnly { + if err := tx.Exec("SET TRANSACTION READ ONLY").Error; err != nil { + tx.Rollback() + return fmt.Errorf("设置只读事务失败: %w", err) + } + } + + // 创建事务上下文 + txContext := &TransactionContext{ + Tx: tx, + StartTime: time.Now(), + Config: config, + Context: txCtx, + } + + // 执行操作 + err := operation(txContext) + if err != nil { + // 回滚事务 + if rollbackErr := tx.Rollback().Error; rollbackErr != nil { + logger.Errorf("事务回滚失败: %v", rollbackErr) + return fmt.Errorf("操作失败且回滚失败: %w, 回滚错误: %v", err, rollbackErr) + } + return fmt.Errorf("事务操作失败: %w", err) + } + + // 提交事务 + if commitErr := tx.Commit().Error; commitErr != nil { + return fmt.Errorf("事务提交失败: %w", commitErr) + } + + return nil +} + +// WithOptimisticLock 使用乐观锁执行操作 +func (tm *TransactionManager) WithOptimisticLock(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult { + return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + // 在乐观锁模式下,我们需要在操作中检查版本号 + return operation(txCtx) + }) +} + +// SaveWithOptimisticLock 使用乐观锁保存数据 +func (tm *TransactionManager) SaveWithOptimisticLock(ctx context.Context, model interface{}, condition string, args ...interface{}) error { + config := TransactionConfig{ + Timeout: 30 * time.Second, + RetryAttempts: 3, + RetryDelay: 100 * time.Millisecond, + } + + result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + // 使用 WHERE 条件和版本号进行更新 + result := txCtx.Tx.Where(condition, args...).Save(model) + if result.Error != nil { + return result.Error + } + + // 检查是否有行被更新 + if result.RowsAffected == 0 { + return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改") + } + + return nil + }) + + return result.Error +} + +// UpdateWithOptimisticLock 使用乐观锁更新数据 +func (tm *TransactionManager) UpdateWithOptimisticLock(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error { + config := TransactionConfig{ + Timeout: 30 * time.Second, + RetryAttempts: 3, + RetryDelay: 100 * time.Millisecond, + } + + result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + // 增加版本号 + if _, hasVersion := updates["version"]; !hasVersion { + updates["version"] = gorm.Expr("version + 1") + } + + // 执行更新 + result := txCtx.Tx.Model(model).Where(condition, args...).Updates(updates) + if result.Error != nil { + return result.Error + } + + // 检查是否有行被更新 + if result.RowsAffected == 0 { + return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改") + } + + return nil + }) + + return result.Error +} + +// BatchOperation 批量操作 +func (tm *TransactionManager) BatchOperation(ctx context.Context, config TransactionConfig, operations []func(*TransactionContext) error) *TransactionResult { + return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error { + for i, operation := range operations { + if err := operation(txCtx); err != nil { + return fmt.Errorf("批量操作第 %d 步失败: %w", i+1, err) + } + } + return nil + }) +} + +// CreateSavepoint 创建保存点 +func (tm *TransactionManager) CreateSavepoint(txCtx *TransactionContext, name string) error { + return txCtx.Tx.Exec(fmt.Sprintf("SAVEPOINT %s", name)).Error +} + +// RollbackToSavepoint 回滚到保存点 +func (tm *TransactionManager) RollbackToSavepoint(txCtx *TransactionContext, name string) error { + return txCtx.Tx.Exec(fmt.Sprintf("ROLLBACK TO SAVEPOINT %s", name)).Error +} + +// ReleaseSavepoint 释放保存点 +func (tm *TransactionManager) ReleaseSavepoint(txCtx *TransactionContext, name string) error { + return txCtx.Tx.Exec(fmt.Sprintf("RELEASE SAVEPOINT %s", name)).Error +} + +// isRetryableError 判断错误是否可重试 +func (tm *TransactionManager) isRetryableError(err error) bool { + if err == nil { + return false + } + + errorMsg := err.Error() + + // 可重试的错误类型 + retryableErrors := []string{ + "deadlock", + "lock wait timeout", + "connection", + "timeout", + "temporary", + "乐观锁冲突", + } + + for _, retryableError := range retryableErrors { + if strings.Contains(errorMsg, retryableError) { + return true + } + } + + return false +} + +// GetTransactionStats 获取事务统计信息 +func (tm *TransactionManager) GetTransactionStats() map[string]interface{} { + stats := make(map[string]interface{}) + + // 从指标收集器获取数据库相关统计 + if tm.metrics != nil { + stats["total_transactions"] = tm.metrics.PerformanceMetrics.DbTransactions + stats["total_queries"] = tm.metrics.PerformanceMetrics.DbQueries + stats["total_errors"] = tm.metrics.PerformanceMetrics.DbErrors + stats["avg_query_time"] = tm.metrics.PerformanceMetrics.DbQueryTime + } + + return stats +} + +// 预定义的事务配置 + +// GetDefaultTransactionConfig 获取默认事务配置 +func GetDefaultTransactionConfig() TransactionConfig { + return TransactionConfig{ + Timeout: 30 * time.Second, + RetryAttempts: 3, + RetryDelay: 100 * time.Millisecond, + IsolationLevel: "READ COMMITTED", + ReadOnly: false, + } +} + +// GetReadOnlyTransactionConfig 获取只读事务配置 +func GetReadOnlyTransactionConfig() TransactionConfig { + return TransactionConfig{ + Timeout: 10 * time.Second, + RetryAttempts: 1, + RetryDelay: 0, + IsolationLevel: "READ COMMITTED", + ReadOnly: true, + } +} + +// GetLongRunningTransactionConfig 获取长时间运行事务配置 +func GetLongRunningTransactionConfig() TransactionConfig { + return TransactionConfig{ + Timeout: 300 * time.Second, // 5分钟 + RetryAttempts: 5, + RetryDelay: 500 * time.Millisecond, + IsolationLevel: "REPEATABLE READ", + ReadOnly: false, + } +} + +// GetCriticalTransactionConfig 获取关键事务配置 +func GetCriticalTransactionConfig() TransactionConfig { + return TransactionConfig{ + Timeout: 60 * time.Second, + RetryAttempts: 5, + RetryDelay: 200 * time.Millisecond, + IsolationLevel: "SERIALIZABLE", + ReadOnly: false, + } +} + +// 全局事务管理器实例 +var GlobalTransactionManager *TransactionManager + +// InitTransactionManager 初始化事务管理器 +func InitTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) { + GlobalTransactionManager = NewTransactionManager(db, metrics, config) +} + +// GetTransactionManager 获取全局事务管理器 +func GetTransactionManager() *TransactionManager { + return GlobalTransactionManager +} + +// WithDefaultTransaction 使用默认配置执行事务 +func WithDefaultTransaction(ctx context.Context, operation func(*TransactionContext) error) error { + if GlobalTransactionManager == nil { + return fmt.Errorf("事务管理器未初始化") + } + + config := GetDefaultTransactionConfig() + result := GlobalTransactionManager.WithTransaction(ctx, config, operation) + return result.Error +} + +// WithReadOnlyTransaction 使用只读配置执行事务 +func WithReadOnlyTransaction(ctx context.Context, operation func(*TransactionContext) error) error { + if GlobalTransactionManager == nil { + return fmt.Errorf("事务管理器未初始化") + } + + config := GetReadOnlyTransactionConfig() + result := GlobalTransactionManager.WithTransaction(ctx, config, operation) + return result.Error +} + +// WithCriticalTransaction 使用关键事务配置执行事务 +func WithCriticalTransaction(ctx context.Context, operation func(*TransactionContext) error) error { + if GlobalTransactionManager == nil { + return fmt.Errorf("事务管理器未初始化") + } + + config := GetCriticalTransactionConfig() + result := GlobalTransactionManager.WithTransaction(ctx, config, operation) + return result.Error +} + +// SaveWithOptimisticLockGlobal 全局乐观锁保存 +func SaveWithOptimisticLockGlobal(ctx context.Context, model interface{}, condition string, args ...interface{}) error { + if GlobalTransactionManager == nil { + return fmt.Errorf("事务管理器未初始化") + } + + return GlobalTransactionManager.SaveWithOptimisticLock(ctx, model, condition, args...) +} + +// UpdateWithOptimisticLockGlobal 全局乐观锁更新 +func UpdateWithOptimisticLockGlobal(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error { + if GlobalTransactionManager == nil { + return fmt.Errorf("事务管理器未初始化") + } + + return GlobalTransactionManager.UpdateWithOptimisticLock(ctx, model, updates, condition, args...) +} + +// TransactionHelper 事务辅助函数集合 +type TransactionHelper struct { + tm *TransactionManager +} + +// NewTransactionHelper 创建事务辅助器 +func NewTransactionHelper(tm *TransactionManager) *TransactionHelper { + return &TransactionHelper{tm: tm} +} + +// SavePosition 保存持仓(带乐观锁) +func (th *TransactionHelper) SavePosition(ctx context.Context, position interface{}, userID, symbol string, version int) error { + condition := "user_id = ? AND symbol = ? AND version = ?" + return th.tm.SaveWithOptimisticLock(ctx, position, condition, userID, symbol, version) +} + +// SaveOrder 保存订单(带乐观锁) +func (th *TransactionHelper) SaveOrder(ctx context.Context, order interface{}, orderID string, version int) error { + condition := "order_id = ? AND version = ?" + return th.tm.SaveWithOptimisticLock(ctx, order, condition, orderID, version) +} + +// UpdateOrderStatus 更新订单状态(带乐观锁) +func (th *TransactionHelper) UpdateOrderStatus(ctx context.Context, orderID, newStatus string, version int) error { + updates := map[string]interface{}{ + "status": newStatus, + "updated_at": time.Now(), + } + condition := "order_id = ? AND version = ?" + return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, orderID, version) +} + +// UpdatePositionQuantity 更新持仓数量(带乐观锁) +func (th *TransactionHelper) UpdatePositionQuantity(ctx context.Context, userID, symbol string, quantity float64, version int) error { + updates := map[string]interface{}{ + "quantity": quantity, + "updated_at": time.Now(), + } + condition := "user_id = ? AND symbol = ? AND version = ?" + return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, userID, symbol, version) +} + +// GetGlobalTransactionHelper 获取全局事务辅助器 +func GetGlobalTransactionHelper() *TransactionHelper { + if GlobalTransactionManager == nil { + return nil + } + return NewTransactionHelper(GlobalTransactionManager) +} \ No newline at end of file