Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
package luckydrawser
import "time"
type PrizeType int32
const (
// 实物
InKindPrize PrizeType = iota + 1
// VIP
VipPrize
// 金币
GoldPrize
)
type VipType int32
const (
// 默认值
Default VipType = iota
// 月卡
MonthCard
// 季卡
QuarterlyCard
// 年卡
YearCard
// 终身会员卡
LifelongCard
)
type CenterBaseInfo struct {
FreeCount int32 `json:"freeCount"` // 免费次数
RefreshTime time.Time `json:"refreshTime"` // 免费次数恢复时间
PrizeList []*PrizeInfo `json:"prizeList"` // 奖品列表
DrawPrize int64 `json:"drawPrize"` // 单次抽奖价格
ActivityTitle string `json:"activityTitle"` // 活动标题
ActivityDesc string `json:"activityDesc"` // 活动注意事项
ActivityDate string `json:"activityDate"` // 活动时间
}
// 奖品信息
type PrizeInfo struct {
PrizeId int64 `json:"prizeId"`
PrizeName string `json:"prizeName"`
PrizeImgCover string `json:"prizeImgCover"`
PrizePrice int64 `json:"prizePrice"`
PrizeCount int32 `json:"prizeCount"`
PrizeType int32 `json:"prizeType"`
VipCard int32 `json:"vipCard"`
}
type CenterDrawReq struct {
AppId int32 `json:"appId"` // 应用id
ActivityId int64 `json:"activityId"` // 活动id
UserId int64 `json:"userId"` // 用户id
UserName string `json:"userName"`
UserAvatar string `json:"userAvatar"`
Count int `json:"count"` // 抽奖次数
}
// 抽奖返回
type CenterDrawResp struct {
List []*PrizeInfo `json:"list"`
}
// 抽奖记录
type CenterRecordReq struct {
AppId int32 `json:"appId"` // 应用id
ActivityId int64 `json:"activityId"` // 活动id
UserId int64 `json:"userId"` // 用户id
}
// 抽奖记录返回
type CenterRecordResp struct {
List []*RecordInfo `json:"list"`
}
// 跑马灯
type CenterMarqueeReq struct {
AppId int32 `json:"appId"` // 应用id
ActivityId int64 `json:"activityId"` // 活动id
}
// 跑马灯返回
type CenterMarqueeResp struct {
List []*RecordInfo `json:"list"`
}
// 幸运狼友
type CenterLuckUserReq struct {
AppId int32 `json:"appId"` // 应用id
ActivityId int64 `json:"activityId"` // 活动id
}
type CenterLuckUserResp struct {
List []*RecordInfo `json:"list"`
}
type RecordInfo struct {
PrizeName string `json:"prizeName"` // 奖品名称
CreateTime time.Time `json:"createTime"` // 创建时间
}
+206
View File
@@ -0,0 +1,206 @@
package luckydrawser
import (
"context"
"errors"
"fmt"
"time"
"91porn-server/common/timeutil"
"91porn-server/models/v/activitymod"
"91porn-server/models/v/prizemod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/walletmod"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func BaseInfo(ctx context.Context, in *BaseInfoReq) (out *BaseInfoResp, err error) {
now := time.Now()
wal, err := walletmod.GetWallet(in.UserId)
if err != nil {
return
}
id, err := primitive.ObjectIDFromHex(in.ActivityId)
if err != nil {
return nil, err
}
// 获取活动
activityInfo, err := activitymod.Get(id)
if err != nil {
return nil, err
}
if activityInfo == nil {
return nil, errors.New("活动不存在")
}
if !activityInfo.Status {
return nil, errors.New("活动未开启")
}
// 活动时间判定
if !now.After(activityInfo.StartTime) || now.After(activityInfo.EndTime) {
return nil, errors.New("活动已结束")
}
// 获取奖池配置
prizePoolInfo, err := activitymod.GetPrizePool(id)
if err != nil {
return nil, err
}
if prizePoolInfo == nil {
return nil, errors.New("奖池配置为空")
}
var freeCount int32
if prizePoolInfo.FreeCount > 0 {
// 获取用户免费记录
userFree, err := activitymod.GetUserFree(id, in.UserId)
if err != nil {
return nil, err
}
if userFree == nil {
if err = activitymod.AddUserFree(id, in.UserId, prizePoolInfo.FreeCount); err != nil {
return nil, err
}
freeCount = prizePoolInfo.FreeCount
} else {
// 今日已使用
if userFree.UpdateTime.After(timeutil.BeginningOfDay(time.Now())) {
freeCount = userFree.FreeCount
} else {
// 刷新免费次数
if err = activitymod.UpdateOneSet(id, in.UserId, prizePoolInfo.FreeCount); err != nil {
return nil, err
}
freeCount = prizePoolInfo.FreeCount
}
}
}
// 获取奖品列表
prizeList, err := prizemod.GetPrizeListByActivityID(id)
if err != nil {
return nil, err
}
if len(prizeList) == 0 {
return nil, errors.New("奖品配置为空")
}
// 获取用户当日抽奖消费金额
consumerAmount, _, err := txnmod.GetIncomeByType(in.UserId, txnmod.JiuGongGeDraw, timeutil.BeginningOfDay(now), timeutil.BeginningOfDay(now.AddDate(0, 0, 1)))
out = &BaseInfoResp{
Balance: wal.Amount,
FreeCount: freeCount,
Price: prizePoolInfo.Price,
Prizes: prizeList,
ConsumerAmount: -int64(consumerAmount),
ActivityTitle: activityInfo.Title,
ActivityDesc: activityInfo.Desc,
ActivityDate: activityInfo.StartTime.Format("2006-01-02") + "到" + activityInfo.EndTime.Format("2006-01-02"),
}
return
}
func Draw(ctx context.Context, in *DrawReq) ([]*prizemod.Prize, error) {
id, err := primitive.ObjectIDFromHex(in.ActivityId)
if err != nil {
return nil, err
}
// 获取活动
activityInfo, err := activitymod.Get(id)
if err != nil {
return nil, err
}
if activityInfo == nil {
return nil, errors.New("活动不存在")
} else {
if !activityInfo.Status {
return nil, errors.New("活动未开启")
}
// 活动时间判定
now := time.Now()
if !now.After(activityInfo.StartTime) || now.After(activityInfo.EndTime) {
return nil, errors.New("活动已结束")
}
}
// 获取奖品列表
prizeList, err := prizemod.GetPrizeListByActivityID(id)
if err != nil {
return nil, err
}
if len(prizeList) == 0 {
return nil, errors.New("奖品配置为空")
}
// 获取奖池配置
prizePoolInfo, err := activitymod.GetPrizePool(id)
if err != nil {
return nil, err
}
if prizePoolInfo == nil {
return nil, errors.New("奖池配置为空")
}
// 奖池模式处理
switch prizePoolInfo.Model {
case activitymod.Game:
case activitymod.Free:
return freeModelHanlder(in, id, prizeList, prizePoolInfo)
}
return nil, nil
}
// 个人抽奖记录
func Record(ctx context.Context, activityId string, uid uint64) (interface{}, error) {
id, err := primitive.ObjectIDFromHex(activityId)
if err != nil {
return nil, err
}
list, err := activitymod.QueryAllRecord(bson.M{"activityId": id, "uid": uid}, options.Find().SetLimit(100).SetSort(bson.M{"createTime": -1}))
if err != nil {
return nil, err
}
data := make([]map[string]interface{}, len(list))
for i, v := range list {
data[i] = map[string]interface{}{
"name": v.Name + "*" + decimal.NewFromInt32(v.Count).String(),
"createTime": v.CreateTime.Format("2006-01-02 15:04:05"),
}
}
return data, nil
}
func Marquee(activityId string) ([]string, error) {
id, err := primitive.ObjectIDFromHex(activityId)
if err != nil {
return nil, err
}
list, err := activitymod.GetMarquee(id)
if err != nil {
return nil, err
}
listLen := len(list)
uids := make([]uint64, listLen)
for i, v := range list {
uids[i] = v.UID
}
userMap := make(map[uint64]string, 10)
if len(uids) > 0 {
userList, err := usermod.FindUsersByUID(uids)
if err != nil {
return nil, err
}
for _, v := range userList {
userMap[v.UID] = v.Name
}
for _, v := range userList {
userMap[v.UID] = v.Name
}
}
strs := make([]string, listLen)
for i, v := range list {
if v.UserName != "" {
strs[i] = fmt.Sprintf("恭喜%s 抽中%s", v.UserName, v.Name)
continue
}
strs[i] = fmt.Sprintf("恭喜%s 抽中%s", userMap[v.UID], v.Name)
}
return strs, nil
}
+212
View File
@@ -0,0 +1,212 @@
package luckydrawser
import (
"91porn-server/models/v/taskrecordmod"
"errors"
"fmt"
"math/rand"
"time"
"91porn-server/app/appg"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/models/v/activitymod"
"91porn-server/models/v/prizemod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"github.com/shopspring/decimal"
"github.com/smallnest/weighted"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// 免费模式处理
func freeModelHanlder(in *DrawReq, activityId primitive.ObjectID, prizeList []*prizemod.Prize, prizePool *activitymod.PrizePool) ([]*prizemod.Prize, error) {
var (
wg *weighted.RandW = weighted.NewRandW()
drawPrice int64 // 本次抽奖总支出
prizes []*prizemod.Prize // 本次抽奖结果
reservoir int64 // 蓄水池金额
systemIncome int64 // 系统收益金额
systemRake int64 // 系统抽成金额
now time.Time = time.Now()
)
// 奖品分类
prizeListLen := len(prizeList)
lowPrizes := make([]*prizemod.Prize, 0, prizeListLen) // 低级奖品列表
middlePrizes := make([]*prizemod.Prize, 0, prizeListLen) // 中级奖品列表
for _, v := range prizeList {
if v.Level == prizemod.Low {
lowPrizes = append(lowPrizes, v)
}
if v.Level == prizemod.Middle {
middlePrizes = append(middlePrizes, v)
}
wg.Add(v, int(v.Weights.Mul(decimal.NewFromInt(100)).IntPart()))
}
// 获取用户签到信息
signInfo, err := taskrecordmod.GetByUID(in.UserId)
if err != nil {
return nil, err
}
if signInfo == nil {
if _, err = taskrecordmod.AddTaskRecord(in.UserId); err != nil {
return nil, err
}
}
// 获取用户免费次数
userFree, err := activitymod.GetUserFree(activityId, in.UserId)
if err != nil {
return nil, err
}
// 使用金币抽奖
if userFree != nil && userFree.FreeCount < int32(in.Count) {
drawPrice = prizePool.Price * in.Count
wal, err := walletmod.GetWallet(in.UserId)
if err != nil {
return nil, err
}
// 余额不足
if wal.Amount < drawPrice {
return nil, errors.New("余额不足")
}
}
log.Info(fmt.Sprintf("本次抽奖所需金币为[%d]", drawPrice))
// 本次抽奖总抽成收益
systemRake += decimal.NewFromInt(drawPrice).Mul(decimal.NewFromFloat(0.1)).IntPart()
// 本次抽奖蓄水池抽成收益
reservoir += decimal.NewFromInt(drawPrice).Mul(decimal.NewFromFloat(0.1)).IntPart()
// 本次抽奖系统收益
systemIncome += drawPrice - systemRake - reservoir
drawRecord := make([]activitymod.UserRecord, in.Count) // 抽奖记录
for i := 0; i < int(in.Count); i++ {
p, ok := wg.Next().(*prizemod.Prize)
if !ok || p == nil {
return nil, errors.New("异常错误")
}
log.Info(fmt.Sprintf("金币抽奖-正常获取奖品[%s]", p.Name))
// 玩家使用免费抽奖
if drawPrice == 0 {
// 替换为低级奖品
if p.Level != prizemod.Low {
p = lowPrizes[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(lowPrizes))]
}
}
// 金币抽奖 杀分判定 固定70%几率杀分 亏损达到预期2倍100%杀分
if prizePool.SystemIncome < -(prizePool.EstimatedIncome/2) && drawPrice > 0 && p.Level != prizemod.Low {
if rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(100) > 30 || prizePool.SystemIncome < -(prizePool.EstimatedIncome*2) {
log.Info(fmt.Sprintf("金币抽奖-杀分判定成功[当前奖品:%s]", p.Name))
// 替换为低级奖品
p = lowPrizes[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(lowPrizes))]
log.Info(fmt.Sprintf("金币抽奖-杀分判定成功[替换奖品:%s]", p.Name))
}
}
// 放分判定 固定30%几率放分
if prizePool.SystemIncome > prizePool.EstimatedIncome && rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(100) < 30 && drawPrice > 0 && p.Level != prizemod.Middle {
log.Info(fmt.Sprintf("金币抽奖-放分判定成功[当前奖品:%s]", p.Name))
// 替换为中级奖品
p = middlePrizes[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(middlePrizes))]
log.Info(fmt.Sprintf("金币抽奖-放分判定成功[替换奖品:%s]", p.Name))
}
// 高级奖品放出判定
if p.Level == prizemod.High {
if prizePool.Reservoir < p.GetPrice() {
// 替换为中级奖品
p = middlePrizes[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(middlePrizes))]
} else {
// 扣除蓄水池
prizePool.Reservoir -= p.GetPrice()
reservoir -= p.GetPrice()
}
}
// 增加系统收益 不包含本次抽奖收益
prizePool.SystemIncome -= p.GetPrice()
systemIncome -= p.GetPrice()
prizes = append(prizes, p)
// 增加抽奖记录
drawRecord[i] = activitymod.UserRecord{
ActivityID: activityId,
VIPCardID: p.VIPCardID,
UID: in.UserId,
Name: p.Name,
Type: p.Type,
Price: p.Price,
Count: p.Count,
Value: p.Value,
Level: p.Level,
DrawPrice: prizePool.Price,
CreateTime: now,
}
}
hanlders := make([]prizemod.PrizeHanlder, 0, len(prizes))
var addGoldHanlder prizemod.PrizeHanlder
var transactionLogs []txnmod.TransactionLog // 资金流水记录
for _, v := range prizes {
hanlder, err := prizemod.Run(in.UserId, *v, txnmod.JiuGongGeDraw)
if err != nil {
return nil, err
}
switch v.Type {
case prizemod.Gold:
addGoldHanlder = hanlder
hanlders = append(hanlders, hanlder)
default:
if hanlder != nil {
hanlders = append(hanlders, hanlder)
transactionLogs = append(transactionLogs, hanlder.GetTransactionLog()...)
}
}
}
if err = appg.VideoDB.Trans(func(t *db.MongoTool) error {
if reservoir != 0 || systemIncome != 0 || systemRake != 0 {
// 更新奖池
if err := activitymod.UpdatePrizePoolInc(t, activityId, reservoir, systemIncome, systemRake); err != nil {
return err
}
}
// 更新用户免费次数
if drawPrice == 0 {
if err := activitymod.UpdateOneUserFreeInc(t, activityId, in.UserId, -int32(in.Count)); err != nil {
return err
}
} else if drawPrice > 0 {
wal, err := walletmod.DebitAmount(t, drawPrice, in.UserId)
if err != nil {
return err
}
transactionLogs = append(transactionLogs, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: in.UserId,
Amount: -drawPrice,
ActualAmount: -float64(drawPrice),
TranType: txnmod.JiuGongGeDraw.Key(),
TranTypeInt: int64(txnmod.JiuGongGeDraw),
Desc: fmt.Sprintf("%s消费-%d个金币", "", drawPrice),
RealAmount: wal.RealAmount(),
})
}
// 更新抽奖记录
if len(drawRecord) > 0 {
if err = activitymod.AddMany(drawRecord); err != nil {
return err
}
}
for _, v := range hanlders {
if err = v.Run(t); err != nil {
return err
}
}
if addGoldHanlder != nil {
transactionLogs = append(transactionLogs, addGoldHanlder.GetTransactionLog()...)
}
if len(transactionLogs) > 0 {
if err = txnmod.InsertManyTransactionLog(t, transactionLogs); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
return prizes, nil
}
+47
View File
@@ -0,0 +1,47 @@
package luckydrawser
import "91porn-server/models/v/prizemod"
type BaseInfoReq struct {
ActivityId string `json:"activityId" form:"activityId"` // 活动id
AppId uint64 `json:"appId" form:"appId"` // 活动id
UserId uint64
}
type BaseInfoResp struct {
Balance int64 `json:"balance"` // 用户余额(金币)
FreeCount int32 `json:"freeCount"` // 免费次数
Price int64 `json:"price"` // 单次抽奖价格
Prizes []*prizemod.Prize `json:"prizes"` // 奖品列表
ConsumerAmount int64 `json:"consumerAmount"` // 当日消费金额
ActivityTitle string `json:"activityTitle"` // 活动标题
ActivityDesc string `json:"activityDesc"` // 活动注意事项
ActivityDate string `json:"activityDate"` // 活动时间
}
type DrawReq struct {
ActivityId string `json:"activityId" form:"activityId"` // 活动id
Count int64 `json:"count" form:"count"` // 抽奖次数
UserId uint64
}
type DrawResp []*PrizeInfo
type RecordReq struct {
ActivityId string `json:"activityId" form:"activityId"` // 活动id
}
type RecordResp []*RecordInfo
type MarqueeReq struct {
ActivityId string `json:"activityId" form:"activityId"` // 活动id
}
type MarqueeResp []string
type LuckUserReq struct {
ActivityId uint64 `json:"activityId" form:"activityId"` // 活动id
AppId uint64 `json:"appId" form:"appId"` // 活动id
}
type LuckUserResp []*RecordInfo