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: ×} _, 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, }) }