1、优化积分抽奖接口;
This commit is contained in:
parent
8509413f9f
commit
e3cc4705d1
|
@ -1,11 +1,11 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/codinl/go-logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
"mh-server/lib/auth"
|
||||
"mh-server/lib/status"
|
||||
"mh-server/lib/utils"
|
||||
|
@ -27,6 +27,7 @@ func LotteryDraw(c *gin.Context) {
|
|||
RespJson(c, status.Unauthorized, nil)
|
||||
return
|
||||
}
|
||||
logger.Info("参与抽奖用户uid:", uc.Uid)
|
||||
|
||||
lotteryConfig, _ := model.GetLotteryConfig()
|
||||
if !lotteryConfig.LotteryEnabled {
|
||||
|
@ -39,19 +40,23 @@ func LotteryDraw(c *gin.Context) {
|
|||
|
||||
// 1. 判断用户当前积分是否足够
|
||||
var userVm model.UserVm
|
||||
if err := model.DB.Where("uid = ?", uc.Uid).First(&userVm).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) || userVm.Vm < uint32(lotteryConfig.CostPerDraw) {
|
||||
RespJson(c, 400, "积分不足,无法抽奖")
|
||||
} else {
|
||||
RespJson(c, 500, "查询积分失败")
|
||||
}
|
||||
err := model.DB.Where("uid = ?", uc.Uid).First(&userVm).Error
|
||||
if err != nil {
|
||||
logger.Errorf("查询用户积分失败 uid=%d err=%v", uc.Uid, err)
|
||||
RespJson(c, 400, "积分不足,无法抽奖")
|
||||
return
|
||||
}
|
||||
if userVm.Vm < uint32(lotteryConfig.CostPerDraw) {
|
||||
logger.Warnf("积分不足 uid=%d 积分=%d", uc.Uid, userVm.Vm)
|
||||
RespJson(c, 400, "积分不足,无法抽奖")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 获取可用奖品
|
||||
var prizes []model.LotteryPrize
|
||||
if err := model.DB.Where("status = ? AND stock > 0", model.LotteryPrizeStatusEnabled).Find(&prizes).Error; err != nil {
|
||||
RespJson(c, 500, "读取奖品失败")
|
||||
logger.Error("读取奖品失败:", err)
|
||||
RespJson(c, 500, "奖品更新中,请稍后再试...")
|
||||
return
|
||||
}
|
||||
if len(prizes) == 0 {
|
||||
|
@ -65,8 +70,9 @@ func LotteryDraw(c *gin.Context) {
|
|||
}
|
||||
|
||||
// 3. 扣除积分
|
||||
err := model.UserVmUpdateTx(model.DB, uc.Uid, -int(lotteryConfig.CostPerDraw), "lottery", "抽奖消耗积分")
|
||||
err = model.UserVmUpdateTx(model.DB, uc.Uid, -int(lotteryConfig.CostPerDraw), "lottery", "抽奖消耗积分")
|
||||
if err != nil {
|
||||
logger.Errorf("积分扣除失败 uid=%d err=%v", uc.Uid, err)
|
||||
RespJson(c, 400, "积分扣除失败")
|
||||
return
|
||||
}
|
||||
|
@ -77,67 +83,91 @@ func LotteryDraw(c *gin.Context) {
|
|||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
RespJson(c, 500, "系统异常")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// 5. 查询抽奖次数
|
||||
// 5. 查询抽奖次数 & 更新总抽奖数
|
||||
drawNumber, totalDrawNumber, err := model.GetLotteryDrawStats(uc.Uid)
|
||||
if err != nil {
|
||||
drawNumber = 0
|
||||
totalDrawNumber = 0
|
||||
}
|
||||
logger.Infof("用户抽奖次数为:%d,总抽奖次数为:%d", drawNumber, totalDrawNumber)
|
||||
|
||||
// 更新总抽奖数
|
||||
if err = model.IncrementTotalDraw(); err != nil {
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
logger.Error("更新总抽奖次数失败:", err)
|
||||
RespJson(c, 500, "抽奖失败,系统异常")
|
||||
if err = model.IncrementTotalDraw(tx); err != nil {
|
||||
tx.Rollback()
|
||||
logger.Errorf("更新总抽奖次数失败 uid=%d err=%v", uc.Uid, err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 6. 白名单判断
|
||||
// 6. 获取用户信息
|
||||
userInfo := model.GetUserByUidTx(tx, uc.Uid)
|
||||
if userInfo == nil {
|
||||
tx.Rollback()
|
||||
logger.Error("查询用户信息异常,uid:", uc.Uid)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 7. 白名单判断
|
||||
var prize model.LotteryPrize
|
||||
var whiteEntry model.LotteryWhiteList
|
||||
err = model.DB.
|
||||
Where("uid = ?", uc.Uid).First(&whiteEntry).Error
|
||||
|
||||
if err == nil {
|
||||
// 命中白名单,直接返回配置奖品
|
||||
err = model.DB.First(&prize, whiteEntry.PrizeID).Error
|
||||
if err != nil {
|
||||
RespOK(c, model.LotteryDrawResponse{
|
||||
PrizeID: 0,
|
||||
PrizeName: "谢谢参与",
|
||||
PrizeLevel: 0,
|
||||
Message: "很遗憾未中奖,祝你下次好运!",
|
||||
})
|
||||
err = tx.Where("uid = ?", uc.Uid).First(&whiteEntry).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) || (err == nil && drawNumber < int(whiteEntry.DrawNumber)) { // 普通抽奖
|
||||
if err == nil && drawNumber < int(whiteEntry.DrawNumber) {
|
||||
logger.Errorf("用户%d在白名单,但抽奖次数 %d 小于设定次数 %d", uc.Uid, drawNumber, whiteEntry.DrawNumber)
|
||||
}
|
||||
} else {
|
||||
// 查询用户当天消费金额
|
||||
userTotalAmount := model.GetUserDailyTotalAmount(uc.Uid)
|
||||
|
||||
isRentalMember := false
|
||||
// 判断用户是否为租卡会员
|
||||
if userInfo.IsMemberNew() {
|
||||
isRentalMember = true
|
||||
}
|
||||
|
||||
// 当用户抽奖次数超过40,则判断是否中过1-4等奖;收集用户已中奖的奖项
|
||||
hasWon4OrAbove, userPrizeIds, _ := model.PrepareUserLotteryInfo(uc.Uid)
|
||||
|
||||
// 普通抽奖逻辑
|
||||
prize, err = model.DrawFromDatabasePrizes(prizes, drawNumber, totalDrawNumber)
|
||||
prize, err = model.DrawFromDatabasePrizes(prizes, drawNumber, totalDrawNumber, userTotalAmount, userPrizeIds,
|
||||
hasWon4OrAbove, isRentalMember)
|
||||
if err != nil {
|
||||
RespOK(c, model.LotteryDrawResponse{
|
||||
PrizeID: 0,
|
||||
PrizeName: "谢谢参与",
|
||||
PrizeLevel: 0,
|
||||
Message: "很遗憾未中奖,祝你下次好运!",
|
||||
})
|
||||
tx.Rollback()
|
||||
logger.Errorf("DrawFromDatabasePrizes err uid=%d err=%v", uc.Uid, err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
logger.Errorf("查询白名单失败 uid=%d err=%v", uc.Uid, err)
|
||||
RespJson(c, 500, "系统异常,请稍后再试...")
|
||||
return
|
||||
} else {
|
||||
logger.Infof("用户%d在白名单中,%d等奖,第%d次中奖", uc.Uid, whiteEntry.PrizeLevel, whiteEntry.DrawNumber)
|
||||
// 命中白名单,直接返回配置奖品
|
||||
err = tx.Where("level = ? and stock > 1", whiteEntry.PrizeLevel).First(&prize).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Errorf("白名单抽奖失败 uid=%d err=%v", uc.Uid, err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 减少奖品库存(乐观锁)
|
||||
// 8. 减少奖品库存(乐观锁)
|
||||
result := tx.Model(&model.LotteryPrize{}).
|
||||
Where("id = ? AND stock > 0", prize.ID).
|
||||
UpdateColumn("stock", gorm.Expr("stock - 1"))
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
tx.Rollback()
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
RespJson(c, 500, "奖品库存不足")
|
||||
logger.Errorf("减少奖品库存失败 uid=%d err=%v", uc.Uid, result.Error)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 8. 记录中奖信息
|
||||
// 9. 记录中奖信息
|
||||
isWin := prize.PrizeType != model.LotteryPrizeTypeNone
|
||||
lotteryRecord := model.LotteryRecord{
|
||||
Uid: uint(uc.Uid),
|
||||
|
@ -154,34 +184,63 @@ func LotteryDraw(c *gin.Context) {
|
|||
err = tx.Create(&lotteryRecord).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
RespJson(c, 500, "记录中奖信息失败")
|
||||
logger.Errorf("记录中奖信息失败 uid=%d err=%v", uc.Uid, err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是积分奖品,则直接发放积分
|
||||
if prize.PrizeType == model.LotteryPrizeTypePoint {
|
||||
err = model.UserVmUpdateTx(tx, uc.Uid, prize.PrizeValue, "lottery_add", "抽奖赢取积分")
|
||||
// 10. 发放奖品 如果是租卡会员兑换码,则直接发放兑换券
|
||||
if prize.PrizeType == model.LotteryPrizeTypeRentCard30 {
|
||||
var redeemCode model.RedeemCode
|
||||
err = tx.Table("redeem_code").Where("status=?", model.RedeemCodeStatusStock).
|
||||
Where("code_type=?", model.CodeTypeMemberCard30).Order("id ASC").Limit(1).Find(&redeemCode).Error
|
||||
if err != nil {
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
tx.Rollback()
|
||||
RespJson(c, 400, "积分发放失败")
|
||||
logger.Error("get redeem_code err:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是优惠券奖品,则直接发放优惠券
|
||||
if prize.PrizeType == model.LotteryPrizeTypeCoupon {
|
||||
var coupon model.Coupon
|
||||
err = model.NewCouponQuerySet(model.DB).ActivityIdEq(uint32(prize.PrizeValue)).One(&coupon)
|
||||
userRedeemCode := &model.UserRedeemCode{
|
||||
Uid: uc.Uid,
|
||||
Status: model.UserRedeemCodeStatusHold,
|
||||
SerialCode: redeemCode.SerialCode,
|
||||
CodeType: redeemCode.CodeType,
|
||||
ActivityType: model.RedeemCodeActivityTypeLottery,
|
||||
StoreId: uint32(userInfo.StoreId),
|
||||
}
|
||||
err = tx.Create(userRedeemCode).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error("create userRedeemCode err:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
} else if prize.PrizeType == model.LotteryPrizeTypePoint { // 如果是积分奖品,则直接发放积分
|
||||
err = model.UserVmUpdateTx(tx, uc.Uid, prize.PrizeValue, "lottery_add", "抽奖赢取积分")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error("积分发放失败:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
} else if prize.PrizeType == model.LotteryPrizeTypeCoupon { // 如果是优惠券奖品,则直接发放优惠券
|
||||
var coupon model.Coupon
|
||||
err = model.NewCouponQuerySet(tx).ActivityIdEq(uint32(prize.PrizeValue)).One(&coupon)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error("get coupon err:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
if coupon.ID != 0 {
|
||||
couponCode, err := utils.GenerateRandomNumber19()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error("GenerateRandomNumber19err:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
userCoupon := &model.UserCoupon{
|
||||
Uid: uc.Uid,
|
||||
|
@ -205,26 +264,26 @@ func LotteryDraw(c *gin.Context) {
|
|||
|
||||
err = tx.Create(userCoupon).Error
|
||||
if err != nil {
|
||||
logger.Error("user coupon err:", err)
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
tx.Rollback()
|
||||
RespJson(c, 400, "优惠券发放失败")
|
||||
logger.Error("优惠券发放失败:", err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 9. 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
model.UserVmUpdateTx(model.DB, uc.Uid, int(lotteryConfig.CostPerDraw), "lottery", "抽奖失败积分补偿")
|
||||
RespJson(c, 500, "抽奖失败,请稍后重试")
|
||||
// 11. 提交事务
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
tx.Rollback()
|
||||
logger.Errorf("抽奖提交事务失败 uid=%d err=%v", uc.Uid, err)
|
||||
respNoPrize(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 10. 返回中奖结果
|
||||
// 12. 返回中奖结果
|
||||
var message string
|
||||
if prize.Name == "谢谢参与" || prize.PrizeType == model.LotteryPrizeTypeNone {
|
||||
message = "很遗憾未中奖,祝你下次好运~"
|
||||
message = "很遗憾未中奖,祝您下次好运~"
|
||||
} else {
|
||||
message = fmt.Sprintf("🎉 恭喜您获得【%s】", prize.Name)
|
||||
}
|
||||
|
@ -235,7 +294,15 @@ func LotteryDraw(c *gin.Context) {
|
|||
PrizeLevel: prize.Level,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func respNoPrize(c *gin.Context) {
|
||||
RespOK(c, model.LotteryDrawResponse{
|
||||
PrizeID: 8,
|
||||
PrizeName: "谢谢参与",
|
||||
PrizeLevel: 0,
|
||||
Message: "很遗憾未中奖,祝您下次好运!",
|
||||
})
|
||||
}
|
||||
|
||||
// LotteryPrizes 奖品列表接口
|
||||
|
@ -333,6 +400,13 @@ func LotteryRecords(c *gin.Context) {
|
|||
db = db.Where("r.is_win = ?", false)
|
||||
}
|
||||
|
||||
if req.PrizeLevel != 0 {
|
||||
db = db.Where("r.prize_level = ?", req.PrizeLevel)
|
||||
}
|
||||
if req.PrizeType != 0 {
|
||||
db = db.Where("r.prize_type = ?", req.PrizeType)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
|
|
|
@ -85,6 +85,13 @@ func (m *User) IsMember() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (m *User) IsMemberNew() bool {
|
||||
if m.MemberLevel == 2 || m.MemberLevel == 3 || m.MemberLevel == 4 || m.MemberLevel == 5 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//var GameCardListUpdateTime = time.Time{}
|
||||
|
||||
func GetGameCardList(sortType, page, pageSize int, gameTypeIds []uint64, storeId, uid uint32) ([]GameCard, uint32, error) {
|
||||
|
|
195
model/lottery.go
195
model/lottery.go
|
@ -2,14 +2,17 @@ package model
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/codinl/go-logger"
|
||||
"github.com/jinzhu/gorm"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LotteryPrizeTypePoint = 1 // 积分奖品
|
||||
LotteryPrizeTypeCoupon = 2 // 优惠券奖品
|
||||
LotteryPrizeTypePhysical = 3 // 实物奖品
|
||||
LotteryPrizeTypePoint = 1 // 积分奖品
|
||||
LotteryPrizeTypeCoupon = 2 // 优惠券奖品
|
||||
LotteryPrizeTypePhysical = 3 // 实物奖品
|
||||
LotteryPrizeTypeRentCard30 = 4 // 30天租卡会员兑换码
|
||||
|
||||
LotteryRecordStatusPending = 0 // 待处理
|
||||
LotteryRecordStatusDelivered = 1 // 已发放
|
||||
|
@ -20,22 +23,26 @@ const (
|
|||
LotteryPrizeStatusDisabled = 2 // 禁用奖品
|
||||
|
||||
LotteryPrizeTypeNone = 0 // 谢谢参与
|
||||
|
||||
LotteryCheckLimit = 40 // 抽奖超过40次时判断是否有中过1-4等奖
|
||||
LotteryDefaultLevel = 4 // 抽奖超过40次时且未中过1-4等奖,则默认中的奖项
|
||||
)
|
||||
|
||||
// LotteryPrize 奖品信息表(抽奖)
|
||||
type LotteryPrize struct {
|
||||
Model
|
||||
|
||||
Name string `json:"name"` // 奖品名称
|
||||
PrizeType int `json:"prize_type"` // 奖品类型:1-积分 2-优惠券 3-实物 0-谢谢参与
|
||||
PrizeValue int `json:"prize_value"` // 奖品值(积分数或券ID等)
|
||||
Level int `json:"level"` // 奖品等级,如1等奖、2等奖等
|
||||
Weight int `json:"weight"` // 抽奖权重
|
||||
Stock int `json:"stock"` // 剩余库存
|
||||
Status int `json:"status"` // 奖品状态:1-启用 2-禁用
|
||||
UnlockUserCount int `json:"unlock_user_count"` // 解锁条件:用户个人抽奖次数
|
||||
UnlockTotalCount int `json:"unlock_total_count"` // 解锁条件:所有用户总抽奖次数
|
||||
Images string `json:"images"` // 奖品图片
|
||||
Name string `json:"name"` // 奖品名称
|
||||
PrizeType int `json:"prize_type"` // 奖品类型:1-积分 2-优惠券 3-实物 4-30天租卡会员兑换码 0-谢谢参与
|
||||
PrizeValue int `json:"prize_value"` // 奖品值(积分数或券ID等)
|
||||
Level int `json:"level"` // 奖品等级,如1等奖、2等奖等
|
||||
Weight int `json:"weight"` // 抽奖权重
|
||||
Stock int `json:"stock"` // 剩余库存
|
||||
Status int `json:"status"` // 奖品状态:1-启用 2-禁用
|
||||
UnlockUserCount int `json:"unlock_user_count"` // 解锁条件:用户个人抽奖次数
|
||||
UnlockTotalCount int `json:"unlock_total_count"` // 解锁条件:所有用户总抽奖次数
|
||||
DailyAmountLimit float64 `json:"daily_amount_limit"` // 当天消费金额限制(0 表示不限制)
|
||||
Images string `json:"images"` // 奖品图片
|
||||
}
|
||||
|
||||
// LotteryRecord 抽奖记录
|
||||
|
@ -69,10 +76,12 @@ type LotteryPrizeOrder struct {
|
|||
ReceiverAddr string `json:"receiver_addr"` // 收件地址
|
||||
|
||||
// 发货信息
|
||||
LogisticsCompany string `json:"logistics_company"` // 快递公司
|
||||
LogisticsNumber string `json:"logistics_number"` // 快递单号
|
||||
ShippedAt time.Time `json:"shipped_at"` // 发货时间
|
||||
ReceivedAt time.Time `json:"received_at"` // 收货时间
|
||||
LogisticsCompany string `json:"logistics_company"` // 快递公司
|
||||
LogisticsNumber string `json:"logistics_number"` // 快递单号
|
||||
DeliverShopperCode uint32 `json:"deliver_shopper_code"` // 发货店员用户ID
|
||||
DeliverShopperName string `json:"deliver_shopper_name"` // 发货店员名称
|
||||
ShippedAt *time.Time `json:"shipped_at"` // 发货时间
|
||||
ReceivedAt *time.Time `json:"received_at"` // 收货时间
|
||||
|
||||
Status int `json:"status"` // 发货状态:0-待发货 1-已发货 2-已收货 3-取消
|
||||
}
|
||||
|
@ -90,7 +99,7 @@ type LotteryWhiteList struct {
|
|||
|
||||
Uid uint `json:"uid"` // 用户ID
|
||||
DrawNumber uint `json:"draw_number"` // 第几次抽奖命中(如第3次)
|
||||
PrizeID uint `json:"prize_id"` // 奖品ID(外键指向 LotteryPrize)
|
||||
PrizeLevel uint `json:"prize_level"` // 奖品等级
|
||||
}
|
||||
|
||||
type LotteryDrawResponse struct {
|
||||
|
@ -112,9 +121,11 @@ type LotteryPrizePageResponse struct {
|
|||
}
|
||||
|
||||
type LotteryRecordQuery struct {
|
||||
PageNum int `json:"page_num"`
|
||||
PageSize int `json:"page_size"`
|
||||
WinStatus int `json:"win_status"` // 新增字段:0=全部,1=已中奖,2=未中奖
|
||||
PageNum int `json:"page_num"`
|
||||
PageSize int `json:"page_size"`
|
||||
WinStatus int `json:"win_status"` // 新增字段:0=全部,1=已中奖,2=未中奖
|
||||
PrizeLevel int `json:"prize_level"` // 奖品等级
|
||||
PrizeType int `json:"prize_type"` // 奖品类型 1-积分 2-优惠券 3-实物 4-30天租卡会员兑换码
|
||||
}
|
||||
|
||||
type LotteryRecordPageResponse struct {
|
||||
|
@ -123,52 +134,91 @@ type LotteryRecordPageResponse struct {
|
|||
List []LotteryRecord `json:"list"` // 抽奖记录列表
|
||||
}
|
||||
|
||||
func DrawFromDatabasePrizes(prizes []LotteryPrize, userDrawCount, globalDrawCount int) (LotteryPrize, error) {
|
||||
var available []LotteryPrize
|
||||
totalWeight := 0
|
||||
func DrawFromDatabasePrizes(prizes []LotteryPrize, userDrawCount, globalDrawCount int, totalAmount float64,
|
||||
userPrizeIds map[uint32]bool, hasWon4OrAbove bool, isRentalMember bool) (LotteryPrize, error) {
|
||||
// 1. 判断是否超过40次且未中过4等奖以上,强制发4等奖
|
||||
if userDrawCount > LotteryCheckLimit && !hasWon4OrAbove {
|
||||
for _, p := range prizes {
|
||||
// 假设奖品等级字段为 PrizeLevel,4等奖等级为4
|
||||
if p.Level == LotteryDefaultLevel && p.Stock > 0 {
|
||||
logger.Infof("用户抽奖40次且未中过4等奖以上,强制发4等奖")
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return LotteryPrize{}, errors.New("未找到4等奖奖品")
|
||||
}
|
||||
|
||||
// 2. 筛选可抽奖品
|
||||
available := make([]LotteryPrize, 0)
|
||||
totalWeight := 0
|
||||
for _, p := range prizes {
|
||||
// 同时满足两个解锁条件才可参与抽奖
|
||||
if userDrawCount >= p.UnlockUserCount && globalDrawCount >= p.UnlockTotalCount {
|
||||
// 权重无效直接跳过
|
||||
if p.Weight <= 0 || p.Stock <= 0 {
|
||||
continue
|
||||
}
|
||||
// 用户是租卡会员则不能抽30日租卡会员奖项
|
||||
if p.PrizeType == LotteryPrizeTypeRentCard30 && isRentalMember {
|
||||
logger.Infof("用户已是租卡会员,不再抽取30天租卡会员兑换码")
|
||||
continue
|
||||
}
|
||||
|
||||
if p.DailyAmountLimit != 0 && totalAmount > p.DailyAmountLimit {
|
||||
logger.Infof("用户当天有消费,且满足消费金额,可以抽取奖品:%s", p.Name)
|
||||
// 超过当天消费限制,直接加入,不限制次数
|
||||
available = append(available, p)
|
||||
totalWeight += p.Weight
|
||||
continue
|
||||
} else {
|
||||
// 同时满足两个解锁条件才可参与抽奖
|
||||
if userDrawCount >= p.UnlockUserCount && globalDrawCount >= p.UnlockTotalCount {
|
||||
logger.Infof("用户抽奖次数及总抽奖次数满足要求,可以抽取奖品:%s", p.Name)
|
||||
available = append(available, p)
|
||||
totalWeight += p.Weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if totalWeight == 0 {
|
||||
if len(available) == 0 || totalWeight <= 0 {
|
||||
return LotteryPrize{}, errors.New("无可抽奖奖品")
|
||||
}
|
||||
logger.Infof("此次抽奖总权重:%d", totalWeight)
|
||||
|
||||
r := rand.Intn(totalWeight)
|
||||
// 3. 随机抽奖
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(totalWeight)
|
||||
logger.Infof("随机数是:%d", r)
|
||||
acc := 0
|
||||
for _, p := range available {
|
||||
acc += p.Weight
|
||||
var selected *LotteryPrize
|
||||
for i := range available {
|
||||
acc += available[i].Weight
|
||||
if r < acc {
|
||||
return p, nil
|
||||
selected = &available[i]
|
||||
logger.Infof("抽中的奖品是:%s", available[i].Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return LotteryPrize{}, errors.New("抽奖失败")
|
||||
}
|
||||
|
||||
func getUnlockDrawCount(level int) int {
|
||||
switch level {
|
||||
case 1:
|
||||
return 5000
|
||||
case 2:
|
||||
return 2000
|
||||
case 3:
|
||||
return 1000
|
||||
case 4:
|
||||
return 500
|
||||
default:
|
||||
return 0
|
||||
// 安全检查
|
||||
if selected == nil {
|
||||
return LotteryPrize{}, errors.New("随机抽取失败,权重配置异常")
|
||||
}
|
||||
|
||||
// 4. 处理重复中奖 同一用户不重复中某个奖项,如再次中奖则自动替换为20积分
|
||||
if userPrizeIds[selected.ID] {
|
||||
for _, p := range prizes {
|
||||
if p.PrizeType == LotteryPrizeTypePoint && p.Stock > 0 {
|
||||
logger.Info("用户重复中奖,自动替换奖品。", "uid:", selected.ID, "替换为", p.Name)
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
// 如果没找到积分奖项,也要安全返回
|
||||
return LotteryPrize{}, errors.New("重复中奖且无积分奖项可替代")
|
||||
}
|
||||
|
||||
return *selected, nil
|
||||
}
|
||||
|
||||
// IncrementTotalDraw 更新总抽奖数
|
||||
func IncrementTotalDraw() error {
|
||||
return DB.Exec("UPDATE lottery_stats SET total_draw = total_draw + 1 WHERE id = 1").Error
|
||||
func IncrementTotalDraw(tx *gorm.DB) error {
|
||||
return tx.Exec("UPDATE lottery_stats SET total_draw = total_draw + 1 WHERE id = 1").Error
|
||||
}
|
||||
|
||||
// GetLotteryDrawStats 查询某个用户的抽奖次数和全站总抽奖次数
|
||||
|
@ -235,3 +285,50 @@ type LotteryPrizeOrderDetailResponse struct {
|
|||
PrizeValue int `json:"prize_value"` // 奖品值
|
||||
Images string `json:"images"` // 奖品图片
|
||||
}
|
||||
|
||||
// GetUserDailyTotalAmount 查询某个用户当天的消费总金额
|
||||
func GetUserDailyTotalAmount(uid uint32) (totalAmount float64) {
|
||||
now := time.Now()
|
||||
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
endTime := startTime.Add(24 * time.Hour).Add(-time.Second)
|
||||
|
||||
var result struct {
|
||||
Amount float64
|
||||
}
|
||||
|
||||
err := DB.Model(&ErpOrder{}).
|
||||
Where("uid = ? AND created_at BETWEEN ? AND ?", uid, startTime, endTime).
|
||||
Select("SUM(total_amount) as amount").
|
||||
Scan(&result).Error
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return result.Amount
|
||||
}
|
||||
|
||||
// PrepareUserLotteryInfo 传入用户ID,判断是否中过4等奖及以上,收集用户已中奖奖品ID
|
||||
func PrepareUserLotteryInfo(uid uint32) (hasWon4OrAbove bool, userPrizeIds map[uint32]bool, err error) {
|
||||
// 初始化map
|
||||
userPrizeIds = make(map[uint32]bool)
|
||||
|
||||
// 查询用户已中奖记录,且状态是中奖的(IsWin = true)
|
||||
var records []LotteryRecord
|
||||
err = DB.Model(&LotteryRecord{}).
|
||||
Where("uid = ? AND is_win = ? AND prize_type != ?", uid, true, LotteryPrizeTypePoint).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否中过4等奖以上(奖品等级<=4,且中奖)
|
||||
hasWon4OrAbove = false
|
||||
for _, r := range records {
|
||||
userPrizeIds[uint32(r.PrizeId)] = true
|
||||
if r.PrizeLevel <= LotteryDefaultLevel {
|
||||
hasWon4OrAbove = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ const (
|
|||
const (
|
||||
RedeemCodeActivityTypeStore = 1 // 门店赠送
|
||||
RedeemCodeActivityTypeUserInvite = 2 // 用户邀请
|
||||
|
||||
RedeemCodeActivityTypeLottery = 3 // 积分抽奖
|
||||
)
|
||||
|
||||
// gen:qs
|
||||
|
|
|
@ -296,6 +296,15 @@ func GetUserByUid(uid uint32) *User {
|
|||
return user
|
||||
}
|
||||
|
||||
func GetUserByUidTx(tx *gorm.DB, uid uint32) *User {
|
||||
user := new(User)
|
||||
if err := NewUserQuerySet(tx).UidEq(uid).One(user); err != nil {
|
||||
logger.Error(err, uid)
|
||||
return user
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
const StoreDateTimeFormat = "2006.01.02"
|
||||
|
||||
// GetUserEffectiveStore 获取店员当前的有效门店(邀请客户时使用)
|
||||
|
|
Loading…
Reference in New Issue
Block a user