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
+378
View File
@@ -0,0 +1,378 @@
package activityser
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"91porn-server/app/appg"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/models/v/backpackmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/videocoupon"
"91porn-server/models/v/walletmod"
)
// RewardType 奖励类型
type RewardType int
const (
RewardGold RewardType = 1 // 金币
RewardVIP RewardType = 2 // 会员卡(VIP天数)
RewardGoldBonusCoupon RewardType = 3 // 金币加赠券
RewardGoldVideoCoupon RewardType = 4 // 金币观影券
RewardAiChangeFaceFree RewardType = 5 // AI视频换脸免费次数
RewardAiUndressFree RewardType = 6 // AI脱衣免费次数
RewardIntegral RewardType = 7 // 积分
RewardPhysical RewardType = 8 // 实物奖品
)
// AddressInfo 收货地址信息
type AddressInfo struct {
Name string `json:"name"`
Phone string `json:"phone"`
Address string `json:"address"`
}
// RewardReq 发奖请求
type RewardReq struct {
RequestId string `json:"requestId"`
UserId string `json:"userId" binding:"required"`
RewardType RewardType `json:"rewardType" binding:"required"`
Amount int64 `json:"amount" binding:"required"`
CouponValue int64 `json:"couponValue"`
Duration int `json:"duration"`
ActivityId string `json:"activityId"`
RewardName string `json:"rewardName"`
Remark string `json:"remark"`
Address *AddressInfo `json:"address"`
}
// RewardItem 批量发奖中的单个奖品
type RewardItem struct {
RequestId string `json:"requestId"`
RewardType RewardType `json:"rewardType"`
Amount int64 `json:"amount"`
CouponValue int64 `json:"couponValue"`
Duration int `json:"duration"`
RewardName string `json:"rewardName"`
Address *AddressInfo `json:"address"`
}
// BatchRewardReq 批量发奖请求
type BatchRewardReq struct {
UserId string `json:"userId" binding:"required"`
ActivityId string `json:"activityId"`
Remark string `json:"remark"`
Rewards []RewardItem `json:"rewards" binding:"required"`
}
// GrantBatchReward 批量发放活动奖励
func GrantBatchReward(ctx context.Context, req *BatchRewardReq) error {
if len(req.Rewards) == 0 {
return nil
}
uid, err := strconv.ParseUint(req.UserId, 10, 64)
if err != nil {
return fmt.Errorf("invalid userId: %s", req.UserId)
}
u, err := usermod.FindUserByUID(uid)
if err != nil {
return fmt.Errorf("查询用户异常: %w", err)
}
if u == nil {
return fmt.Errorf("用户不存在: %s", req.UserId)
}
return appg.VideoDB.Trans(func(t *db.MongoTool) error {
for _, item := range req.Rewards {
single := &RewardReq{
RequestId: item.RequestId,
UserId: req.UserId,
RewardType: item.RewardType,
Amount: item.Amount,
CouponValue: item.CouponValue,
Duration: item.Duration,
ActivityId: req.ActivityId,
RewardName: item.RewardName,
Remark: req.Remark,
Address: item.Address,
}
if err := grantRewardTx(ctx, t, uid, u, single); err != nil {
log.ErrorX(ctx, "批量发奖-单项失败已跳过",
log.Any("uid", req.UserId),
log.Any("requestId", item.RequestId),
log.Any("rewardType", item.RewardType),
log.E(err))
}
}
return nil
})
}
// GrantReward 发放活动奖励(单个)
func GrantReward(ctx context.Context, req *RewardReq) error {
uid, err := strconv.ParseUint(req.UserId, 10, 64)
if err != nil {
return fmt.Errorf("invalid userId: %s", req.UserId)
}
u, err := usermod.FindUserByUID(uid)
if err != nil {
return fmt.Errorf("查询用户异常: %w", err)
}
if u == nil {
return fmt.Errorf("用户不存在: %s", req.UserId)
}
return appg.VideoDB.Trans(func(t *db.MongoTool) error {
return grantRewardTx(ctx, t, uid, u, req)
})
}
func grantRewardTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq) error {
desc := req.Remark
switch req.RewardType {
case RewardGold:
return grantGoldTx(ctx, t, uid, u, req, desc)
case RewardVIP:
return grantVIPTx(ctx, t, uid, u, req, desc)
case RewardGoldBonusCoupon:
return grantGoldBonusCouponTx(ctx, t, uid, u, req, desc)
case RewardGoldVideoCoupon:
return grantGoldVideoCouponTx(ctx, t, uid, u, req, desc)
case RewardAiChangeFaceFree:
return grantAiChangeFaceFreeTx(ctx, t, uid, u, req, desc)
case RewardAiUndressFree:
return grantAiUndressFreeTx(ctx, t, uid, u, req, desc)
case RewardIntegral:
return grantIntegralTx(ctx, t, uid, u, req, desc)
case RewardPhysical:
return grantPhysicalTx(ctx, t, uid, u, req, desc)
default:
return fmt.Errorf("不支持的奖励类型: %d", req.RewardType)
}
}
func grantGoldTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
amount := req.Amount
creditPlan := walletmod.CreditPlan{Amount: &amount}
w, err := walletmod.Credit(t, creditPlan, uid)
if err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放金币", log.Any("uid", uid), log.Any("amount", amount), log.Any("activityId", req.ActivityId))
return txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: amount,
ActualAmount: float64(amount),
TranType: txnmod.ActivityRewardGold.Key(),
TranTypeInt: int64(txnmod.ActivityRewardGold),
Desc: fmt.Sprintf("%s-发放%d金币", desc, amount),
SysType: u.SysType,
RealAmount: w.RealAmount(),
UniqueOrder: req.RequestId,
})
}
func grantVIPTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
days := int(req.Amount)
if days <= 0 {
return errors.New("VIP天数必须大于0")
}
var expire time.Time
if u.VipExpireDate.After(time.Now()) {
expire = u.VipExpireDate.AddDate(0, 0, days)
} else {
expire = time.Now().AddDate(0, 0, days)
}
vipLevel := 1
if u.VipLevel > vipLevel {
vipLevel = u.VipLevel
}
sel := usermod.UserSelector{
VipExpireDate: &expire,
VipLevel: &vipLevel,
}
if err := usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放VIP", log.Any("uid", uid), log.Any("days", days), log.Any("activityId", req.ActivityId))
return txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
TranType: txnmod.ActivityRewardVIP.Key(),
TranTypeInt: int64(txnmod.ActivityRewardVIP),
Desc: fmt.Sprintf("%s-发放VIP%d天", desc, days),
SysType: u.SysType,
UniqueOrder: req.RequestId,
})
}
func grantGoldBonusCouponTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
if req.CouponValue <= 0 {
return errors.New("券额度(couponValue)必须大于0")
}
if err := txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: req.Amount,
TranType: txnmod.ActivityRewardGoldBonusCoupon.Key(),
TranTypeInt: int64(txnmod.ActivityRewardGoldBonusCoupon),
Desc: fmt.Sprintf("%s-发放金币加赠券%d张", desc, req.Amount),
SysType: u.SysType,
UniqueOrder: req.RequestId,
}); err != nil {
return err
}
now := time.Now()
expiredAt := now.AddDate(0, 0, 30)
for i := int64(0); i < req.Amount; i++ {
item := backpackmod.Backpack{
UID: uid,
GoodsType: backpackmod.GoldBonusCoupon,
GoodsValue: req.CouponValue,
GoodsOrigin: desc,
Status: backpackmod.Unused,
ExpiredTime: expiredAt,
CreateTime: now,
}
if err := backpackmod.AddGoods(t, uid, item); err != nil {
return err
}
}
log.InfoX(ctx, "活动发放-发放金币加赠券", log.Any("uid", uid), log.Any("couponValue", req.CouponValue), log.Any("amount", req.Amount))
return nil
}
func grantGoldVideoCouponTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
if req.CouponValue <= 0 {
return errors.New("券额度(couponValue)必须大于0")
}
if err := txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: req.Amount,
TranType: txnmod.ActivityRewardGoldVideoCoupon.Key(),
TranTypeInt: int64(txnmod.ActivityRewardGoldVideoCoupon),
Desc: fmt.Sprintf("%s-发放金币观影券%d张", desc, req.Amount),
SysType: u.SysType,
UniqueOrder: req.RequestId,
}); err != nil {
return err
}
coupons := make([]videocoupon.UserGoldVideoCoupon, 0, req.Amount)
for i := int64(0); i < req.Amount; i++ {
coupons = append(coupons, videocoupon.UserGoldVideoCoupon{
UID: uid,
Num: int(req.CouponValue),
Used: false,
Source: videocoupon.GoldVideoCouponSource(desc),
})
}
if err := videocoupon.InsertManyTrans(t, coupons); err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放金币观影券", log.Any("uid", uid), log.Any("couponValue", req.CouponValue), log.Any("amount", req.Amount))
return nil
}
func grantAiChangeFaceFreeTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
if err := txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: req.Amount,
TranType: txnmod.ActivityRewardAiChangeFaceFree.Key(),
TranTypeInt: int64(txnmod.ActivityRewardAiChangeFaceFree),
Desc: fmt.Sprintf("%s-发放AI换脸免费%d次", desc, req.Amount),
SysType: u.SysType,
UniqueOrder: req.RequestId,
}); err != nil {
return err
}
now := time.Now()
permanent := time.Date(2099, 12, 31, 23, 59, 59, 0, time.Local)
item := backpackmod.Backpack{
UID: uid,
GoodsType: backpackmod.AiChangeFaceDiscount,
GoodsValue: req.Amount,
GoodsOrigin: desc,
Status: backpackmod.Unused,
ExpiredTime: permanent,
CreateTime: now,
}
if err := backpackmod.AddGoods(t, uid, item); err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放AI换脸免费次数", log.Any("uid", uid), log.Any("times", req.Amount))
return nil
}
func grantIntegralTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
integral := req.Amount
_, err := walletmod.CreditIntegral(t, integral, uid)
if err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放积分", log.Any("uid", uid), log.Any("integral", integral), log.Any("activityId", req.ActivityId))
return txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: integral,
TranType: txnmod.ActivityRewardIntegral.Key(),
TranTypeInt: int64(txnmod.ActivityRewardIntegral),
Desc: fmt.Sprintf("%s-发放%d积分", desc, integral),
SysType: u.SysType,
UniqueOrder: req.RequestId,
})
}
func grantAiUndressFreeTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
times := req.Amount
creditPlan := walletmod.CreditPlan{AiUndressFreeTimes: &times}
_, err := walletmod.Credit(t, creditPlan, uid)
if err != nil {
return err
}
log.InfoX(ctx, "活动发放-发放AI脱衣免费次数", log.Any("uid", uid), log.Any("times", times), log.Any("activityId", req.ActivityId))
return txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: times,
TranType: txnmod.ActivityRewardAiUndressFree.Key(),
TranTypeInt: int64(txnmod.ActivityRewardAiUndressFree),
Desc: fmt.Sprintf("%s-发放AI脱衣免费%d次", desc, times),
SysType: u.SysType,
UniqueOrder: req.RequestId,
})
}
func grantPhysicalTx(ctx context.Context, t *db.MongoTool, uid uint64, u *usermod.User, req *RewardReq, desc string) error {
addrDesc := ""
if req.Address != nil {
addrDesc = fmt.Sprintf("【 收件人:%s 手机:%s 地址:%s】", req.Address.Name, req.Address.Phone, req.Address.Address)
}
rewardName := req.RewardName
if rewardName == "" {
rewardName = "实物奖品"
}
log.InfoX(ctx, "活动发放-发放实物奖品", log.Any("uid", uid), log.Any("rewardName", rewardName), log.Any("activityId", req.ActivityId), log.Any("address", addrDesc))
return txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
UID: uid,
Amount: req.Amount,
TranType: txnmod.ActivityRewardPhysical.Key(),
TranTypeInt: int64(txnmod.ActivityRewardPhysical),
Desc: fmt.Sprintf("%s-发放%s%s", desc, rewardName, addrDesc),
SysType: u.SysType,
UniqueOrder: req.RequestId,
})
}