From 031cc19c99b91756c5d33ab35618a66541acb940 Mon Sep 17 00:00:00 2001 From: 0xYYBB | ZYY | Bobo <128128010+the-dev-z@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:33:25 +0800 Subject: [PATCH] fix(api): use UUID to ensure traderID uniqueness (#893) (#1008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When multiple users create traders with the same exchange + AI model combination within the same second, they generate identical traderIDs, causing data conflicts. Old code (Line 496): ```go traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) ``` ## Solution Use UUID to guarantee 100% uniqueness while preserving prefix for debugging: ```go traderID := fmt.Sprintf("%s_%s_%s", req.ExchangeID, req.AIModelID, uuid.New().String()) ``` Example output: `binance_gpt-4_a1b2c3d4-e5f6-7890-abcd-ef1234567890` ## Changes - `api/server.go:495-497`: Use UUID for traderID generation - `api/traderid_test.go`: New test file with 3 comprehensive tests ## Tests ✅ All tests passed (0.189s) ✅ TestTraderIDUniqueness - 100 unique IDs generated ✅ TestTraderIDFormat - 3 exchange/model combinations validated ✅ TestTraderIDNoCollision - 1000 iterations without collision Fixes #893 Co-authored-by: the-dev-z --- api/server.go | 5 +- api/traderid_test.go | 123 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 api/traderid_test.go diff --git a/api/server.go b/api/server.go index 3aadcc24..daf3665e 100644 --- a/api/server.go +++ b/api/server.go @@ -492,8 +492,9 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } } - // 生成交易员ID - traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) + // 生成交易员ID (使用 UUID 确保唯一性,解决 Issue #893) + // 保留前缀以便调试和日志追踪 + traderID := fmt.Sprintf("%s_%s_%s", req.ExchangeID, req.AIModelID, uuid.New().String()) // 设置默认值 isCrossMargin := true // 默认为全仓模式 diff --git a/api/traderid_test.go b/api/traderid_test.go new file mode 100644 index 00000000..52b581c8 --- /dev/null +++ b/api/traderid_test.go @@ -0,0 +1,123 @@ +package api + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/uuid" +) + +// TestTraderIDUniqueness 测试 traderID 的唯一性(修复 Issue #893) +// 验证即使在相同的 exchange 和 AI model 下,也能生成唯一的 traderID +func TestTraderIDUniqueness(t *testing.T) { + exchangeID := "binance" + aiModelID := "gpt-4" + + // 模拟同时创建 100 个 trader(相同参数) + traderIDs := make(map[string]bool) + const numTraders = 100 + + for i := 0; i < numTraders; i++ { + // 模拟 api/server.go:497 的 traderID 生成逻辑 + traderID := generateTraderID(exchangeID, aiModelID) + + // ✅ 检查是否重复 + if traderIDs[traderID] { + t.Errorf("Duplicate traderID detected: %s", traderID) + } + traderIDs[traderID] = true + + // ✅ 验证格式:应该是 "exchange_model_uuid" + if !isValidTraderIDFormat(traderID, exchangeID, aiModelID) { + t.Errorf("Invalid traderID format: %s", traderID) + } + } + + // ✅ 验证生成了预期数量的唯一 ID + if len(traderIDs) != numTraders { + t.Errorf("Expected %d unique traderIDs, got %d", numTraders, len(traderIDs)) + } +} + +// generateTraderID 辅助函数,模拟 api/server.go 中的 traderID 生成逻辑 +func generateTraderID(exchangeID, aiModelID string) string { + return fmt.Sprintf("%s_%s_%s", exchangeID, aiModelID, uuid.New().String()) +} + +// isValidTraderIDFormat 验证 traderID 格式是否符合预期 +func isValidTraderIDFormat(traderID, expectedExchange, expectedModel string) bool { + // 格式:exchange_model_uuid + // 例如:binance_gpt-4_a1b2c3d4-e5f6-7890-abcd-ef1234567890 + parts := strings.Split(traderID, "_") + if len(parts) < 3 { + return false + } + + // 验证前缀 + if parts[0] != expectedExchange { + return false + } + + // AI model 可能包含连字符(如 gpt-4),所以需要重组 + // 最后一部分应该是 UUID + uuidPart := parts[len(parts)-1] + + // 验证 UUID 格式(36 个字符,包含 4 个连字符) + _, err := uuid.Parse(uuidPart) + return err == nil +} + +// TestTraderIDFormat 测试 traderID 格式的正确性 +func TestTraderIDFormat(t *testing.T) { + tests := []struct { + name string + exchangeID string + aiModelID string + }{ + {"Binance + GPT-4", "binance", "gpt-4"}, + {"Hyperliquid + Claude", "hyperliquid", "claude-3"}, + {"OKX + Qwen", "okx", "qwen-2.5"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + traderID := generateTraderID(tt.exchangeID, tt.aiModelID) + + // ✅ 验证包含正确的前缀 + if !strings.HasPrefix(traderID, tt.exchangeID+"_"+tt.aiModelID+"_") { + t.Errorf("traderID does not have correct prefix. Got: %s", traderID) + } + + // ✅ 验证格式有效 + if !isValidTraderIDFormat(traderID, tt.exchangeID, tt.aiModelID) { + t.Errorf("Invalid traderID format: %s", traderID) + } + + // ✅ 验证长度合理(至少应该有 exchange + model + "_" + UUID(36) 的长度) + minLength := len(tt.exchangeID) + len(tt.aiModelID) + 2 + 36 // 2个下划线 + 36字符UUID + if len(traderID) < minLength { + t.Errorf("traderID too short: expected at least %d chars, got %d", minLength, len(traderID)) + } + }) + } +} + +// TestTraderIDNoCollision 测试在高并发场景下不会产生碰撞 +func TestTraderIDNoCollision(t *testing.T) { + const iterations = 1000 + uniqueIDs := make(map[string]bool, iterations) + + // 模拟高并发场景 + for i := 0; i < iterations; i++ { + id := generateTraderID("binance", "gpt-4") + if uniqueIDs[id] { + t.Fatalf("Collision detected after %d iterations: %s", i+1, id) + } + uniqueIDs[id] = true + } + + if len(uniqueIDs) != iterations { + t.Errorf("Expected %d unique IDs, got %d", iterations, len(uniqueIDs)) + } +}