You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

743 lines
17 KiB
Go

package players
import (
"context"
"joybuywork/internal/app"
"joybuywork/internal/consts"
"joybuywork/internal/consts/ckey"
"joybuywork/internal/plugins/jsonm"
"joybuywork/internal/repository/cache"
"joybuywork/internal/repository/dao"
"joybuywork/internal/repository/dbcheck"
"joybuywork/internal/repository/model"
"joybuywork/internal/repository/mtype"
"joybuywork/internal/repository/withdefault"
"joybuywork/internal/service/cmm"
"joybuywork/internal/service/provider/award"
"joybuywork/internal/service/provider/stocks"
"joybuywork/internal/utils"
"joybuywork/internal/utils/lockdo"
"joybuywork/proto/go_proto/workerr"
"math/rand"
"time"
"github.com/golang-module/carbon/v2"
"gorm.io/datatypes"
"gorm.io/gen"
"gorm.io/gen/field"
)
const (
cashFailTypeEnergyLack = iota + 1 // 能量不足
cashFailTypeRewardLack // 奖品库存不足
)
// Cash 现金转盘交互逻辑
type Cash struct {
Player
player *model.FissionCash
conf *model.FissionCampaignCash
blindLang string
cashPercentKey string
cashAgainKey string
}
func NewCash(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Cash, err error) {
player = &Cash{}
err = player.Init(ctx, user, consts.IdCash, opts...)
if err != nil {
return
}
err = player.initPlayer()
if err != nil {
return
}
return
}
// EffectInvite 有效邀请追加进度
func (s *Cash) EffectInvite(helperId int64, add float64) (err error) {
player := s.GetPlayer()
// 一定要触发需要邀请进来的才算
if player.LotteryNum < 5 {
return
}
percent, err := s._addPercent(player.ID, add)
if err != nil {
return
}
inviteData, _ := jsonm.MarshalToString(mtype.RecordTypeInvite{
UserId: helperId,
Help: true,
})
// 更新成有效助力
m := s.GetDao().FissionCashRecord
_, err = m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(player.CampaignID),
m.UserID.Eq(player.UserID),
m.CashID.Eq(player.ID),
m.Type.Eq(mtype.FissionCashRecordTypeInvite),
).Where(gen.Cond(
datatypes.JSONQuery(m.Data.ColumnName().String()).Equals(helperId, "user_id"),
)...).UpdateSimple(m.Data.Value(inviteData))
if err != nil {
err = s.BE().MysqlError(err)
return
}
energyData, _ := jsonm.MarshalToString(mtype.RecordTypeEnergy{
TitleMark: "record:share_reward",
DecOrAdd: mtype.FissionCashRecordDataAdd,
Energy: add,
})
err = s._addRecord(withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeEnergy,
Data: energyData,
}))
if err != nil || percent < 1 {
return
}
// 进度条满了
playerM := s.GetDao().FissionCash
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(playerM.Energy.Add(1))
if err != nil {
err = s.BE().MysqlError(err)
return
}
err = app.Redis.Cache.IncrByFloat(s.Ctx, s.cashPercentKey, -1).Err()
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
// InviteOne 邀请一个人
func (s *Cash) InviteOne(helperId int64) (err error) {
player := s.GetPlayer()
// 一定要触发需要邀请进来的才算
if player.LotteryNum < 5 {
return
}
inviteData, _ := jsonm.MarshalToString(mtype.RecordTypeInvite{
UserId: helperId,
Help: false,
})
return s._addRecord(withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeInvite,
Data: inviteData,
}))
}
// Spin 转转盘
func (s *Cash) Spin() (id int64, failType int32, err error) {
cpn, user := s.cpn, s.user
player, conf := s.GetPlayer(), s.GetConf()
if player.Energy < consts.FissionCashFixEnergyUse {
failType = cashFailTypeEnergyLack
return
}
energyData, _ := jsonm.MarshalToString(mtype.RecordTypeEnergy{
TitleMark: "record:spin_reward",
DecOrAdd: mtype.FissionCashRecordDataDec,
Energy: consts.FissionCashFixEnergyUse,
})
var records = []*model.FissionCashRecord{
withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeEnergy,
Data: energyData,
}),
}
defer func() {
if err != nil {
return
}
err = s._addRecord(records...)
}()
id = consts.SpinGetId[int(player.LotteryNum)]
var price float64
for _, v := range consts.FissionCashSpin {
if id == v.Id {
price = v.Cash
}
}
cashData, _ := jsonm.MarshalToString(mtype.RecordTypeCash{
TitleMark: "record:spin_reward",
DecOrAdd: mtype.FissionCashRecordDataAdd,
Cash: price,
})
records = append(records, withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeCash,
Data: cashData,
}))
m := s.GetDao().FissionCash
var upData = []field.AssignExpr{
m.ShowPrice.Add(price),
m.Energy.Sub(consts.FissionCashFixEnergyUse),
m.LotteryNum.Add(1),
}
switch player.LotteryNum + 1 {
case 4:
upData = append(upData, m.NextStep.Value(mtype.FissionCashNextStepTriggerShare))
case 5:
upData = append(upData, m.NextStep.Value(mtype.FissionCashNextStepLastSpin))
case 6:
var stock *stocks.Campaign
stock, err = stocks.NewCampaign(s.Ctx, cpn.ID)
if err != nil {
return
}
if conf.PriceNum > 0 {
if conf.Lack == mtype.Yes {
failType = cashFailTypeRewardLack
return
}
var ok bool
ok, err = stock.DecrPoolAndAdd(cpn.ID, 1)
if err != nil {
return
}
if !ok {
if conf.Lack == mtype.No {
confM := s.GetDao().FissionCampaignCash
_, err = confM.WithContext(s.Ctx).Where(confM.ID.Eq(conf.ID)).UpdateSimple(confM.Lack.Value(mtype.Yes))
if err != nil {
err = s.BE().MysqlError(err)
return
}
_ = cache.NewCampaignCash(s.Ctx).Clear(conf)
}
failType = cashFailTypeRewardLack
return
}
} else {
err = stock.AddUseStock(1)
if err != nil {
return
}
}
upData = append(upData, m.NextStep.Value(mtype.FissionCashNextStepShowReward))
cm := cmm.NewCMM(s.Ctx, s.GetDao())
var rewards []*model.FissionCampaignReward
rewards, err = cm.UserAppReward(s.user)
if err != nil {
return
}
var addRewards = make([]*award.RewardOne, 0, len(rewards))
var ids []int64
for _, reward := range rewards {
addRewards = append(addRewards, &award.RewardOne{Reward: reward})
}
var rewardsRes []*award.RewardRes
rewardsRes, _, err = award.NewAwardReward(s.Ctx, s.GetDao(), cpn.ProjectID, user).Batch(addRewards, true)
if err != nil {
return
}
for _, reward := range rewardsRes {
ids = append(ids, reward.Record.ID)
}
data, _ := jsonm.MarshalToString(mtype.RecordTypeReward{
Ids: ids,
})
records = append(records, withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeReward,
Data: data,
}))
}
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(upData...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// LastSpin 弹窗还差最后一次抽奖获得奖品动画
func (s *Cash) LastSpin() (err error) {
player := s.player
if player.NextStep != mtype.FissionCashNextStepLastSpin {
return
}
m := s.GetDao().FissionCash
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(
m.NextStep.Value(mtype.FissionCashNextStepInviteSpin),
)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// ShowReward 播放展示奖品动画
func (s *Cash) ShowReward() (err error) {
player := s.player
if player.NextStep != mtype.FissionCashNextStepShowReward {
return
}
m := s.GetDao().FissionCash
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(
m.NextStep.Value(mtype.FissionCashNextStepNone),
)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// TriggerShare 播放触发了分享动画
func (s *Cash) TriggerShare() (err error) {
player := s.player
if player.NextStep != mtype.FissionCashNextStepTriggerShare {
return
}
m := s.GetDao().FissionCash
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(
m.ShowPrice.Add(consts.FissionCashFixShare),
m.Energy.Add(consts.FissionCashFixEnergyUse),
m.NextStep.Value(mtype.FissionCashNextStepNone),
)
if err != nil {
err = s.BE().MysqlError(err)
return
}
cashRecordData, _ := jsonm.MarshalToString(mtype.RecordTypeCash{
TitleMark: "record:share_reward",
DecOrAdd: "add",
Cash: consts.FissionCashFixShare,
})
energyRecordData, _ := jsonm.MarshalToString(mtype.RecordTypeEnergy{
TitleMark: "record:share_reward",
DecOrAdd: "add",
Energy: consts.FissionCashFixEnergyUse,
})
err = s._addRecord(withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeCash,
Data: cashRecordData,
}), withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeEnergy,
Data: energyRecordData,
}))
if err != nil {
return
}
return
}
// OpenGift 播放开盒子动画
func (s *Cash) OpenGift() (openBox, leftBox, rightBox int32, err error) {
player, cpn, conf := s.GetPlayer(), s.cpn, s.GetConf()
if player.NextStep != mtype.FissionCashNextStepOpenGift {
return
}
spl := cpn.GetLangMarks()
var price = conf.Price
if player.Lang != spl[0] {
var lang *model.FissionCampaignLang
lang, err = cache.NewCampaignLang(s.Ctx).FindByCampaignId(cpn.ID, player.Lang)
if err != nil {
err = s.BE().CacheError(err)
return
}
if lang == nil {
err = workerr.NotFound("not found cash conf")
return
}
if c := lang.GetCash(); c != nil {
price = c.Price
}
}
openBox, leftBox, rightBox = s._boxVal(price)
m := s.GetDao().FissionCash
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(
m.ShowPrice.Value(float64(openBox)),
m.Energy.Value(consts.FissionCashFixEnergy),
m.NextStep.Value(mtype.FissionCashNextStepNone),
)
if err != nil {
err = s.BE().MysqlError(err)
return
}
cashRecordData, _ := jsonm.MarshalToString(mtype.RecordTypeCash{
TitleMark: "record:start_reward",
DecOrAdd: mtype.FissionCashRecordDataAdd,
Cash: float64(openBox),
})
energyRecordData, _ := jsonm.MarshalToString(mtype.RecordTypeEnergy{
TitleMark: "record:start_reward",
DecOrAdd: mtype.FissionCashRecordDataAdd,
Energy: consts.FissionCashFixEnergy,
})
err = s._addRecord(withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeCash,
Data: cashRecordData,
}), withdefault.FissionCashRecord(&model.FissionCashRecord{
CampaignID: player.CampaignID,
UserID: player.UserID,
CashID: player.ID,
Type: mtype.FissionCashRecordTypeEnergy,
Data: energyRecordData,
}))
if err != nil {
return
}
return
}
// Again 再玩一次
func (s *Cash) Again() (err error) {
if !app.NewRedisLock(s.Ctx, s.cashAgainKey).SoftLock() {
return workerr.OnceLockError
}
hasAgain, err := s.HasAgain()
if err != nil || !hasAgain {
return
}
_, err = s.createPlayer(s.user.LangMark)
return
}
// HasAgain 是否有再玩一次
func (s *Cash) HasAgain() (ok bool, err error) {
player, conf := s.GetPlayer(), s.GetConf()
if conf.LoopNum == 1 || player.LotteryNum < 6 {
return
}
if conf.LoopNum == 0 {
return true, nil
}
rewardNum, err := s._rewardNum()
if err != nil {
return
}
if rewardNum >= int64(conf.LoopNum) {
return
}
return true, nil
}
// RefreshView 刷新预览用户
func (s *Cash) RefreshView() (err error) {
if !s.IsView() {
err = s.BE().VerifyError("非预览用户无法重新体验")
return
}
player := s.GetPlayer()
m := s.GetDao().FissionCashRecord
_, err = m.WithContext(s.Ctx).Where(m.CashID.Eq(player.ID)).Delete()
if err != nil {
err = s.BE().MysqlError(err)
return
}
app.Redis.Cache.Del(s.Ctx, s.cashPercentKey)
playerM := s.GetDao().FissionCash
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).Delete()
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// GetConf 获取玩法配置
func (s *Cash) GetConf() *model.FissionCampaignCash {
if s.conf == nil {
s.conf, _ = cache.NewCampaignCash(s.Ctx).FindByCampaignId(s.user.CampaignID)
}
return s.conf
}
// GetPlayer 获取玩家基础数据
func (s *Cash) GetPlayer() *model.FissionCash {
return s.player
}
// 玩家
func (s *Cash) initPlayer() (err error) {
user, sp := s.user, s.cpn.GetLangMarks()
lang := utils.NotExistGetFirst(user.LangMark, sp)
s.blindLang = lang
player, err := s._dbFirst(lang)
if err != nil {
return
}
defer func() {
if err != nil {
return
}
s.player = player
s.cashPercentKey = ckey.FissionUserCashPercent(player.ID)
s.cashAgainKey = ckey.FissionUserCashAgain(player.UserID)
}()
if player != nil {
// 没有过期,或者已经完成了任务
if player.ExpiredIn > time.Now().Unix() || player.LotteryNum >= int8(len(consts.SpinGetId)) {
return
}
// 过期了
playerM := s.GetDao().FissionCash
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(playerM.IsExpired.Value(mtype.Yes))
if err != nil {
err = s.BE().MysqlError(err)
return
}
}
player, err = s.firstOrCreate(lang)
if err != nil {
return
}
return
}
// 创建玩家基础数据
func (s *Cash) firstOrCreate(lang string) (player *model.FissionCash, err error) {
lc := app.NewFollower(s.Ctx, ckey.FissionUserCreatePlayerOnce(s.user.ID)).Lock()
defer lc.Release()
if lc.Error() != nil {
err = lc.Error()
return
}
player, err = s._dbFirst(lang)
if err != nil {
return
}
if player != nil {
return
}
return s.createPlayer(lang)
}
func (s *Cash) createPlayer(lang string) (player *model.FissionCash, err error) {
conf := s.GetConf()
playerM := dao.FissionCash
player = withdefault.FissionCash(&model.FissionCash{
CampaignID: s.user.CampaignID,
UserID: s.user.ID,
LotteryNum: 0,
NextStep: mtype.FissionCashNextStepOpenGift,
Energy: 0,
ShowPrice: 0,
ExpiredIn: carbon.Now().AddDays(int(conf.ExpiredDay)).Timestamp(),
IsExpired: 0,
Lang: lang,
})
err = playerM.WithContext(s.Ctx).Create(player)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
func (s *Cash) _dbFirst(lang string) (*model.FissionCash, error) {
playerM := dao.FissionCash
player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID), playerM.Lang.Value(lang), playerM.IsExpired.Eq(mtype.No)).Last()
if err = dbcheck.DbError(err); err != nil {
return nil, s.BE().MysqlError(err)
}
return player, nil
}
func (s *Cash) _addRecord(records ...*model.FissionCashRecord) (err error) {
err = s.GetDao().FissionCashRecord.WithContext(s.Ctx).Create(records...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// 追加进度
func (s *Cash) _addPercent(playerId int64, onePercent float64) (percent float64, err error) {
key := s.cashPercentKey
err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) {
if exist {
return
}
m := s.GetDao().FissionCashRecord
efNum, err := m.WithContext(s.Ctx).Where(
m.CashID.Eq(playerId),
m.Type.Eq(mtype.FissionCashRecordTypeInvite),
).Where(gen.Cond(
datatypes.JSONQuery(m.Data.ColumnName().String()).Equals(true, "help"),
)...).Count()
if err != nil {
return
}
// 重新设置进度
p := float64(efNum) * onePercent
for p > 1 {
p -= 1
}
err = app.Redis.Cache.IncrByFloat(s.Ctx, key, p).Err()
if err != nil {
return
}
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
app.Redis.Cache.Expire(s.Ctx, key, consts.TimeDay)
percent, err = app.Redis.Cache.IncrByFloat(s.Ctx, key, onePercent).Result()
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
func (s *Cash) _boxVal(price int32) (openBox, leftBox, rightBox int32) {
openBox = price - consts.FissionCashFixLeft
if price <= 30 {
leftBox = openBox - 1
rightBox = openBox - 2
return
}
if price <= 50 {
leftBox = openBox - (rand.Int31n(5) + 15)
rightBox = openBox - (rand.Int31n(5) + 10)
return
}
leftBox = openBox - (rand.Int31n(10) + 15)
rightBox = openBox - (rand.Int31n(10) + 10)
return
}
func (s *Cash) _rewardNum() (count int64, err error) {
player := s.GetPlayer()
m := s.GetDao().FissionCashRecord
count, err = m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(player.CampaignID),
m.UserID.Eq(player.UserID),
m.Type.Eq(mtype.FissionCashRecordTypeReward),
).Count()
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}