mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2025-12-06 13:54:41 +08:00
## 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 <the-dev-z@users.noreply.github.com>
This commit is contained in:
committed by
tangmengqiu
parent
f23eaa534d
commit
031cc19c99
@@ -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 // 默认为全仓模式
|
||||
|
||||
123
api/traderid_test.go
Normal file
123
api/traderid_test.go
Normal file
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user