mh_server/model/lottery.go

335 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
import (
"errors"
"github.com/codinl/go-logger"
"github.com/jinzhu/gorm"
"math/rand"
"time"
)
const (
LotteryPrizeTypePoint = 1 // 积分奖品
LotteryPrizeTypeCoupon = 2 // 优惠券奖品
LotteryPrizeTypePhysical = 3 // 实物奖品
LotteryPrizeTypeRentCard30 = 4 // 30天租卡会员兑换码
LotteryRecordStatusPending = 0 // 待处理
LotteryRecordStatusDelivered = 1 // 已发放
LotteryRecordStatusInProcess = 2 // 处理中
LotteryRecordStatusFailed = 3 // 失败
LotteryPrizeStatusEnabled = 1 // 启用奖品
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-实物 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 抽奖记录
type LotteryRecord struct {
Model
Uid uint `json:"uid"` // 用户ID
PrizeId uint `json:"prize_id"` // 奖品ID
PrizeName string `json:"prize_name"` // 奖品名称
PrizeType int `json:"prize_type"` // 奖品类型
PrizeLevel int `json:"prize_level"` // 奖品等级
PrizeValue int `json:"prize_value"` // 奖品值积分数或券ID等
Status int `json:"status"` // 状态0-待处理 1-已发放 2-处理中 3-失败
IsWin bool `json:"is_win"` // 是否中奖false 表示“谢谢参与”等无奖项)
Images string `json:"images" gorm:"-"` // 奖品图片
}
// LotteryPrizeOrder 抽奖奖品订单(包含用户收件信息、物流信息、发货状态)
type LotteryPrizeOrder struct {
Model
RecordId uint `json:"record_id"` // 抽奖记录ID
Uid uint `json:"uid"` // 用户ID
Tel string `json:"tel"` // 用户手机号
PrizeId uint `json:"prize_id"` // 奖品ID
PrizeName string `json:"prize_name"` // 奖品名称
// 用户提交的收货信息
ReceiverName string `json:"receiver_name"` // 收件人
ReceiverPhone string `json:"receiver_phone"` // 收件人手机号
ReceiverAddr string `json:"receiver_addr"` // 收件地址
// 发货信息
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-取消
}
// LotteryStats 抽奖统计表
type LotteryStats struct {
Model
TotalDraw uint `json:"total_draw"` // 总抽奖数
}
// LotteryWhiteList 抽奖白名单表
type LotteryWhiteList struct {
Model
Uid uint `json:"uid"` // 用户ID
DrawNumber uint `json:"draw_number"` // 第几次抽奖命中如第3次
PrizeLevel uint `json:"prize_level"` // 奖品等级
}
type LotteryDrawResponse struct {
PrizeID int `json:"prize_id"` // 奖品id
PrizeName string `json:"prize_name"` // 奖品名称
PrizeLevel int `json:"prize_level"` // 奖品等级
Message string `json:"message"` // 中奖提示信息
}
type LotteryPrizeQuery struct {
PageNum int `json:"page_num"`
PageSize int `json:"page_size"`
}
type LotteryPrizePageResponse struct {
Count uint32 `json:"count"` // 奖品总数
PageNum int `json:"page_num"` // 当前页码
List []LotteryPrize `json:"list"` // 奖品列表
}
type LotteryRecordQuery struct {
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 {
Count uint32 `json:"count"` // 总记录数
PageNum int `json:"page_num"` // 当前页码
List []LotteryRecord `json:"list"` // 抽奖记录列表
}
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 {
// 假设奖品等级字段为 PrizeLevel4等奖等级为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 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 len(available) == 0 || totalWeight <= 0 {
return LotteryPrize{}, errors.New("无可抽奖奖品")
}
logger.Infof("此次抽奖总权重:%d", totalWeight)
// 3. 随机抽奖
r := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(totalWeight)
logger.Infof("随机数是:%d", r)
acc := 0
var selected *LotteryPrize
for i := range available {
acc += available[i].Weight
if r < acc {
selected = &available[i]
logger.Infof("抽中的奖品是:%s", available[i].Name)
break
}
}
// 安全检查
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(tx *gorm.DB) error {
return tx.Exec("UPDATE lottery_stats SET total_draw = total_draw + 1 WHERE id = 1").Error
}
// GetLotteryDrawStats 查询某个用户的抽奖次数和全站总抽奖次数
func GetLotteryDrawStats(uid uint32) (userDrawCount int, totalDrawCount int, err error) {
// 查询用户抽奖次数
err = DB.Model(&LotteryRecord{}).
Where("uid = ?", uid).
Count(&userDrawCount).Error
if err != nil {
return
}
// 查询总抽奖次数(从 lottery_stats 表)
var stats LotteryStats
err = DB.First(&stats).Error
if err != nil {
return
}
totalDrawCount = int(stats.TotalDraw)
return
}
type RecentWinnersQuery struct {
Limit int `json:"limit"` // 期望获取的记录数
}
type RecentWinnersResponse struct {
Count uint32 `json:"count"` // 实际中奖总数
List []LotteryRecord `json:"list"` // 最近中奖记录列表
}
type SubmitDeliveryRequest struct {
RecordId uint `json:"record_id" binding:"required"` // 抽奖记录ID必填
ReceiverName string `json:"receiver_name" binding:"required"` // 收件人姓名,必填
ReceiverPhone string `json:"receiver_phone" binding:"required"` // 收件人手机号,必填
ReceiverAddr string `json:"receiver_addr" binding:"required"` // 收件人地址,必填
}
type ConfirmReceiptRequest struct {
RecordId uint `json:"record_id" binding:"required"` // 抽奖记录ID必填
}
type TodayDrawCountResponse struct {
DrawCount int `json:"draw_count"` // 今日抽奖次数
}
type GetLotteryPrizeOrderDetailRequest struct {
OrderID uint `json:"order_id" binding:"required"` // 奖品订单ID
}
type LotteryPrizeOrderDetailResponse struct {
LotteryPrizeOrder
// 用户信息
Nickname string `json:"nickname"` // 用户昵称
Tel string `json:"tel"` // 用户手机号
MemberLevel uint32 `json:"member_level"` // 当前会员等级
StoreName string `json:"store_name"` // 所属门店
// 奖品信息(扩展字段)
PrizeType int `json:"prize_type"` // 奖品类型
PrizeLevel int `json:"prize_level"` // 奖品等级
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
}