2025-08-06 10:07:11 +00:00
|
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
2025-08-14 03:43:14 +00:00
|
|
|
|
"github.com/codinl/go-logger"
|
|
|
|
|
"github.com/jinzhu/gorm"
|
2025-08-06 10:07:11 +00:00
|
|
|
|
"math/rand"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2025-08-14 03:43:14 +00:00
|
|
|
|
LotteryPrizeTypePoint = 1 // 积分奖品
|
|
|
|
|
LotteryPrizeTypeCoupon = 2 // 优惠券奖品
|
|
|
|
|
LotteryPrizeTypePhysical = 3 // 实物奖品
|
|
|
|
|
LotteryPrizeTypeRentCard30 = 4 // 30天租卡会员兑换码
|
2025-08-06 10:07:11 +00:00
|
|
|
|
|
|
|
|
|
LotteryRecordStatusPending = 0 // 待处理
|
|
|
|
|
LotteryRecordStatusDelivered = 1 // 已发放
|
|
|
|
|
LotteryRecordStatusInProcess = 2 // 处理中
|
|
|
|
|
LotteryRecordStatusFailed = 3 // 失败
|
|
|
|
|
|
|
|
|
|
LotteryPrizeStatusEnabled = 1 // 启用奖品
|
|
|
|
|
LotteryPrizeStatusDisabled = 2 // 禁用奖品
|
|
|
|
|
|
|
|
|
|
LotteryPrizeTypeNone = 0 // 谢谢参与
|
2025-08-14 03:43:14 +00:00
|
|
|
|
|
2025-08-18 06:38:03 +00:00
|
|
|
|
LotteryCheckLimit = 60 // 抽奖超过60次时判断是否有中过1-4等奖
|
|
|
|
|
LotteryDefaultLevel = 4 // 抽奖超过60次时且未中过1-4等奖,则默认中的奖项
|
2025-08-06 10:07:11 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LotteryPrize 奖品信息表(抽奖)
|
|
|
|
|
type LotteryPrize struct {
|
|
|
|
|
Model
|
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
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"` // 奖品图片
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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"` // 收件地址
|
|
|
|
|
|
|
|
|
|
// 发货信息
|
2025-08-14 03:43:14 +00:00
|
|
|
|
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"` // 收货时间
|
2025-08-06 10:07:11 +00:00
|
|
|
|
|
|
|
|
|
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次)
|
2025-08-14 03:43:14 +00:00
|
|
|
|
PrizeLevel uint `json:"prize_level"` // 奖品等级
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 06:38:03 +00:00
|
|
|
|
// LotteryBlackList 抽奖黑名单表
|
|
|
|
|
type LotteryBlackList struct {
|
|
|
|
|
Model
|
|
|
|
|
Uid uint `json:"uid"` // 用户ID
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 10:07:11 +00:00
|
|
|
|
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 {
|
2025-08-14 03:43:14 +00:00
|
|
|
|
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天租卡会员兑换码
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LotteryRecordPageResponse struct {
|
|
|
|
|
Count uint32 `json:"count"` // 总记录数
|
|
|
|
|
PageNum int `json:"page_num"` // 当前页码
|
|
|
|
|
List []LotteryRecord `json:"list"` // 抽奖记录列表
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
func DrawFromDatabasePrizes(prizes []LotteryPrize, userDrawCount, globalDrawCount int, totalAmount float64,
|
|
|
|
|
userPrizeIds map[uint32]bool, hasWon4OrAbove bool, isRentalMember bool) (LotteryPrize, error) {
|
2025-08-18 06:38:03 +00:00
|
|
|
|
// 1. 判断是否超过60次且未中过4等奖以上,强制发4等奖
|
2025-08-14 03:43:14 +00:00
|
|
|
|
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等奖奖品")
|
|
|
|
|
}
|
2025-08-06 10:07:11 +00:00
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
// 2. 筛选可抽奖品
|
|
|
|
|
available := make([]LotteryPrize, 0)
|
|
|
|
|
totalWeight := 0
|
2025-08-06 10:07:11 +00:00
|
|
|
|
for _, p := range prizes {
|
2025-08-14 03:43:14 +00:00
|
|
|
|
// 权重无效直接跳过
|
|
|
|
|
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)
|
|
|
|
|
// 超过当天消费限制,直接加入,不限制次数
|
2025-08-06 10:07:11 +00:00
|
|
|
|
available = append(available, p)
|
|
|
|
|
totalWeight += p.Weight
|
2025-08-14 03:43:14 +00:00
|
|
|
|
continue
|
|
|
|
|
} else {
|
|
|
|
|
// 同时满足两个解锁条件才可参与抽奖
|
|
|
|
|
if userDrawCount >= p.UnlockUserCount && globalDrawCount >= p.UnlockTotalCount {
|
|
|
|
|
logger.Infof("用户抽奖次数及总抽奖次数满足要求,可以抽取奖品:%s", p.Name)
|
|
|
|
|
available = append(available, p)
|
|
|
|
|
totalWeight += p.Weight
|
|
|
|
|
}
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
if len(available) == 0 || totalWeight <= 0 {
|
2025-08-06 10:07:11 +00:00
|
|
|
|
return LotteryPrize{}, errors.New("无可抽奖奖品")
|
|
|
|
|
}
|
2025-08-14 03:43:14 +00:00
|
|
|
|
logger.Infof("此次抽奖总权重:%d", totalWeight)
|
2025-08-06 10:07:11 +00:00
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
// 3. 随机抽奖
|
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(totalWeight)
|
|
|
|
|
logger.Infof("随机数是:%d", r)
|
2025-08-06 10:07:11 +00:00
|
|
|
|
acc := 0
|
2025-08-14 03:43:14 +00:00
|
|
|
|
var selected *LotteryPrize
|
|
|
|
|
for i := range available {
|
|
|
|
|
acc += available[i].Weight
|
2025-08-06 10:07:11 +00:00
|
|
|
|
if r < acc {
|
2025-08-14 03:43:14 +00:00
|
|
|
|
selected = &available[i]
|
|
|
|
|
logger.Infof("抽中的奖品是:%s", available[i].Name)
|
|
|
|
|
break
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-14 03:43:14 +00:00
|
|
|
|
// 安全检查
|
|
|
|
|
if selected == nil {
|
|
|
|
|
return LotteryPrize{}, errors.New("随机抽取失败,权重配置异常")
|
|
|
|
|
}
|
2025-08-06 10:07:11 +00:00
|
|
|
|
|
2025-08-14 03:43:14 +00:00
|
|
|
|
// 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("重复中奖且无积分奖项可替代")
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
2025-08-14 03:43:14 +00:00
|
|
|
|
|
|
|
|
|
return *selected, nil
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IncrementTotalDraw 更新总抽奖数
|
2025-08-14 03:43:14 +00:00
|
|
|
|
func IncrementTotalDraw(tx *gorm.DB) error {
|
|
|
|
|
return tx.Exec("UPDATE lottery_stats SET total_draw = total_draw + 1 WHERE id = 1").Error
|
2025-08-06 10:07:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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"` // 奖品图片
|
|
|
|
|
}
|
2025-08-14 03:43:14 +00:00
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|