最近接手的项目中要新增一个抽奖功能,场景类似年会上的抽奖,触发抽奖的只有一个动作,不存在多线程操作导致所抽奖品已经被抽完的情况。简单来说,就是不存在需要锁的场景,也不存在数据同步的情况。
这种场景的抽奖是最容易实现的,我这里的实现思路就是首先获取参与抽奖的总数,然后根据奖品数量生成几个随机数来确定中奖者。
我这里的实现首先需要上传奖品,然后上传抽奖名单,最后再根据奖品类别和数据库中未中奖用户的数量来生成随机数:
// 奖品
type Gift struct {
gorm.Model
Name string `json:"name" gorm:"type:varchar(32)"` // 奖品名称
GiftType int `json:"giftType"` // 奖品类别
Number int `json:"number"` // 奖品数据
}
type Gifts struct {
Gifts []*Gift `json:"gifts"`
}
func (p *Gift) TableName() string {
return "gifts"
}
func (p *Gift) Create(ctx context.Context, db *gorm.DB) error {
tx := db.Begin()
if err := tx.Save(p).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
// 抽奖人员
type Awarder struct {
Name string `json:"name" gorm:"type:varchar(32)"` // 抽奖人员名称
ID string `json:"id" gorm:"primarykey;type:varchar(32)"` // 抽奖人员id
Prize string `json:"prize" gorm:"type:varchar(32);default:prize"` // 奖品
}
func (a Awarder) Equal(o Awarder) bool {
if a.Name != o.Name {
return false
}
if a.ID != o.ID {
return false
}
return true
}
type Awarders struct {
Awarders []*Awarder `json:"awarders"`
}
func (p *Awarder) TableName() string {
return "awarders"
}
func (p *Awarder) Create(ctx context.Context, db *gorm.DB) error {
tx := db.Begin()
if err := tx.Save(p).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
type Draw struct {
GiftType int `json:"giftType" form:"giftType"`
}
func award(ctx *gin.Context) {
draw := &Draw{}
if err := ctx.Bind(draw); err != nil {
log.Errorf("Call award function error: %s", err.Error())
ctx.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "msg": err.Error()})
return
}
gift := &Gift{}
if err := dbHandler.Model(&Gift{}).Where(&Gift{GiftType: draw.GiftType}).First(gift).Error; err != nil {
log.Errorf("Get gifts from db error: %s", err.Error())
ctx.JSON(http.StatusOK, gin.H{"code": http.StatusInternalServerError, "msg": err.Error()})
return
}
if gift.Number == 0 {
log.Infof("there is no gift with name: %s", gift.Name)
ctx.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "msg": fmt.Sprintf("there is no gift with name: %s", gift.Name)})
return
}
var awarders []Awarder
if err := dbHandler.Model(&Awarder{}).Where("prize = ?", "prize").Find(&awarders).Error; err != nil {
log.Errorf("Get awarders from db error: %s", err.Error())
ctx.JSON(http.StatusOK, gin.H{"code": http.StatusInternalServerError, "msg": err.Error()})
return
}
if len(awarders) == 0 {
log.Info("there is no body without prize")
ctx.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "msg": "there is no body without prize"})
return
}
var winner []Awarder
if gift.Number > len(awarders) {
winner = make([]Awarder, len(awarders))
} else {
winner = make([]Awarder, gift.Number)
}
for i := 0; i < len(winner); i++ {
if gift.Number == 0 {
log.Info("no gift with name: %s", gift.Name)
break
}
gift.Number -= 1
index, err := rand.Int(rand.Reader, big.NewInt(int64(len(winner))))
if err != nil {
log.Errorf("Gen random number error: %s", err.Error())
ctx.JSON(http.StatusOK, gin.H{"code": http.StatusInternalServerError, "msg": err.Error()})
return
}
awarder := awarders[index.Int64()]
awarder.Prize = gift.Name
awarder.Create(context.TODO(), dbHandler)
awarders = delAwarder(awarders, awarder)
winner[i] = awarder
}
gift.Create(context.TODO(), dbHandler)
ctx.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "msg": "success", "data": winner})
}
func delAwarder(awarders []Awarder, awarder Awarder) []Awarder {
i := 0
for _, v := range awarders {
if !v.Equal(awarder) {
awarders[i] = v
i++
}
}
return awarders[:i]
}
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin92
Github: mengbin92
cnblogs: 恋水无意