GoLang抽奖系统简易实现流程

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

GoLang抽奖系统简易实现流程

上后左爱   2022-12-15 我要评论

业务难点

设计一个抽奖系统,这个系统并不是具体化,是抽象化,具有以下的几个难点:

1、抽奖业务需要 复杂多变

2、奖品类型和概率设置

3、公平的抽奖和安全的发奖

4、并发安全性问题 一个人不能枪多次

5、高效的抽奖和发奖,提供高并发和性能

6、 如何使用redies进行优化

技术选项

  • 高并发 Go 协程优先于 PHP多进程,Java的 多线程模型
  • 高性能编译后的二进制优先于PHP解释性和Java虚拟机
  • 高效的网络模型 epoll 模型优先于PHPBIO模型和Java NIO模型

抽奖活动

  • 年会抽奖,彩票刮奖,微信摇一摇,抽奖大转盘,集福卡等活动,本项目以 抽奖大转盘作为一种活动进行设计。
  • 项目实战内容: 框架/核心代码后台功能 ,合理设置奖品和发送奖品, mysql+优化-使用redies 发奖计划与奖品池, 压力测试和更多的运营策略(系统的性能有更好的了解,运营产品的策略有更多), 引入 thirft 框架(RPC 框架), 设计接口生成代码, 服务端接口和客户端程序

需求分析

1. go mod 配置

2. 配置国内代理: go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct

3. go get -u -v github.com/kataras/iris 下载包在 GOPATH的PKG目录下

4. iris:功能: 安全认证,缓存 cookies 文件 MVC, 模板 丰富的示例代码

5. https://iris-go.com/v10/recipe

*年会抽奖程序

使用的是iris 这个web 框架 进行处理

/**
*  curl http://localhost:8080/
*  curl --data "users=123,567" http://localhost:8080/import
*  curl http://localhost:8080/lucky
 */
package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"strings"
	"sync"
	"time"
)
var userList []string // 共享变量读写前后 需要增加 锁的设定 简单方式添加互斥锁
var mu sync.Mutex
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	userList = []string{}
	mu = sync.Mutex{}
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error: %s\n", err))
		return
	}
}
func (c *lotteryController) Get() string {
	count := len(userList)
	return fmt.Sprintf("当前总共参与抽奖的用户数:%d\n", count)
}
// PostImport POST http://localhost:8090/import
// params : users
func (c *lotteryController) PostImport() string {
	strUsers := c.Ctx.FormValue("users")
	users := strings.Split(strUsers, ",")
	// 批量线程导入时候 发现有多线程的问题 数据统计不正确
	mu.Lock()
	defer mu.Unlock()
	count1 := len(userList)
	for _, u := range users {
		u = strings.TrimSpace(u)
		if len(u) > 0 {
			userList = append(userList, u)
		}
	}
	count2 := len(userList)
	return fmt.Sprintf("当前总共参与抽奖的用户数:%d, 成功导入的用户数:%d\n", count2, count2-count1)
}
// GetLucky GET http://localhost:8090/lucky
func (c *lotteryController) GetLucky() string {
	// 抽奖地方进行锁的判断
	mu.Lock()
	defer mu.Unlock()
	count := len(userList)
	if count > 1 {
		index := rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(int32(count))
		user := userList[index]
		// 需要 删除被挑选过的人 直接可以删除 当前index 下的数据 更好
		userList = append(userList[0:index], userList[index+1:]...)
		return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
	} else if count == 1 {
		user := userList[0]
		return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
	} else {
		return fmt.Sprintf("当前中奖完毕,没有用户参与中奖\n")
	}
}

单元测试问题,对于 userList 的 多线程下发生数据竞争问题 :

package main
import (
	"fmt"
	"github.com/kataras/iris/v12/httptest"
	"sync"
	"testing"
)
func TestMVC(t *testing.T) {
	app := newApp()
	e := httptest.New(t, app)
	// 使用同步等待锁
	var wg sync.WaitGroup
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:0\n")
	for i := 0; i < 100; i++ {
		wg.Add(1)
		// 不会出现协程并发性问题
		go func(i int) {
			defer wg.Done()
			e.POST("/import").WithFormField("users", fmt.Sprintf("test_u%d", i)).Expect().Status(httptest.StatusOK)
		}(i)
	}
	wg.Wait()
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:100\n")
	e.GET("/lucky").Expect().Status(httptest.StatusOK)
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:99\n")
}

