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
+414
View File
@@ -0,0 +1,414 @@
package active2023ser
import (
"errors"
"fmt"
"time"
"91porn-server/app/appg"
"91porn-server/common/db"
"91porn-server/models/commod"
"91porn-server/models/v/active2023mod"
"91porn-server/models/v/goldextramod"
"91porn-server/models/v/prdcthsomod"
"91porn-server/models/v/productmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/videocoupon"
"91porn-server/models/v/videodiscountmod"
"91porn-server/models/v/walletmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func Lottery(userInfo *usermod.User, req LotteryReq) error {
wallet, err := walletmod.GetWallet(userInfo.UID)
if err != nil {
return err
}
if wallet == nil {
return errors.New("钱包信息未找到")
}
isLottery := true // 默认为抽奖
desc := "2023新春抽奖"
if req.Gold == 0 && req.Count == 0 { // 不使用免费抽奖次数又不消耗金币, 则为领取保底福利
isLottery = false
desc = "2023新春福利"
}
return appg.VideoDB.Trans(func(t *db.MongoTool) error {
var transactionLogs []txnmod.TransactionLog // 用户所有抽奖花费金, 获奖金币以及游戏币发放记录
if isLottery {
if req.Gold == 0 { // 使用免费抽奖次数
if err = active2023mod.UseLotteryCountFree(t, int64(userInfo.UID), int64(req.Count)); err != nil {
return err
}
} else { // 使用金币抽奖
if err = walletmod.DebitAmountDefault(t, int64(userInfo.UID), int64(req.Gold)); err != nil {
return err
}
wallet.Amount -= int64(req.Gold)
if err = active2023mod.UserLotteryCountPay(t, int64(userInfo.UID), int64(req.Count)); err != nil {
return err
}
transactionType := txnmod.Active2023Cost
transactionLogs = []txnmod.TransactionLog{txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: userInfo.UID,
Amount: -int64(req.Gold),
ActualAmount: -float64(req.Gold),
TranType: transactionType.Key(),
TranTypeInt: int64(transactionType),
Desc: fmt.Sprintf("%s-金币[%d个]", transactionType.Key(), req.Gold),
RealAmount: walletmod.GetRealAmount(wallet),
}}
}
}
var totalGold, totalVipDates uint64 // totalGold: 用户总获奖金币数; totalVipDates: 用户总获奖vip天数
var pzs []active2023mod.Prize // 用户所有奖励, 用以存库
var videoCoupons []videocoupon.UserGoldVideoCoupon // 用户所有获视频折扣券
var goldExtras []goldextramod.GoldExtra // 用户所有获奖金币加购券
var videoDiscounts []productmod.Product // 用户所有获奖视频折扣券
var vipCards []productmod.Product // 用户所有获奖vip卡
now := time.Now()
pzs = make([]active2023mod.Prize, len(req.Prizes))
for i, pz := range req.Prizes {
pzs[i] = active2023mod.Prize{
UID: int64(userInfo.UID),
Type: int64(pz.Type),
Name: pz.Name,
Unit: int64(pz.Price),
CreatedAt: now,
UpdatedAt: now,
}
switch pz.Type {
case active2023mod.PrizeTypeGold: // 金币
if pz.Price <= 0 {
return errors.New("金币数量设置错误")
}
totalGold += pz.Price
transactionType := txnmod.Active2023Reward
wallet.Amount += int64(pz.Price)
transactionLogs = append(transactionLogs, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: userInfo.UID,
Amount: int64(pz.Price),
ActualAmount: float64(pz.Price),
TranType: transactionType.Key(),
TranTypeInt: int64(transactionType),
Desc: fmt.Sprintf("%s-金币[%d个]", transactionType.Key(), pz.Price),
RealAmount: walletmod.GetRealAmount(wallet),
})
case active2023mod.PrizeTypeGameGold: // 游戏币
return errors.New("瓦力游戏已下架")
case active2023mod.PrizeTypeProduct: // 现有产品
if pz.Param1 == "" {
return errors.New("产品id为空")
}
productID, err := primitive.ObjectIDFromHex(pz.Param1)
if err != nil {
return errors.New("无效的产品id")
}
p, err := productmod.FindProduct(productID, "")
if err != nil {
return err
}
if p == nil {
return errors.New("产品不存在")
}
pzs[i].ProductID = &productID
switch p.ProductType {
case commod.VideoDiscount: // 视频折扣卡
videoDiscounts = append(videoDiscounts, *p)
case commod.VIP: // 会员卡
vipCards = append(vipCards, *p)
default:
return errors.New("不支持的产品类型")
}
case active2023mod.PrizeTypeVIPDate: // 会员日期
if pz.Price <= 0 {
return errors.New("vip天数设置错误")
}
transactionType := txnmod.Active2023Reward
transactionLogs = append(transactionLogs, txnmod.TransactionLog{
UID: userInfo.UID,
Amount: 0,
ActualAmount: 0,
TranType: transactionType.Key(),
TranTypeInt: int64(transactionType),
TransNo: primitive.NilObjectID,
Desc: fmt.Sprintf("%s获得VIP天数%d天", desc, pz.Price),
DiscDoc: userInfo.DiscDoc,
SysType: userInfo.SysType,
RealAmount: walletmod.GetRealAmount(wallet),
})
totalVipDates += pz.Price
case active2023mod.PrizeTypeGoldCoinBonus: // 金币加赠券
if pz.Price <= 0 {
return errors.New("金币加赠券面值设置错误")
}
expired := now.Add(time.Hour * 24 * 7) // 默认一周过期
if pz.ValidDate > 0 {
expired = now.Add(time.Hour * 24 * time.Duration(pz.ValidDate))
}
goldExtras = append(goldExtras, goldextramod.GoldExtra{
Uid: userInfo.UID,
Name: "金币加赠券",
Amount: pz.Price,
Expired: expired,
Used: false,
Desc: "充值加送金币",
Source: desc,
CreatedAt: now,
UpdatedAt: now,
})
case active2023mod.PrizeTypeGoldWatch: // 金币观影券
if pz.Price <= 0 {
return errors.New("会员观影券面值设置错误")
}
videoCoupons = append(videoCoupons, videocoupon.UserGoldVideoCoupon{
UID: userInfo.UID,
Num: int(pz.Price),
Source: videocoupon.GoldVideoCouponSource(desc),
Used: false,
CreatedAt: now,
UpdatedAt: now,
})
case active2023mod.PrizeTypeInKind: // 实物奖励
default:
return fmt.Errorf("不支持的奖品类型: %d", pz.Type)
}
}
needRefreshUserCache := false
if totalGold > 0 {
if wallet, err = walletmod.CreditAmount(t, int64(totalGold), userInfo.UID); err != nil {
return err
}
}
if totalVipDates > 0 {
vipLevel := 1 // 默认vip等级为1
start := time.Now()
discount := userInfo.PayVidDiscount
if userInfo.VipExpireDate.After(start) { // 如果用户VIP未过期, 则VIP等级和视频折扣等级需要参考用户现在的情况
start = userInfo.VipExpireDate
if userInfo.VipLevel > vipLevel {
vipLevel = userInfo.VipLevel
}
}
vipExpireDate := start.Add(time.Hour * 24 * time.Duration(totalVipDates))
sel := usermod.UserSelector{VipExpireDate: &vipExpireDate, VipLevel: &vipLevel, PayVidDiscount: &discount}
if err := usermod.UpdateVIP(t, userInfo.UID, userInfo.VipExpireDate, sel); err != nil {
return err
}
userInfo.VipExpireDate = vipExpireDate
userInfo.VipLevel = vipLevel
needRefreshUserCache = true
}
if len(videoCoupons) > 0 {
if err = videocoupon.InsertManyTrans(t, videoCoupons); err != nil {
return err
}
}
if len(transactionLogs) > 0 {
if err = txnmod.InsertManyTransactionLog(t, transactionLogs); err != nil {
return err
}
}
if len(goldExtras) > 0 {
if err = goldextramod.InsertGoldExtras(t, &goldExtras); err != nil {
return err
}
}
if len(videoDiscounts) > 0 {
if err = dealVideoDiscardCards(t, userInfo, videoDiscounts, desc); err != nil {
return err
}
needRefreshUserCache = true
}
if len(vipCards) > 0 {
if err = dealVipCards(t, userInfo, vipCards, desc); err != nil {
return err
}
needRefreshUserCache = true
}
ids, err := active2023mod.InsertUserPrizes(t, &pzs)
if err != nil {
return err
}
if err := active2023mod.InsertUserLottery(t, &active2023mod.UserLottery{
UID: int64(userInfo.UID),
Count: int64(req.Count),
UseGold: int64(req.Gold),
Prizes: ids,
Desc: desc,
CreatedAt: now,
UpdatedAt: now,
}); err != nil {
return err
}
if needRefreshUserCache {
go func() { _, _ = usermod.RefreshCacheAndGetUser(userInfo.UID) }()
}
return nil
})
}
func dealVideoDiscardCards(t *db.MongoTool, userInfo *usermod.User, products []productmod.Product, desc string) error {
productIDsLen := len(products)
if productIDsLen == 0 {
return nil
}
videoDiscountLog, err := videodiscountmod.GetByUID(userInfo.UID)
if err != nil {
return err
}
// 最终过期时间
var videoDiscountTotal int // 最终折扣
var totalDates int // 总增加天数
// 所有视频折扣卡都加入日志
// 计算出最后用户的折扣和过期时间, 只更新一次用户配置
if videoDiscountLog != (videodiscountmod.VideoDiscountLog{}) { // 当存在用户配置时, 需要跟用户配置做对比
if videoDiscountLog.VideoDiscount > 0 && videoDiscountLog.VideoDiscount < 10 { // 如果用户有有效现有折扣
videoDiscountTotal = videoDiscountLog.VideoDiscount // 默认折扣取用户折扣
}
}
productHistorys := make([]prdcthsomod.ProductHistory, productIDsLen)
transactionLogs := make([]txnmod.TransactionLog, productIDsLen)
for i, p := range products {
// 查找视频折扣卡详情
if p.Duration <= 0 || p.VideoDiscount >= 10 || p.VideoDiscount <= 0 {
return errors.New("视频折扣卡配置不正确")
}
// 添加记录
historyID := primitive.NewObjectID()
productHistorys[i] = prdcthsomod.ProductHistory{
ID: historyID,
UID: userInfo.UID,
ProductID: p.ID,
Name: p.Name,
ProductType: commod.VideoDiscount,
DiscDoc: userInfo.DiscDoc,
SysType: userInfo.SysType,
ProductSnapShot: &p,
}
transactionLogs[i] = txnmod.TransactionLog{
UID: userInfo.UID,
Amount: -p.DiscountedPrice,
ActualAmount: 0,
TranType: txnmod.VideoDiscount.Key(),
TranTypeInt: int64(txnmod.VideoDiscount),
TransNo: historyID,
Desc: desc + "获得-" + p.Name,
DiscDoc: userInfo.DiscDoc,
SysType: userInfo.SysType,
}
if p.Duration <= 0 {
p.Duration = 7 // 如果没有设置有效过期时间, 则默认为7天
}
totalDates += p.Duration
videoDiscount := p.VideoDiscount // 默认折扣: 产品折扣
// 最终取折扣值比较小的(折扣值越小说明享受的折扣越大)
if videoDiscountTotal == 0 {
videoDiscountTotal = videoDiscount
} else if videoDiscountTotal > videoDiscount {
videoDiscountTotal = videoDiscount
}
}
// 写入历史纪录
if err = prdcthsomod.InsertManyProductHistory(t, productHistorys); err != nil {
return err
}
if err = txnmod.InsertManyTransactionLog(t, transactionLogs); err != nil {
return err
}
now := time.Now()
start := now
if videoDiscountLog != (videodiscountmod.VideoDiscountLog{}) { // 当存在用户配置时, 需要跟用户配置做对比
if videoDiscountLog.Expiration.After(now) { // 如果用户现有折扣未过期, 则过期时间从用户现有折扣过期时间算起去加折扣卡有效期
start = videoDiscountLog.Expiration
}
}
expireAt := start.Add(time.Hour * 24 * time.Duration(totalDates))
// 修改用户折扣
set := videodiscountmod.EditSelector{
UID: &userInfo.UID,
Expiration: &expireAt,
VideoDiscount: &videoDiscountTotal,
}
return videodiscountmod.Upsert(t, &set)
}
// 抽奖中的vip卡只计算等级, 天数和视频折扣三种权益
// 不计算vip卡配置中附赠的金币和游戏币
func dealVipCards(t *db.MongoTool, userInfo *usermod.User, products []productmod.Product, desc string) error {
productIDsLen := len(products)
if productIDsLen == 0 {
return nil
}
productHistorys := make([]prdcthsomod.ProductHistory, productIDsLen)
transactionLogs := make([]txnmod.TransactionLog, productIDsLen)
totalDuration := 0 // 所有VIP卡增加的天数总和
lowestDiscount := 10 // 所有VIP卡最大打折, 10表示原价不打折
hightestVipLevel := 1 // 最高VIP等级
for i, p := range products {
if p.PayVidDiscount <= 0 || p.PayVidDiscount > 10 {
return errors.New("VIP卡配置不正确")
}
if lowestDiscount > p.PayVidDiscount {
lowestDiscount = p.PayVidDiscount
}
if hightestVipLevel < p.VipLevel {
hightestVipLevel = p.VipLevel
}
totalDuration += p.Duration
// 添加记录
historyID := primitive.NewObjectID()
productHistorys[i] = prdcthsomod.ProductHistory{
ID: historyID,
UID: userInfo.UID,
ProductID: p.ID,
Name: p.Name,
ProductType: commod.VIP,
DiscDoc: userInfo.DiscDoc,
SysType: userInfo.SysType,
ProductSnapShot: &p,
}
transactionLogs[i] = txnmod.TransactionLog{
UID: userInfo.UID,
Amount: -p.DiscountedPrice,
ActualAmount: 0,
TranType: txnmod.Active2023VIP.Key(),
TranTypeInt: int64(txnmod.Active2023VIP),
TransNo: historyID,
Desc: desc + "获得-" + p.Name,
DiscDoc: userInfo.DiscDoc,
SysType: userInfo.SysType,
}
}
// 写入历史纪录
if err := prdcthsomod.InsertManyProductHistory(t, productHistorys); err != nil {
return err
}
if err := txnmod.InsertManyTransactionLog(t, transactionLogs); err != nil {
return err
}
start := time.Now()
if userInfo.VipExpireDate.After(start) { // 如果用户VIP未过期, 则VIP等级和视频折扣等级需要参考用户现在的情况
start = userInfo.VipExpireDate
}
vipExpireDate := start.Add(time.Hour * 24 * time.Duration(totalDuration))
if userInfo.VipLevel > hightestVipLevel {
hightestVipLevel = userInfo.VipLevel
}
if (userInfo.PayVidDiscount > 0 && userInfo.PayVidDiscount < 10) && userInfo.PayVidDiscount < lowestDiscount {
lowestDiscount = userInfo.PayVidDiscount
}
sel := usermod.UserSelector{VipExpireDate: &vipExpireDate, VipLevel: &hightestVipLevel, PayVidDiscount: &lowestDiscount}
if err := usermod.UpdateVIP(t, userInfo.UID, userInfo.VipExpireDate, sel); err != nil {
return err
}
userInfo.VipExpireDate = vipExpireDate
userInfo.VipLevel = hightestVipLevel
userInfo.PayVidDiscount = lowestDiscount
return nil
}
+15
View File
@@ -0,0 +1,15 @@
package active2023ser
type LotteryReq struct {
Count uint64 `form:"count" json:"count"` // 抽奖次数
Gold uint64 `form:"gold" json:"gold"` // 抽奖花费金币. 如果使用的是免费次数, 该值为0
Prizes []Prize `form:"prizes" json:"prizes" binding:"required,min=1,max=10"` // 奖品情况
}
type Prize struct {
Param1 string `json:"param1"` // 附加字段. 目前仅用于视频折扣卡, 表示视频折扣卡id
Type uint64 `form:"prizeType" json:"prizeType" binding:"required"` // 奖品类型
Name string `form:"name" json:"name"` // 奖品名称. 非实物奖品可以不传
Price uint64 `form:"price" json:"price"` // 奖品面值, 配合奖品类型代表具体奖品. 金币: 金币数量; 金币加购券: 加购券金币面值; 观影券: 观影券抵扣面值; 会员礼包: 会员延续天数
ValidDate uint64 `form:"validDate" json:"validDate"` // 有效期(单位: 天)
}