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/provider/cachecurd" "joybuywork/internal/service/provider/fissionaction" stos "joybuywork/internal/service/provider/s2s" "time" "gorm.io/gen/field" ) const ( CoinDeductStatusLack = iota + 1 // 缺乏金币 CoinDeductStatusMax // 达到投放上限 CoinDeductStatusEnd // 当前场次已结束 ) const ( CoinGainStatusNotOpen = iota + 1 // 未开奖 CoinGainStatusLackJoin // 参与次数不足 CoinGainStatusExpired // 领奖超时 CoinGainStatusGot // 已领取 CoinGainStatusFail // 领取失败 ) type CoinStatus struct { GetCoin int64 // 预计获得金币数 UseNum int32 // 投放次数 AllCoin int64 // 总奖池金额 OpenSec int64 // 剩余开奖时间(秒) CanGet int32 // 是否可以领取瓜分了 End int32 // 是否没有下一场了 } // Coin 金币池交互逻辑 type Coin struct { Player player *model.FissionCoin conf *model.FissionCampaignCoin nowScene *model.FissionCoinScene end int32 preSceneOk int8 lackKey string } func NewCoin(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Coin, err error) { player = &Coin{} err = player.Init(ctx, user, consts.IdCoin, opts...) if err != nil { return } err = player.initNowScene() if err != nil { return } err = player.initPlayer() if err != nil { return } return } func (s *Coin) FinishTask(tasks ...*model.FissionCampaignTask) (err error) { player := s.GetPlayer() var logs []*model.FissionCoinLog for _, task := range tasks { if task.AwardMode == mtype.FissionCampaignTaskAwardModeManual || task.PropMark == mtype.FissionCampaignTaskPropMarkChance { continue } var source = mtype.FissionCoinLogSourceFinishTask if task.TaskType == mtype.FissionCampaignTaskTypeCondAll { source = mtype.FissionCoinLogSourceFinishAllTask } log := withdefault.FissionCoinLog(&model.FissionCoinLog{ UserID: player.UserID, CampaignID: player.CampaignID, CoinID: player.ID, SceneID: player.SceneID, Type: mtype.FissionCoinLogTypeReward, Source: source, }) log.SetVal(mtype.CoinLogVal{ TaskId: task.ID, RewardId: task.RewardID, }) logs = append(logs, log) } if len(logs) == 0 { return } return s._addLog(logs...) } func (s *Coin) Deduct() (useNum, status int32, err error) { player := s.GetPlayer() if s.IsView() { useNum = 2 return } nowScene := s.GetNowScene() if nowScene.OpenTime <= time.Now().Unix() { status = CoinDeductStatusEnd return } conf, lackKey := s.GetConf(), s.lackKey if player.UseNum >= conf.UseLimit { status = CoinDeductStatusMax return } // 看看是不是有 if app.Redis.Cache.Exists(s.Ctx, lackKey).Val() != 0 { status = CoinDeductStatusLack return } deductM := s.GetDao().FissionPointDeduct deduct, err := deductM.WithContext(s.Ctx).Where(deductM.UserID.Eq(player.UserID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if deduct == nil { deduct = withdefault.FissionPointsDeduct(&model.FissionPointDeduct{ UserID: player.UserID, }) if err = s.GetDao().FissionPointDeduct.WithContext(s.Ctx).Create(deduct); err != nil { err = s.BE().MysqlError(err) return } } s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID) if err != nil { return } res, err := s2s.CoinDeduct(player, conf.UseCoin, deduct.ID) if err != nil { return } if res.State == stos.PointsChangeLack { app.Redis.Cache.Set(s.Ctx, lackKey, 1, 10*time.Second) status = CoinDeductStatusLack return } m := s.GetDao().FissionCoin var up = []field.AssignExpr{ m.UseCoin.Add(conf.UseCoin), m.UseNum.Add(1), } if player.Status == mtype.FissionCoinStatusWait { up = append(up, m.Status.Value(mtype.FissionCoinStatusJoin)) } _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(up...) if err != nil { err = s.BE().MysqlError(err) return } _, err = s._sceneCoin(player.SceneID, conf.UseCoin, player.UseNum == 0) if err != nil { return } val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{ CoinNum: int64(conf.UseCoin), RelateId: res.RelateId, }) err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{ CampaignID: player.CampaignID, UserID: player.UserID, CoinID: player.ID, SceneID: player.SceneID, Type: mtype.FissionCoinLogTypeUse, Source: mtype.FissionCoinLogSourcePutIn, Val: val, })) if err != nil { return } useNum = player.UseNum + 1 s.player.UseNum += 1 fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseCoin(time.Now().Unix(), player.SceneID, int64(useNum)) _, err = deductM.WithContext(s.Ctx).Delete(deduct) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Coin) Gain() (getCoin int64, status int32, err error) { if s.IsView() { getCoin = 99999 return } nowScene := s.GetNowScene() if nowScene.OpenTime > time.Now().Unix() { status = CoinGainStatusNotOpen return } player, conf := s.GetPlayer(), s.GetConf() if player.Status == mtype.FissionCoinStatusWait { status = CoinGainStatusLackJoin return } if player.Status == mtype.FissionCoinStatusOk { status = CoinGainStatusGot return } if player.Status == mtype.FissionCoinStatusExpired { status = CoinGainStatusExpired return } s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID) if err != nil { return } _, getCoin, err = s._getCoin(player.SceneID, player.UseNum) if err != nil { return } res, err := s2s.CoinGain(player, int32(getCoin)) if err != nil { return } if res.State != stos.PointsChangeSuccess { status = CoinGainStatusFail return } m := s.GetDao().FissionCoin _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.GetCoin.Add(int32(getCoin)), m.Status.Value(mtype.FissionCoinStatusOk), ) if err != nil { err = s.BE().MysqlError(err) return } sceneData, err := s._sceneData(player.SceneID) if err != nil { return } val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{ CoinNum: getCoin, RelateId: res.RelateId, }) err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{ CampaignID: player.CampaignID, UserID: player.UserID, CoinID: player.ID, SceneID: player.SceneID, Type: mtype.FissionCoinLogTypeAdd, Source: mtype.FissionCoinLogSourceGetOk, Val: val, })) if err != nil { return } _, err = sceneData.Incr(cachecurd.CoinSceneGetNum, 1) if err != nil { err = s.BE().CacheError(err) return } _, err = sceneData.Incr(cachecurd.CoinSceneGetTotal, getCoin) if err != nil { err = s.BE().CacheError(err) return } fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).GetCoin(time.Now().Unix(), player.SceneID) return } // GetStatus 获取用户状态 func (s *Coin) GetStatus() (status CoinStatus, err error) { nowScene, player := s.GetNowScene(), s.GetPlayer() userCoin, getCoin, err := s._getCoin(nowScene.ID, player.UseNum) if err != nil { return } status = CoinStatus{ GetCoin: getCoin, UseNum: player.UseNum, AllCoin: int64(nowScene.InitCoin) + userCoin, OpenSec: nowScene.OpenTime - time.Now().Unix(), CanGet: 0, End: s.end, } if status.OpenSec < 0 { status.OpenSec = 0 } // 开奖了 if nowScene.OpenTime <= time.Now().Unix() && nowScene.RewardTime > time.Now().Unix() && player.Status == mtype.FissionCoinStatusJoin { status.CanGet = 1 } return } // GetConf 获取玩法配置 func (s *Coin) GetConf() *model.FissionCampaignCoin { if s.conf == nil { s.conf, _ = cache.NewCampaignCoin(s.Ctx).FindByCampaignId(s.user.CampaignID) } return s.conf } // GetNowScene 当前场次数据 func (s *Coin) GetNowScene() *model.FissionCoinScene { return s.nowScene } // GetNowScene 当前场次数据 func (s *Coin) initNowScene() (err error) { m := s.GetDao().FissionCoinScene nowScene, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(s.cpn.ID), m.OpenTime.Gte(time.Now().Unix())).Order(m.OpenTime).First() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if nowScene == nil { nowScene, _ = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(s.cpn.ID)).Last() s.end = 1 } s.nowScene = nowScene return } // GetPlayer 获取玩家基础数据 func (s *Coin) GetPlayer() *model.FissionCoin { return s.player } // 玩家 func (s *Coin) initPlayer() (err error) { player, err := s._dbFirst() if err != nil { return } defer func() { if err != nil { return } s.player = player s.lackKey = ckey.FissionCoinLack(player.UserID) }() if player != nil { return } player, err = s.firstOrCreate() if err != nil { return } return } // 创建玩家基础数据 func (s *Coin) firstOrCreate() (player *model.FissionCoin, 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() if err != nil { return } if player != nil { return } return s.createPlayer() } func (s *Coin) createPlayer() (player *model.FissionCoin, err error) { nowScene := s.GetNowScene() playerM := dao.FissionCoin player = withdefault.FissionCoin(&model.FissionCoin{ CampaignID: s.user.CampaignID, UserID: s.user.ID, SceneID: nowScene.ID, PreSceneOk: s.preSceneOk, }) err = playerM.WithContext(s.Ctx).Create(player) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Coin) _dbFirst() (player *model.FissionCoin, err error) { nowScene := s.GetNowScene() playerM := dao.FissionCoin player, err = playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID), playerM.SceneID.Eq(nowScene.ID)).Take() if err = dbcheck.DbError(err); err != nil { return nil, s.BE().MysqlError(err) } if nowScene.PreScene == 0 || (player != nil && player.PreSceneOk == mtype.Yes) || s.end == 1 { return } lastPlayer, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID)).Last() if err = dbcheck.DbError(err); err != nil { return nil, s.BE().MysqlError(err) } if lastPlayer == nil { s.preSceneOk = mtype.Yes return } // 不是上一场 if lastPlayer.SceneID != nowScene.PreScene { if lastPlayer.Status == mtype.FissionCoinStatusJoin { var scene *model.FissionCoinScene m := s.GetDao().FissionCoinScene scene, err = m.WithContext(s.Ctx).Where(m.ID.Eq(lastPlayer.SceneID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(lastPlayer.ID)).UpdateSimple(playerM.Status.Value(mtype.FissionCoinStatusExpired)) if err != nil { err = s.BE().MysqlError(err) return } val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{ CoinNum: s._count(scene.UserCoin, scene.JoinNum, lastPlayer.UseNum), }) err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{ CreatedAt: scene.RewardTime + consts.DaySec, CampaignID: lastPlayer.CampaignID, UserID: lastPlayer.UserID, CoinID: lastPlayer.ID, SceneID: lastPlayer.SceneID, Type: mtype.FissionCoinLogTypeAdd, Source: mtype.FissionCoinLogSourceGetExpired, Val: val, })) if err != nil { return } } s.preSceneOk = mtype.Yes return } // 如果这个用户上一场没结算,要拿来结算一下 if lastPlayer.Status == mtype.FissionCoinStatusJoin { m := s.GetDao().FissionCoinScene var preScene *model.FissionCoinScene preScene, err = m.WithContext(s.Ctx).Where(m.ID.Eq(lastPlayer.SceneID)).Take() if err = dbcheck.DbError(err); err != nil { return nil, s.BE().MysqlError(err) } if preScene.RewardTime > time.Now().Unix() { player = lastPlayer s.nowScene = preScene } return } s.preSceneOk = mtype.Yes return player, nil } func (s *Coin) _sceneCoin(sceneId int64, add int32, first bool) (num int64, err error) { sceneData, err := s._sceneData(sceneId) if err != nil { return } if add > 0 { _, _ = sceneData.Incr(cachecurd.CoinSceneJoinNum, 1) if first { _, _ = sceneData.Incr(cachecurd.CoinSceneJoinUserNum, 1) } return sceneData.Incr(cachecurd.CoinSceneUserCoin, int64(add)) } return sceneData.GetInt64(cachecurd.CoinSceneUserCoin) } // 奖池金币总数,用户获得的金币数 func (s *Coin) _getCoin(sceneId int64, useNum int32) (userCoin, getCoin int64, err error) { sceneData, err := s._sceneData(sceneId) if err != nil { return } userCoin, _ = sceneData.GetInt64(cachecurd.CoinSceneUserCoin) joinNum, _ := sceneData.GetInt64(cachecurd.CoinSceneJoinNum) getCoin = s._count(userCoin, joinNum, useNum) return } func (s *Coin) _sceneData(sceneId int64) (sceneData *cachecurd.HMap, err error) { conf, key := s.GetConf(), ckey.FissionCoinSceneData(sceneId) return cachecurd.NewHMap(s.Ctx, key, cachecurd.SetCoinSceneBuild(s.cpn.ID, sceneId), cachecurd.SetHMapExp(time.Duration(conf.OpenDays+1)*consts.TimeDay), ) } func (s *Coin) _addLog(logs ...*model.FissionCoinLog) (err error) { if len(logs) == 0 { return } err = s.GetDao().FissionCoinLog.WithContext(s.Ctx).Create(logs...) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Coin) _count(userCoin, joinNum int64, useNum int32) (getCoin int64) { scene := s.GetNowScene() var oneCoin int64 if joinNum > 0 || scene.InitUse > 0 { oneCoin = (userCoin + int64(scene.InitCoin)) / (joinNum + int64(scene.InitUse)) if oneCoin == 0 { oneCoin = 1 } } return int64(useNum) * oneCoin }