微信摇一摇得抽奖活动

/**
* 微信摇一摇得功能
* wrk -t10 -c10 -d5 http://localhost:8080/lucky 进行压力测试 查看代码是否有竞争异常问题和 接口请求量速度
 */
package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"log"
	"math/rand"
	"os"
	"sync"
	"time"
)
var mu sync.Mutex
const (
	giftTypeCoin      = iota // 虚拟币
	giftTypeCoupon           // 不同卷
	giftTypeCouponFix        // 不同卷
	giftTypeRealSmall        // 实物小奖
	giftTypeRealLarge        // 十五大奖
)
type gift struct {
	id       int
	name     string
	pic      string
	link     string
	gType    int
	data     string // 奖品数据(特定得配置信息)
	dataList []string
	total    int
	left     int
	inuse    bool
	rate     int // 万分之N
	rateMin  int
	rateMax  int
}
// 最大中奖号码
const rateMax = 1000
var logger *log.Logger
// 奖品类表
var giftList []*gift
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func initLog() {
	f, _ := os.Create("G:\\goLandProject\\lottery_demo.log")
	logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
func initGift() {
	giftList = make([]*gift, 5)
	g1 := gift{
		id:       1,
		name:     "手机大奖",
		pic:      "",
		link:     "",
		gType:    giftTypeRealLarge,
		data:     "",
		dataList: nil,
		total:    20000,
		left:     20000,
		inuse:    true,
		rate:     10000,
		rateMin:  0,
		rateMax:  0,
	}
	g2 := gift{
		id:       2,
		name:     "充电器",
		pic:      "",
		link:     "",
		gType:    giftTypeRealSmall,
		data:     "",
		dataList: nil,
		total:    5,
		left:     5,
		inuse:    false,
		rate:     10,
		rateMin:  0,
		rateMax:  0,
	}
	g3 := gift{
		id:       3,
		name:     "优惠卷满200减50",
		pic:      "",
		link:     "",
		gType:    giftTypeCouponFix,
		data:     "mall-coupon-2018",
		dataList: nil,
		total:    50,
		left:     50,
		inuse:    false,
		rate:     500,
		rateMin:  0,
		rateMax:  0,
	}
	g4 := gift{
		id:       4,
		name:     "直降优惠卷",
		pic:      "",
		link:     "",
		gType:    giftTypeCoupon,
		data:     "",
		dataList: []string{"c01", "c02", "c03", "c04", "c05"},
		total:    50,
		left:     50,
		inuse:    false,
		rate:     100,
		rateMin:  0,
		rateMax:  0,
	}
	g5 := gift{
		id:       5,
		name:     "金币",
		pic:      "",
		link:     "",
		gType:    giftTypeCoin,
		data:     "10金币",
		dataList: nil,
		total:    100,
		left:     100,
		inuse:    false,
		rate:     5000,
		rateMin:  0,
		rateMax:  0,
	}
	giftList[0] = &g1
	giftList[1] = &g2
	giftList[2] = &g3
	giftList[3] = &g4
	giftList[4] = &g5
	// s数据整理 中奖区间数据
	rateStart := 0
	for _, data := range giftList {
		if !data.inuse {
			continue
		}
		data.rateMin = rateStart
		data.rateMax = rateStart + data.rate
		if data.rateMax >= rateMax {
			data.rateMax = rateMax
			rateStart = 0
		} else {
			rateStart += data.rate
		}
	}
}
func main() {
	initLog()
	initGift()
	mu = sync.Mutex{}
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() string {
	count := 0
	total := 0
	for _, data := range giftList {
		if data.inuse && (data.total == 0 || (data.total > 0 && data.left > 0)) {
			count++
			total += data.left
		}
	}
	return fmt.Sprintf("当前有效奖品种类数量:%d, 限量奖品总数量:%d\n", count, total)
}
// GetLucky  http://localhost:8080/lucky
func (c *lotteryController) GetLucky() map[string]interface{} {
	mu.Lock()
	defer mu.Unlock()
	code := luckyCode()
	ok := false
	result := make(map[string]interface{})
	result["success"] = ok
	// 对 code 与 rateMin -rateMax 区间内进行对比 判断是否获奖
	for _, data := range giftList {
		if !data.inuse || (data.total > 0 && data.left <= 0) {
			continue
		}
		if data.rateMin <= int(code) && data.rateMax > int(code) {
			sendData := ""
			switch data.gType {
			case giftTypeCoin:
				ok, sendData = sendCoin(data)
			case giftTypeCoupon:
				ok, sendData = sendCoupon(data)
			case giftTypeCouponFix:
				ok, sendData = sendCouponFix(data)
			case giftTypeRealSmall:
				ok, sendData = sendRealSmall(data)
			case giftTypeRealLarge:
				ok, sendData = sendRealLarge(data)
			}
			if ok {
				// 中奖后得到奖品 生成中奖记录
				saveLuckyData(code, data, sendData)
				result["success"] = true
				result["id"] = data.id
				result["name"] = data.name
				result["data"] = sendData
				break
			}
		}
	}
	return result
}
func luckyCode() int32 {
	seed := time.Now().UnixNano()
	code := rand.New(rand.NewSource(seed)).Int31n(int32(rateMax))
	return code
}
func sendCoin(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
// 不同优惠卷
func sendCoupon(data *gift) (bool, string) {
	if data.left > 0 {
		left := data.left - 1
		data.left = left
		return true, data.dataList[left%5]
	} else {
		return false, "奖品已经发完"
	}
}
func sendCouponFix(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func sendRealSmall(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func sendRealLarge(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func saveLuckyData(code int32, g *gift, data string) {
	logger.Printf("lucky, code =%d ,id =%d, name =%d, data =%s, left=%d \n", code, g.id, g.name, data, g.left)
}

微博抢红包

  • 红包得集合,红包内红包数量读写都存在并发安全性问题
  • 第一种方式 使用 Sync.Map 互斥锁得方式
/**
* 微信抢红包 普通得 map 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
*
 */
package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"sync"
	"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
	// 返回当前全部得红包
	rs := make(map[uint32][2]int)
	packageList.Range(func(key, value interface{}) bool {
		id := key.(uint32)
		list := value.([]uint)
		var money int
		for _, v := range list {
			money += int(v)
		}
		rs[id] = [2]int{len(list), money}
		return true
	})
	return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	moeny, errMoney := c.Ctx.URLParamFloat64("money")
	num, errNum := c.Ctx.URLParamInt("num")
	if errUid != nil || errNum != nil || errMoney != nil {
		fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
	}
	moenyTotal := int(moeny * 100)
	if uid < 1 || moenyTotal < num || num < 1 {
		return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
	}
	// 金额分配算法
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	rMax := 0.55 // 随机分配最大值
	if num > 1000 {
		rMax = 0.01
	} else if num < 10 {
		rMax = 0.80
	}
	list := make([]uint, num)
	leftMoney := moenyTotal
	leftNum := num
	for leftNum > 0 {
		if leftNum == 1 {
			list[num-1] = uint(leftMoney)
			break
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		if leftMoney == leftNum {
			for i := num - leftNum; i < num; i++ {
				list[i] = 1
				break
			}
		}
		// 随机分配最大值
		rMoney := int(float64(leftMoney-leftNum) * rMax)
		m := r.Intn(rMoney)
		if m < 1 {
			m = 1
		}
		list[num-leftNum] = uint(m)
		leftMoney -= m
		leftNum--
	}
	// 红包得UUID
	id := r.Uint32()
	packageList.Store(id, list)
	return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
	id, errid := c.Ctx.URLParamInt("id")
	uid, errUid := c.Ctx.URLParamInt("uid")
	if errUid != nil || errid != nil {
		return fmt.Sprintf("")
	}
	if uid < 1 || id < 1 {
		return fmt.Sprintf("")
	}
	listq, ok := packageList.Load(uint32(id))
	list := listq.([]int)
	if !ok || len(list) < 1 {
		return fmt.Sprintf("红包不存在, id =%d \n", id)
	}
	// 分配随机数获取红包
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	i := r.Intn(len(list))
	money := list[i]
	// 更新红包中列表信息
	if len(list) > 1 {
		if i == len(list)-1 {
			packageList.Store(uint32(id), list[:i])
		} else if i == 0 {
			packageList.Store(uint32(id), list[1:])
		} else {
			packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
		}
	} else {
		packageList.Delete(uint32(id))
	}
	return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
}

第二种方式: chan 队列方式 解决线程安全

/**
* 微信抢红包 普通得 map 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
*
* 单核任务 修改成 16 核心 进行抢红包
 */
package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"sync"
	"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type task struct {
	id       uint32
	callback chan uint
}
const taskNum = 16 // 初始化队列数量
var chTaskList []chan task = make([]chan task, taskNum)
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	// 启动多个子线程进行 红包抢
	for i := 0; i < taskNum; i++ {
		chTaskList[i] = make(chan task)
		go fetchPackageListMoney(chTaskList[i])
	}
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
	// 返回当前全部得红包
	rs := make(map[uint32][2]int)
	packageList.Range(func(key, value interface{}) bool {
		id := key.(uint32)
		list := value.([]uint)
		var money int
		for _, v := range list {
			money += int(v)
		}
		rs[id] = [2]int{len(list), money}
		return true
	})
	return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	moeny, errMoney := c.Ctx.URLParamFloat64("money")
	num, errNum := c.Ctx.URLParamInt("num")
	if errUid != nil || errNum != nil || errMoney != nil {
		fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
	}
	moenyTotal := int(moeny * 100)
	if uid < 1 || moenyTotal < num || num < 1 {
		return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
	}
	// 金额分配算法
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	rMax := 0.55 // 随机分配最大值
	if num > 1000 {
		rMax = 0.01
	} else if num < 10 {
		rMax = 0.80
	}
	list := make([]uint, num)
	leftMoney := moenyTotal
	leftNum := num
	for leftNum > 0 {
		if leftNum == 1 {
			list[num-1] = uint(leftMoney)
			break
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		if leftMoney == leftNum {
			for i := num - leftNum; i < num; i++ {
				list[i] = 1
				break
			}
		}
		// 随机分配最大值
		rMoney := int(float64(leftMoney-leftNum) * rMax)
		m := r.Intn(rMoney)
		if m < 1 {
			m = 1
		}
		list[num-leftNum] = uint(m)
		leftMoney -= m
		leftNum--
	}
	// 红包得UUID
	id := r.Uint32()
	packageList.Store(id, list)
	return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
	id, errid := c.Ctx.URLParamInt("id")
	uid, errUid := c.Ctx.URLParamInt("uid")
	if errUid != nil || errid != nil {
		return fmt.Sprintf("")
	}
	if uid < 1 || id < 1 {
		return fmt.Sprintf("")
	}
	listq, ok := packageList.Load(uint32(id))
	list := listq.([]int)
	if !ok || len(list) < 1 {
		return fmt.Sprintf("红包不存在, id =%d \n", id)
	}
	// 构造一个任务
	callback := make(chan uint)
	t := task{id: uint32(id), callback: callback}
	// 发送任务
	chTasks := chTaskList[id%taskNum]
	chTasks <- t
	// 接受返回结果值
	money := <-callback
	if money <= 0 {
		return "很遗憾,没有抢到红包\n"
	} else {
		return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
	}
}
// 使用队列方式, 需要不断从chan 通道中获取数据
func fetchPackageListMoney(chTasks chan task) {
	for {
		t := <-chTasks
		id := t.id
		l, ok := packageList.Load(id)
		if ok && l != nil {
			// 分配随机数获取红包
			list := l.([]int)
			r := rand.New(rand.NewSource(time.Now().UnixNano()))
			i := r.Intn(len(list))
			money := list[i]
			// 更新红包中列表信息
			if len(list) > 1 {
				if i == len(list)-1 {
					packageList.Store(uint32(id), list[:i])
				} else if i == 0 {
					packageList.Store(uint32(id), list[1:])
				} else {
					packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
				}
			} else {
				packageList.Delete(uint32(id))
			}
			t.callback <- uint(money)
		} else {
			t.callback <- 0
		}
	}
}

抽奖大转盘

后端设置各个奖品得中奖概率和数量限制,更新库存时候发现并发安全性质问题 和微信摇一摇 类似

使用CAS进行安全代码进行修改,不在使用同步锁,CAS乐观锁比sync.mutSync 会快一些

/**
 * 大转盘程序
 * curl http://localhost:8080/
 * curl http://localhost:8080/debug
 * curl http://localhost:8080/prize
 * 固定几个奖品,不同的中奖概率或者总数量限制
 * 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品信息
 *
 * 增加互斥锁,保证并发库存更新的正常
 * 压力测试:
 * wrk -t10 -c100 -d5 "http://localhost:8080/prize"
 */
package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"log"
	"math/rand"
	"strings"
	"sync/atomic"
	"time"
)
// Prate 奖品中奖概率
type Prate struct {
	Rate  int    // 万分之N的中奖概率
	Total int    // 总数量限制,0 表示无限数量
	CodeA int    // 中奖概率起始编码(包含)
	CodeB int    // 中奖概率终止编码(包含)
	Left  *int32 // 剩余数使用CAS乐观锁 进行修改
}
var left = int32(1000)
// 奖品列表
var prizeList []string = []string{
	"一等奖,火星单程船票",
	"二等奖,凉飕飕南极之旅",
	"三等奖,iPhone一部",
	"", // 没有中奖
}
// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
var rateList []Prate = []Prate{
	//Prate{1, 1, 0, 0, 1},
	//Prate{2, 2, 1, 2, 2},
	Prate{5, 1000, 0, 9999, &left},
	//Prate{100,0, 0, 9999, 0},
}
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get GET http://localhost:8080/
func (c *lotteryController) Get() string {
	c.Ctx.Header("Content-Type", "text/html")
	return fmt.Sprintf("大转盘奖品列表:<br/> %s", strings.Join(prizeList, "<br/>\n"))
}
// GetPrize GET http://localhost:8080/prize
func (c *lotteryController) GetPrize() string {
	c.Ctx.Header("Content-Type", "text/html")
	// 第一步,抽奖,根据随机数匹配奖品
	seed := time.Now().UnixNano()
	r := rand.New(rand.NewSource(seed))
	// 得到个人的抽奖编码
	code := r.Intn(10000)
	//fmt.Println("GetPrize code=", code)
	var myPrize string
	var prizeRate *Prate
	// 从奖品列表中匹配,是否中奖
	for i, prize := range prizeList {
		rate := &rateList[i]
		if code >= rate.CodeA && code <= rate.CodeB {
			// 满足中奖条件
			myPrize = prize
			prizeRate = rate
			break
		}
	}
	if myPrize == "" {
		// 没有中奖
		myPrize = "很遗憾,再来一次"
		return myPrize
	}
	// 第二步,发奖,是否可以发奖
	if prizeRate.Total == 0 {
		// 无限奖品
		fmt.Println("中奖: ", myPrize)
		return myPrize
	} else if *prizeRate.Left > 0 {
		// 还有剩余奖品
		left := atomic.AddInt32(prizeRate.Left, -1)
		if left >= 0 {
			log.Printf("奖品:%s", myPrize)
			return myPrize
		}
	}
	// 有限且没有剩余奖品,无法发奖
	myPrize = "很遗憾,再来一次"
	return myPrize
}
// GetDebug GET http://localhost:8080/debug
func (c *lotteryController) GetDebug() string {
	c.Ctx.Header("Content-Type", "text/html")
	return fmt.Sprintf("获奖概率: %v", rateList)
}

抽奖活动总结

  • 并发安全性质问题,互斥锁,队列, CAS递减方式
  • 优化,通过散列减小单个集合得大小

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们