Go语言实现权重抽奖系统

案例:Go语言实现权重抽奖系统

需求描述

  1. 支持配置多个奖品及对应权重
  2. 保证抽奖结果符合权重概率分布
  3. 防止重复中奖
  4. 提供抽奖结果验证接口

完整实现代码

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)
}

核心功能说明

  1. 权重算法
go 复制代码
// 权重选择逻辑
current := 0
for _, p := range ls.prizes {
    current += p.Weight
    if randomNum < current {
        return &p
    }
}
  • 使用累计权重区间算法
  • 保证概率分布准确性
  1. 安全随机数
go 复制代码
// 使用crypto/rand生成安全随机数
func secureRandom(max int) (int, error) {
    n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
    // ...
}
  • 避免使用math/rand的可预测性
  • 满足安全抽奖需求
  1. 并发控制
go 复制代码
var mu sync.Mutex

func (ls *LotterySystem) Draw() {
    ls.mu.Lock()
    defer ls.mu.Unlock()
    // ...
}
  • 使用互斥锁保证线程安全
  • 防止并发抽奖导致的数据竞争
  1. 防重复机制
go 复制代码
issuedPrizes map[int]bool
  • 使用内存映射记录已发放奖品
  • 生产环境可替换为Redis等持久化存储

扩展功能建议

  1. 概率可视化验证
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)
})
  1. 分布式锁扩展
go 复制代码
// 使用Redis分布式锁
func (ls *LotterySystem) DistributedDraw() {
    lock := redis.NewLock("lottery_lock")
    err := lock.Lock()
    // ...抽奖逻辑...
    lock.Unlock()
}
  1. 奖品库存管理
go 复制代码
type Prize struct {
    // ...
    Stock     int // 新增库存字段
}

func (ls *LotterySystem) Draw() {
    // 检查库存
    if p.Stock <= 0 {
        continue
    }
    // 扣减库存
    p.Stock--
}

运行测试

  1. 启动服务:
bash 复制代码
go run main.go
  1. 测试抽奖:
bash 复制代码
curl http://localhost:8080/draw
# 示例返回:{"id":3,"name":"三等奖","weight":20}
  1. 概率验证测试:
bash 复制代码
curl http://localhost:8080/test
# 返回万次抽奖结果分布

关键优化点

  1. 性能优化
  • 使用预计算总权重值
  • 内存级锁粒度控制
  • 对象池复用
  1. 安全增强
  • JWT用户身份验证
  • 抽奖频率限制
  • 敏感操作日志
  1. 业务扩展
  • 支持不同抽奖活动
  • 奖品有效期管理
  • 中奖名单公示
相关推荐
编码者卢布4 分钟前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
她说..3 小时前
策略模式+工厂模式实现审批流(面试问答版)
java·后端·spring·面试·springboot·策略模式·javaee
梦梦代码精3 小时前
开源、免费、可商用:BuildingAI一站式体验报告
开发语言·前端·数据结构·人工智能·后端·开源·知识图谱
程序设计实验室4 小时前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
李慕婉学姐4 小时前
【开题答辩过程】以《基于Spring Boot的疗养院理疗管理系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·spring boot·后端
我的golang之路果然有问题4 小时前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
tb_first4 小时前
SSM速通2
java·javascript·后端
一路向北⁢5 小时前
Spring Boot 3 整合 SSE (Server-Sent Events) 企业级最佳实践(一)
java·spring boot·后端·sse·通信
风象南5 小时前
JFR:Spring Boot 应用的性能诊断利器
java·spring boot·后端
爱吃山竹的大肚肚5 小时前
微服务间通过Feign传输文件,处理MultipartFile类型
java·spring boot·后端·spring cloud·微服务