225 lines
6.0 KiB
Go
225 lines
6.0 KiB
Go
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)
|
||
}
|
||
}
|