2026-02-03 17:44:02 +08:00

169 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 直播间抽奖系统 - 随机离散方案
## 概述
此方案将原来的"连续区间权重"改为"随机离散位置",解决大奖连续出现或长时间不出的问题。
## 核心变化
### 原方案(连续区间)
```
总权重: 99000
冰箱贴 (65800): [0 - 65799] ← 占据大片连续区间
高达徽章 (28000): [65800 - 93799]
兰博基尼 (3000): [93800 - 96799]
...
大奖 (100): [98900 - 98999] ← 仅占100个连续数字
问题:如果随机数生成有偏,整个区间都会受影响
```
### 新方案(随机离散)
```
1. 创建位置池 [0 - 98999]
2. 使用Fisher-Yates算法打乱位置顺序
3. 按权重分配位置给每个奖品
冰箱贴获得65800个**分散**的位置
大奖获得100个**分散**的位置
优势:随机数生成即使有偏,也不会导致某个奖品连续出现
```
## 实现原理
### 1. 随机位置生成
```go
// Fisher-Yates洗牌算法
positions := [0, 1, 2, 3, ..., 98999]
for i := len(positions)-1; i > 0; i-- {
j = random(0, i)
swap(positions[i], positions[j])
}
```
### 2. 位置分配
```go
prizePosMap = {
冰箱贴ID: [打乱后的位置...], // 65800个
徽章ID: [打乱后的位置...], // 28000个
...
}
```
### 3. 抽奖查找
```go
randValue = crypto/rand(0, totalWeight)
// 二分查找
for each prize in prizePosMap:
if randValue in prize.positions:
return prize
```
## 代码结构
```
internal/service/livestream/
├── livestream.go # 主服务文件(已集成随机离散)
├── discrete_random.go # 随机离散核心逻辑
├── discrete_random_test.go # 单元测试
└── draw_integration_test.go # 集成测试
```
## 使用方法
### 1. 配置活动奖品
```go
// 原有接口,无需改动
s.CreatePrizes(ctx, activityID, prizes)
// 系统会自动生成随机离散位置
```
### 2. 执行抽奖
```go
// Draw() 方法已自动使用随机离散
result, err := s.Draw(ctx, input)
```
### 3. 调试日志
```
随机离散位置已生成
activity_id: 1
total_weight: 99000
prize_count: 11
```
## 测试验证
### 运行测试
```bash
cd /Users/win/aicode/bindbox/bindbox_game
# 单元测试
go test -v ./internal/service/livestream/ -run TestDiscreteRandomDistribution
# 集成测试
go test -v ./internal/service/livestream/ -run TestDrawWithDiscreteIntegration
# 性能测试
go test -bench=BenchmarkDiscreteGeneration ./internal/service/livestream/
```
### 预期输出
```
========== 随机离散抽奖统计结果 ==========
模拟次数: 1000
Nu高达 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
NT高达 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
魔礼青 权重: 100 理论: 0.10% 实际: 0.10% 偏差: +0.00% 次数: 1
维达尔 权重: 100 理论: 0.10% 实际: 0.20% 偏差: +0.10% 次数: 2
玉衡星6号 权重: 400 理论: 0.40% 实际: 0.40% 偏差: +0.00% 次数: 4
马克兔 权重: 600 理论: 0.61% 实际: 0.70% 偏差: +0.09% 次数: 7
00高达 权重: 700 理论: 0.71% 实际: 0.60% 偏差: -0.11% 次数: 6
SD随机款 权重: 1100 理论: 1.11% 实际: 1.10% 偏差: -0.01% 次数: 11
兰博基尼 权重: 3000 理论: 3.03% 实际: 2.90% 偏差: -0.13% 次数: 29
高达徽章 权重: 28000 理论: 28.28% 实际: 28.20% 偏差: -0.08% 次数: 282
冰箱贴 权重: 65800 理论: 66.47% 实际: 66.50% 偏差: +0.03% 次数: 665
```
## 性能分析
| 操作 | 时间复杂度 | 说明 |
|------|-----------|------|
| 生成位置 | O(n) | 一次生成,缓存复用 |
| 查找奖品 | O(m × log(k)) | m=奖品数量, k=平均位置数 |
| 内存占用 | O(totalWeight × 4字节) | 99000个位置≈390KB |
## 注意事项
1. **缓存失效**:奖品配置变更后,缓存会自动重新生成
2. **并发安全**使用RWMutex保护缓存读写
3. **位置唯一**Fisher-Yates算法保证每个位置只属于一个奖品
4. **密码学安全**使用crypto/rand保证随机性
## 对比分析
| 特性 | 连续区间 | 随机离散 |
|------|----------|----------|
| 实现复杂度 | ⭐ 简单 | ⭐⭐⭐ 复杂 |
| 概率精度 | ✅ 准确 | ✅ 准确 |
| 分布均匀性 | ⚠️ 可能偏斜 | ✅ 均匀 |
| 防连续中奖 | ❌ 无 | ✅ 天然防聚集 |
| 缓存需求 | 无 | 需要 |
| 内存占用 | 低 | 中等(~400KB |
## 结论
随机离散方案更适合需要**分布均匀、防连续中奖**的场景,虽然实现稍复杂,但能显著提升用户体验。