最近接手的项目中要新增一个抽奖功能,场景类似年会上的抽奖,触发抽奖的只有一个动作,不存在多线程操作导致所抽奖品已经被抽完的情况。简单来说,就是不存在需要锁的场景,也不存在数据同步的情况。

这种场景的抽奖是最容易实现的,我这里的实现思路就是首先获取参与抽奖的总数,然后根据奖品数量生成几个随机数来确定中奖者。

我这里的实现首先需要上传奖品,然后上传抽奖名单,最后再根据奖品类别和数据库中未中奖用户的数量来生成随机数:

// 奖品
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: 恋水无意