案例:Go语言实现权重抽奖系统
需求描述
- 支持配置多个奖品及对应权重
- 保证抽奖结果符合权重概率分布
- 防止重复中奖
- 提供抽奖结果验证接口
完整实现代码
go
复制代码
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"net/http"
"sync"
)
// 奖品配置
type Prize struct {
ID int `json:"id"`
Name string `json:"name"`
Weight int `json:"weight"` // 权重值(非百分比)
}
// 抽奖系统
type LotterySystem struct {
prizes []Prize
totalWeight int
issuedPrizes map[int]bool
mu sync.Mutex
}
// 初始化抽奖系统
func NewLotterySystem(prizes []Prize) *LotterySystem {
total := 0
for _, p := range prizes {
total += p.Weight
}
return &LotterySystem{
prizes: prizes,
totalWeight: total,
issuedPrizes: make(map[int]bool),
}
}
// 安全随机数生成
func secureRandom(max int) (int, error) {
n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
if err != nil {
return 0, err
}
return int(n.Int64()), nil
}
// 执行抽奖
func (ls *LotterySystem) Draw() (*Prize, error) {
ls.mu.Lock()
defer ls.mu.Unlock()
if ls.totalWeight == 0 {
return nil, fmt.Errorf("no available prizes")
}
// 生成随机数
randomNum, err := secureRandom(ls.totalWeight)
if err != nil {
return nil, err
}
// 权重选择
current := 0
for _, p := range ls.prizes {
current += p.Weight
if randomNum < current {
if ls.issuedPrizes[p.ID] {
continue // 已发放的奖品跳过
}
ls.issuedPrizes[p.ID] = true
return &p, nil
}
}
return nil, fmt.Errorf("draw failed")
}
// HTTP服务
func main() {
// 初始化奖品池
prizes := []Prize{
{ID: 1, Name: "一等奖", Weight: 1},
{ID: 2, Name: "二等奖", Weight: 5},
{ID: 3, Name: "三等奖", Weight: 20},
{ID: 4, Name: "参与奖", Weight: 74},
}
lottery := NewLotterySystem(prizes)
http.HandleFunc("/draw", func(w http.ResponseWriter, r *http.Request) {
prize, err := lottery.Draw()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(prize)
})
fmt.Println("抽奖服务已启动,监听端口 8080")
http.ListenAndServe(":8080", nil)
}
核心功能说明
- 权重算法:
go
复制代码
// 权重选择逻辑
current := 0
for _, p := range ls.prizes {
current += p.Weight
if randomNum < current {
return &p
}
}
- 安全随机数:
go
复制代码
// 使用crypto/rand生成安全随机数
func secureRandom(max int) (int, error) {
n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
// ...
}
- 避免使用
math/rand
的可预测性
- 满足安全抽奖需求
- 并发控制:
go
复制代码
var mu sync.Mutex
func (ls *LotterySystem) Draw() {
ls.mu.Lock()
defer ls.mu.Unlock()
// ...
}
- 使用互斥锁保证线程安全
- 防止并发抽奖导致的数据竞争
- 防重复机制:
go
复制代码
issuedPrizes map[int]bool
- 使用内存映射记录已发放奖品
- 生产环境可替换为Redis等持久化存储
扩展功能建议
- 概率可视化验证:
go
复制代码
// 添加测试端点验证概率分布
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
results := make(map[int]int)
for i := 0; i < 10000; i++ {
tempLottery := NewLotterySystem(prizes)
prize, _ := tempLottery.Draw()
results[prize.ID]++
}
json.NewEncoder(w).Encode(results)
})
- 分布式锁扩展:
go
复制代码
// 使用Redis分布式锁
func (ls *LotterySystem) DistributedDraw() {
lock := redis.NewLock("lottery_lock")
err := lock.Lock()
// ...抽奖逻辑...
lock.Unlock()
}
- 奖品库存管理:
go
复制代码
type Prize struct {
// ...
Stock int // 新增库存字段
}
func (ls *LotterySystem) Draw() {
// 检查库存
if p.Stock <= 0 {
continue
}
// 扣减库存
p.Stock--
}
运行测试
- 启动服务:
bash
复制代码
go run main.go
- 测试抽奖:
bash
复制代码
curl http://localhost:8080/draw
# 示例返回:{"id":3,"name":"三等奖","weight":20}
- 概率验证测试:
bash
复制代码
curl http://localhost:8080/test
# 返回万次抽奖结果分布
关键优化点
- 性能优化:
- 安全增强:
- 业务扩展: