package main import ( "fmt" "os" _ "github.com/go-sql-driver/mysql" "gorm.io/driver/mysql" "gorm.io/gorm" ) type LivestreamPrize struct { ID int64 `gorm:"column:id"` Name string `gorm:"column:name"` Weight int32 `gorm:"column:weight"` Level int32 `gorm:"column:level"` } type LivestreamDrawLog struct { ID int64 `gorm:"column:id"` ActivityID int64 `gorm:"column:activity_id"` PrizeID int64 `gorm:"column:prize_id"` PrizeName string `gorm:"column:prize_name"` Level int32 `gorm:"column:level"` WeightsTotal int64 `gorm:"column:weights_total"` RandValue int64 `gorm:"column:rand_value"` } func main() { // 从环境变量读取数据库连接信息 dsn := os.Getenv("DB_DSN") if dsn == "" { fmt.Println("请设置环境变量 DB_DSN,例如:") fmt.Println("export DB_DSN='user:password@tcp(host:port)/dev_game?charset=utf8mb4&parseTime=True&loc=Local'") fmt.Println("\n或者直接运行:") fmt.Println("DB_DSN='user:password@tcp(host:port)/dev_game?charset=utf8mb4&parseTime=True&loc=Local' go run main.go") return } db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { fmt.Printf("连接数据库失败: %v\n", err) return } fmt.Println("========== 直播间抽奖概率分析工具 ==========\n") // 1. 查询最近的活动 fmt.Println("【最近的直播间活动】") var activities []struct { ID int64 `gorm:"column:id"` Name string `gorm:"column:name"` } if err := db.Table("livestream_activities"). Order("id DESC"). Limit(10). Find(&activities).Error; err != nil { fmt.Printf("查询活动失败: %v\n", err) return } if len(activities) == 0 { fmt.Println("没有找到直播间活动") return } for i, act := range activities { fmt.Printf("%d. ID: %d, 名称: %s\n", i+1, act.ID, act.Name) } // 选择第一个活动进行分析 activityID := activities[0].ID fmt.Printf("\n分析活动ID: %d (%s)\n\n", activityID, activities[0].Name) // 2. 查询奖品配置 fmt.Println("【奖品权重配置】") var prizes []LivestreamPrize if err := db.Table("livestream_prizes"). Where("activity_id = ?", activityID). Order("weight ASC"). Find(&prizes).Error; err != nil { fmt.Printf("查询奖品失败: %v\n", err) return } if len(prizes) == 0 { fmt.Println("该活动没有配置奖品") return } var totalWeight int64 for _, p := range prizes { totalWeight += int64(p.Weight) } fmt.Printf("总权重: %d\n\n", totalWeight) fmt.Printf("%-5s %-30s %-10s %-10s %-10s\n", "ID", "名称", "权重", "概率", "期望") fmt.Println("------------------------------------------------------------------------------------") for _, p := range prizes { prob := float64(p.Weight) / float64(totalWeight) * 100 expected := int(float64(totalWeight) / float64(p.Weight)) fmt.Printf("%-5d %-30s %-10d %-10.3f%% 1/%-10d\n", p.ID, p.Name, p.Weight, prob, expected) } // 3. 查询中奖记录 fmt.Println("\n【中奖统计】") var drawLogs []LivestreamDrawLog if err := db.Table("livestream_draw_logs"). Where("activity_id = ?", activityID). Find(&drawLogs).Error; err != nil { fmt.Printf("查询中奖记录失败: %v\n", err) return } if len(drawLogs) == 0 { fmt.Println("该活动还没有中奖记录") return } fmt.Printf("总抽奖次数: %d\n\n", len(drawLogs)) // 统计每个奖品的中奖次数 prizeStats := make(map[int64]int) for _, log := range drawLogs { prizeStats[log.PrizeID]++ } // 创建奖品ID到奖品的映射 prizeMap := make(map[int64]LivestreamPrize) for _, p := range prizes { prizeMap[p.ID] = p } // 分析每个奖品的实际中奖率 fmt.Println("【实际中奖率分析】") fmt.Printf("%-5s %-30s %-10s %-10s %-10s %-10s %-10s\n", "ID", "名称", "权重", "理论概率", "实际次数", "实际概率", "偏差") fmt.Println("------------------------------------------------------------------------------------") type PrizeStat struct { Prize LivestreamPrize Count int TheoryProb float64 ActualProb float64 Diff float64 } var stats []PrizeStat for _, p := range prizes { count := prizeStats[p.ID] theoryProb := float64(p.Weight) / float64(totalWeight) * 100 actualProb := float64(count) / float64(len(drawLogs)) * 100 diff := actualProb - theoryProb stats = append(stats, PrizeStat{ Prize: p, Count: count, TheoryProb: theoryProb, ActualProb: actualProb, Diff: diff, }) fmt.Printf("%-5d %-30s %-10d %-10.3f%% %-10d %-10.3f%% %+10.3f%%\n", p.ID, p.Name, p.Weight, theoryProb, count, actualProb, diff) } // 4. 分析大奖出现频率 fmt.Println("\n【大奖分析】") var bigPrizeCount int var bigPrizeWeight int64 var bigPrizeNames []string // 假设权重 <= 1000 的是大奖 for _, stat := range stats { if stat.Prize.Weight <= 1000 { bigPrizeCount += stat.Count bigPrizeWeight += int64(stat.Prize.Weight) if stat.Count > 0 { bigPrizeNames = append(bigPrizeNames, fmt.Sprintf("%s(%d次)", stat.Prize.Name, stat.Count)) } } } if bigPrizeWeight > 0 { bigPrizeTheory := float64(bigPrizeWeight) / float64(totalWeight) * 100 bigPrizeActual := float64(bigPrizeCount) / float64(len(drawLogs)) * 100 fmt.Printf("大奖定义: 权重 <= 1000\n") fmt.Printf("大奖总权重: %d\n", bigPrizeWeight) fmt.Printf("大奖理论概率: %.3f%% (1/%d)\n", bigPrizeTheory, int(float64(totalWeight)/float64(bigPrizeWeight))) fmt.Printf("大奖实际概率: %.3f%%\n", bigPrizeActual) fmt.Printf("大奖出现次数: %d / %d\n", bigPrizeCount, len(drawLogs)) fmt.Printf("偏差: %+.3f%%\n", bigPrizeActual-bigPrizeTheory) if len(bigPrizeNames) > 0 { fmt.Printf("\n中奖明细: %v\n", bigPrizeNames) } // 判断是否异常 fmt.Println() if bigPrizeActual > bigPrizeTheory*3 { fmt.Println("🔴 严重警告:大奖实际概率是理论概率的 3 倍以上!") fmt.Println(" 可能原因:") fmt.Println(" 1. 权重配置错误") fmt.Println(" 2. 随机数生成有问题") fmt.Println(" 3. 缓存未更新(修改权重后未重新生成随机位置)") } else if bigPrizeActual > bigPrizeTheory*2 { fmt.Println("🟠 警告:大奖实际概率是理论概率的 2 倍以上!") fmt.Println(" 建议检查权重配置和随机位置生成") } else if bigPrizeActual > bigPrizeTheory*1.5 { fmt.Println("🟡 注意:大奖实际概率偏高") fmt.Println(" 可能是统计波动,建议继续观察") } else { fmt.Println("✅ 大奖概率在正常范围内") } } // 5. 查询最近的中奖记录 fmt.Println("\n【最近 20 次中奖记录】") var recentLogs []LivestreamDrawLog if err := db.Table("livestream_draw_logs"). Where("activity_id = ?", activityID). Order("id DESC"). Limit(20). Find(&recentLogs).Error; err != nil { fmt.Printf("查询中奖记录失败: %v\n", err) return } for _, log := range recentLogs { prize, ok := prizeMap[log.PrizeID] isBigPrize := "" if ok && prize.Weight <= 1000 { isBigPrize = " [大奖]" } fmt.Printf("ID: %d, 奖品: %s, 随机值: %d/%d%s\n", log.ID, log.PrizeName, log.RandValue, log.WeightsTotal, isBigPrize) } // 6. 随机值分布分析 fmt.Println("\n【随机值分布分析】") if len(drawLogs) > 0 { // 检查随机值是否均匀分布 bucketCount := 10 buckets := make([]int, bucketCount) bucketSize := totalWeight / int64(bucketCount) for _, log := range drawLogs { if log.WeightsTotal > 0 { bucket := int(log.RandValue / bucketSize) if bucket >= bucketCount { bucket = bucketCount - 1 } buckets[bucket]++ } } fmt.Printf("将随机值范围 [0, %d) 分为 %d 个区间:\n", totalWeight, bucketCount) expectedPerBucket := float64(len(drawLogs)) / float64(bucketCount) for i, count := range buckets { start := int64(i) * bucketSize end := start + bucketSize if i == bucketCount-1 { end = totalWeight } deviation := (float64(count) - expectedPerBucket) / expectedPerBucket * 100 fmt.Printf("区间 [%6d, %6d): %4d 次 (期望: %.1f, 偏差: %+.1f%%)\n", start, end, count, expectedPerBucket, deviation) } } }