package main import ( "database/sql" "fmt" "log" "os" _ "github.com/go-sql-driver/mysql" ) 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'") return } db, err := sql.Open("mysql", dsn) if err != nil { log.Fatal("连接数据库失败:", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatal("数据库连接测试失败:", err) } fmt.Println("========== 抽奖数据分析工具 ==========\n") // 1. 查询最近的活动和期次 fmt.Println("【最近的活动期次】") rows, err := db.Query(` SELECT ai.id, ai.activity_id, a.name, ai.issue_number, ai.status FROM activity_issues ai LEFT JOIN activities a ON ai.activity_id = a.id WHERE a.play_type = 'default' ORDER BY ai.created_at DESC LIMIT 5 `) if err != nil { log.Fatal("查询期次失败:", err) } defer rows.Close() var issueIDs []int64 for rows.Next() { var issueID, activityID int64 var activityName string var issueNumber, status int32 if err := rows.Scan(&issueID, &activityID, &activityName, &issueNumber, &status); err != nil { log.Fatal(err) } fmt.Printf("期次ID: %d, 活动: %s, 期号: %d, 状态: %d\n", issueID, activityName, issueNumber, status) issueIDs = append(issueIDs, issueID) } if len(issueIDs) == 0 { fmt.Println("没有找到活动期次") return } // 选择第一个期次进行分析 issueID := issueIDs[0] fmt.Printf("\n分析期次ID: %d\n\n", issueID) // 2. 查询该期次的奖品权重配置 fmt.Println("【奖品权重配置】") rows, err = db.Query(` SELECT id, product_id, weight, quantity, level, is_boss FROM activity_reward_settings WHERE issue_id = ? ORDER BY weight ASC `, issueID) if err != nil { log.Fatal("查询奖品配置失败:", err) } defer rows.Close() type Reward struct { ID int64 ProductID int64 Weight int32 Quantity int32 Level int32 IsBoss int32 } var rewards []Reward var totalWeight int64 for rows.Next() { var r Reward if err := rows.Scan(&r.ID, &r.ProductID, &r.Weight, &r.Quantity, &r.Level, &r.IsBoss); err != nil { log.Fatal(err) } rewards = append(rewards, r) totalWeight += int64(r.Weight) } fmt.Printf("总权重: %d\n\n", totalWeight) for _, r := range rewards { prob := float64(r.Weight) / float64(totalWeight) * 100 fmt.Printf("奖品ID: %d, 权重: %6d, 概率: %6.3f%%, 数量: %d, 等级: %d, 是否大奖: %d\n", r.ID, r.Weight, prob, r.Quantity, r.Level, r.IsBoss) } // 3. 查询该期次的中奖记录 fmt.Println("\n【中奖统计】") rows, err = db.Query(` SELECT reward_id, COUNT(*) as count FROM activity_draw_logs WHERE issue_id = ? GROUP BY reward_id ORDER BY count DESC `, issueID) if err != nil { log.Fatal("查询中奖记录失败:", err) } defer rows.Close() type DrawStat struct { RewardID int64 Count int64 } var drawStats []DrawStat var totalDraws int64 for rows.Next() { var ds DrawStat if err := rows.Scan(&ds.RewardID, &ds.Count); err != nil { log.Fatal(err) } drawStats = append(drawStats, ds) totalDraws += ds.Count } fmt.Printf("总抽奖次数: %d\n\n", totalDraws) // 创建奖品ID到权重的映射 rewardMap := make(map[int64]Reward) for _, r := range rewards { rewardMap[r.ID] = r } // 分析每个奖品的实际中奖率 fmt.Println("【实际中奖率分析】") fmt.Printf("%-10s %-10s %-10s %-10s %-10s %-10s\n", "奖品ID", "权重", "理论概率", "实际次数", "实际概率", "偏差") for _, ds := range drawStats { reward, ok := rewardMap[ds.RewardID] if !ok { continue } theoryProb := float64(reward.Weight) / float64(totalWeight) * 100 actualProb := float64(ds.Count) / float64(totalDraws) * 100 diff := actualProb - theoryProb fmt.Printf("%-10d %-10d %-10.3f%% %-10d %-10.3f%% %+10.3f%%\n", ds.RewardID, reward.Weight, theoryProb, ds.Count, actualProb, diff) } // 4. 分析大奖出现频率 fmt.Println("\n【大奖分析】") var bigPrizeCount int64 var bigPrizeWeight int64 for _, ds := range drawStats { reward, ok := rewardMap[ds.RewardID] if !ok { continue } // 假设权重 <= 1000 的是大奖 if reward.Weight <= 1000 { bigPrizeCount += ds.Count bigPrizeWeight += int64(reward.Weight) } } if bigPrizeWeight > 0 { bigPrizeTheory := float64(bigPrizeWeight) / float64(totalWeight) * 100 bigPrizeActual := float64(bigPrizeCount) / float64(totalDraws) * 100 fmt.Printf("大奖总权重: %d\n", bigPrizeWeight) fmt.Printf("大奖理论概率: %.3f%%\n", bigPrizeTheory) fmt.Printf("大奖实际概率: %.3f%%\n", bigPrizeActual) fmt.Printf("大奖出现次数: %d / %d\n", bigPrizeCount, totalDraws) fmt.Printf("偏差: %+.3f%%\n", bigPrizeActual-bigPrizeTheory) // 判断是否异常 if bigPrizeActual > bigPrizeTheory*2 { fmt.Println("\n⚠️ 警告:大奖实际概率是理论概率的 2 倍以上,可能存在问题!") } else if bigPrizeActual > bigPrizeTheory*1.5 { fmt.Println("\n⚠️ 注意:大奖实际概率偏高,建议进一步调查") } else { fmt.Println("\n✅ 大奖概率在正常范围内") } } // 5. 查询最近的中奖记录 fmt.Println("\n【最近 20 次中奖记录】") rows, err = db.Query(` SELECT id, user_id, reward_id, created_at FROM activity_draw_logs WHERE issue_id = ? ORDER BY created_at DESC LIMIT 20 `, issueID) if err != nil { log.Fatal("查询中奖记录失败:", err) } defer rows.Close() for rows.Next() { var logID, userID, rewardID int64 var createdAt string if err := rows.Scan(&logID, &userID, &rewardID, &createdAt); err != nil { log.Fatal(err) } reward, ok := rewardMap[rewardID] isBigPrize := "" if ok && reward.Weight <= 1000 { isBigPrize = " [大奖]" } fmt.Printf("用户: %d, 奖品ID: %d, 时间: %s%s\n", userID, rewardID, createdAt, isBigPrize) } }