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" "joybuywork/internal/service/provider/award" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/service/provider/projectuser" stos "joybuywork/internal/service/provider/s2s" "joybuywork/internal/service/provider/stocks" "joybuywork/internal/utils" "joybuywork/internal/utils/lockdo" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/merr" "joybuywork/internal/utils/timez" "joybuywork/proto/go_proto/workerr" "fmt" "strings" "time" "gits.branchcn.com/backend/microcode/plugins/lucky" ) type Player struct { logic.Context cpn *model.FissionCampaign user *model.FissionUser tz *provider.CampaignTZ mpInApp *fissionaction.InApp } // Init 初始化一个逻辑类 func (s *Player) Init(ctx context.Context, user *model.FissionUser, fissionId int64, opts ...Option) (err error) { s.Context.Init(ctx, dao.Q, merr.NewBE(ctx)) s.user = user for _, opt := range opts { opt(s) } // 如果外部没设置的话自己设置一下 if s.cpn == nil { s.cpn, err = cache.NewCampaign(s.Ctx).FindById(s.user.CampaignID) if err != nil { err = s.BE().CacheError(err) return } if s.cpn == nil { return workerr.NotFound("not found cpn") } } // 验证一下活动玩法和实际玩法是否一致 if s.cpn.FissionID != fissionId { return workerr.VerifyError(fmt.Sprintf("The current user identity does not belong to the current activity (%s)", consts.FissionIdMap[fissionId])) } return } // IsView 预览用户 func (s *Player) IsView() bool { return s.user.IsView == mtype.Yes } // HelpOk 助力成功用户 func (s *Player) HelpOk() bool { return s.user.ParentID > 0 && s.user.IsHelp == mtype.Yes } // appTrack 应用事件记录 func (s *Player) appTrack() fissionaction.InApp { if s.mpInApp == nil { mpInApp := fissionaction.NewMarkPoint(s.Ctx).InApp(s.user) s.mpInApp = &mpInApp } return *s.mpInApp } // 统一抽奖 func (s *Player) _lottery() (reward *model.FissionCampaignReward, err error) { rewards, err := cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user) if err != nil { return } //如果是预览用户,随机返回一个奖品 if s.IsView() { var dataMap = make(map[int64]*model.FissionCampaignReward) for _, v := range rewards { if v.Odds == 0 { continue } dataMap[v.ID] = v } for _, v := range dataMap { reward = v } return } var ids []int64 for _, v := range rewards { ids = append(ids, v.ID) } stock, err := stocks.NewCampaignReward(s.Ctx, ids...) if err != nil { return } useStockMap, _ := stock.AllUseStock() var thinkId int64 var thinkMap = make(map[int64]*model.FissionCampaignReward) var rewardMap = make(map[int64]*model.FissionCampaignReward) for _, v := range rewards { if v.IsThank == mtype.Yes { thinkMap[v.ID] = v rewardMap[v.ID] = v thinkId = v.ID } else if int64(v.Stock) > useStockMap[v.ID] { rewardMap[v.ID] = v } } //随机一个谢谢参与发放 var think *model.FissionCampaignReward for _, v := range thinkMap { think = v } //获取到了奖品 for { //构建抽奖数据 var prize = make([]lucky.Reward, 0, len(rewardMap)) for _, v := range rewardMap { prize = append(prize, lucky.Reward{ Share: int(v.Odds), Mark: v.ID, }) } // 抽奖 lcy, _ := lucky.NewDiscrete(s.Ctx, prize).Lottery() // 没中奖 if lcy.Int64() == 0 { reward = think } else { reward = rewardMap[lcy.Int64()] } // 中奖了 var check bool check, err = award.NewAwardIssVerify(s.Ctx, s.user, reward).DeductStock().RuleFilter().Unlock().IsOK() if err != nil { return } // 扣除库存成功 if check { return } // 这个奖品这个人不能中了 rewardMap[thinkId].Odds += reward.Odds delete(rewardMap, lcy.Int64()) } } // GetTz 获取用户的时区 func (s *Player) GetTz() *provider.CampaignTZ { if s.tz == nil { s.tz = provider.NewCampaignTZ(s.Ctx, s.cpn, s.user.LangMark) } return s.tz } // 最后签到 func (s *Player) _lastSign(lastSign string) (ok bool, day string) { tz := provider.NewCampaignTZ(s.Ctx, s.cpn, s.user.LangMark) if tz.IsWait() { return false, lastSign } signDate, _, _ := timez.GMTStartAndEndTs(tz.GetTz()) day = strings.ReplaceAll(signDate, "-", "") if lastSign == "" || day > lastSign { s.appTrack().Sign(time.Now().Unix(), tz.GetTz(), day) return true, day } return false, lastSign } // 手动领取任务奖励 func (s *Player) _receiveReward(taskRewards []*model.FissionUserTaskReward, callback func(tasks []*model.FissionCampaignTask, records []*award.RewardRes) error) (err error) { var taskRewardMap = make(map[int64]int64) var ids []int64 defer func() { if err != nil || len(ids) == 0 { return } receiveTime := time.Now().Unix() m := s.GetDao().FissionUserTaskReward _, err = m.WithContext(s.Ctx).Where(m.ID.In(ids...)).UpdateSimple(m.ReceiveTime.Value(receiveTime)) if err != nil { err = s.BE().MysqlError(err) return } for _, task := range taskRewards { s.appTrack().ManualTask(receiveTime, task.TaskID, taskRewardMap[task.TaskID]) } }() var taskMap = make(map[int64]*model.FissionCampaignTask) var taskIds []int64 for _, v := range taskRewards { ids = append(ids, v.ID) if _, ok := taskMap[v.TaskID]; !ok { taskIds = append(taskIds, v.TaskID) taskMap[v.TaskID] = nil } } taskM := s.GetDao().FissionCampaignTask cpnTasks, err := taskM.WithContext(s.Ctx).Where(taskM.ID.In(taskIds...)).Find() if err != nil { err = s.BE().MysqlError(err) return } for _, v := range cpnTasks { taskMap[v.ID] = v } var rewardIds []int64 var tasks []*model.FissionCampaignTask for _, v := range taskRewards { task, ok := taskMap[v.TaskID] if !ok { continue } taskRewardMap[v.TaskID] = task.RewardID tasks = append(tasks, task) switch task.PropMark { case mtype.FissionCampaignRewardPropMarkReward: rewardIds = append(rewardIds, task.RewardID) } } var records []*award.RewardRes if len(rewardIds) > 0 { records, _, err = award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).RewardIdsBatch(rewardIds, true) if err != nil { return } } return callback(tasks, records) } const ( pointsUseOk = iota // 积分使用成功 pointsUseLack // 积分不足 pointsUseClose // 积分使用关闭 pointsUseDayLimit // 积分每日使用次数超限额 pointsUseTotalLimit // 积分使用次数超限额 ) type pointsUseCond struct { Field string Value any } func (s *Player) pointsUse(source, tableName string, other mtype.PointsLogOther, where []*pointsUseCond) (status int32, deduct *model.FissionPointDeduct, err error) { lackKey := ckey.FissionCoinLack(s.user.ID) if app.Redis.Cache.Exists(s.Ctx, lackKey).Val() != 0 { status = pointsUseLack return } conf, err := cache.NewCampaignPointsUse(s.Ctx).FindByCampaignId(s.user.CampaignID) if err != nil { err = s.BE().CacheError(err) return } if conf == nil || conf.UseNum == 0 { status = pointsUseClose return } if conf.DayLimit > 0 { tz := s.GetTz() day, start, end := timez.GMTStartAndEndTs(tz.GetTz()) dayLimitKey := ckey.FissionPointsUseDayNum(s.user.ID, day) var dayNum int64 dayNum, err = lockdo.Int64(s.Ctx, dayLimitKey, func() (num int64, err error) { query := app.DB.Client.Debug().Table(tableName).WithContext(s.Ctx). Where("campaign_id = ? and user_id = ? and created_at between ? and ?", s.user.CampaignID, s.user.ID, start, end) for _, cond := range where { query = query.Where(fmt.Sprintf("%s = ?", cond.Field), cond.Value) } query.Count(&num) return }) if err != nil { err = s.BE().CacheError(err) return } if dayNum >= int64(conf.DayLimit) { status = pointsUseDayLimit return } defer func() { if err == nil && status == pointsUseOk { app.Redis.Cache.Incr(s.Ctx, dayLimitKey) } }() } if conf.TotalLimit > 0 { totalLimitKey := ckey.FissionPointsUseNum(s.user.ID) var totalNum int64 totalNum, err = lockdo.Int64(s.Ctx, totalLimitKey, func() (num int64, err error) { query := app.DB.Client.Debug().Table(tableName).WithContext(s.Ctx). Where("campaign_id = ? and user_id = ?", s.user.CampaignID, s.user.ID) for _, cond := range where { query = query.Where(fmt.Sprintf("%s = ?", cond.Field), cond.Value) } query.Count(&num) return }) if err != nil { err = s.BE().CacheError(err) return } if totalNum >= int64(conf.TotalLimit) { status = pointsUseTotalLimit return } defer func() { if err == nil && status == pointsUseOk { app.Redis.Cache.Incr(s.Ctx, totalLimitKey) } }() } deductM := s.GetDao().FissionPointDeduct deduct, err = deductM.WithContext(s.Ctx).Where(deductM.UserID.Eq(s.user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if deduct == nil { deduct = withdefault.FissionPointsDeduct(&model.FissionPointDeduct{ UserID: s.user.ID, Num: conf.UseNum, }) if err = s.GetDao().FissionPointDeduct.WithContext(s.Ctx).Create(deduct); err != nil { err = s.BE().MysqlError(err) return } } else { deduct.Num = conf.UseNum } s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID) if err != nil { return 0, nil, err } other.DeductId = deduct.ID res, err := s2s.Deduct(conf.UseNum, source, other) if err != nil { return 0, nil, err } if res.State == stos.PointsChangeLack { status = pointsUseLack app.Redis.Cache.Set(s.Ctx, lackKey, 1, 10*time.Second) return } return } func (s *Player) pointsUseOk(deduct *model.FissionPointDeduct) { if deduct == nil { return } deductM := s.GetDao().FissionPointDeduct _, _ = deductM.WithContext(s.Ctx).Delete(deduct) s.appTrack().PointsUse(time.Now().Unix(), deduct.Num) } func (s *Player) GetTCoin() (coinNum int64, err error) { pu, err := projectuser.NewProjectUser(s.Ctx, projectuser.WithInit(s.cpn.ProjectID, s.user.SubUserID)) if err != nil { return } coin, err := pu.GetCoin() if err != nil { return } return coin.Num, nil } func (s *Player) unlockReward(task *model.FissionCampaignTask) (ok bool, err error) { if task.PropMark != mtype.FissionCampaignTaskPropMarkUnlockReward { return } cpnId, userId, rewardId := s.cpn.ID, s.user.ID, task.RewardID key := ckey.FissionUserUnlockReward(userId, rewardId) first := app.Redis.Cache.SetNX(s.Ctx, key, 1, consts.TimeDay).Val() if !first { return false, nil } m := s.GetDao().FissionUnlockReward ulr, err := m.WithContext(s.Ctx).Where( m.CampaignID.Eq(cpnId), m.UserID.Eq(userId), m.RewardID.Eq(rewardId), ).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if ulr != nil { return false, nil } err = m.WithContext(s.Ctx).Create(withdefault.FissionUnlockReward(&model.FissionUnlockReward{ CampaignID: cpnId, UserID: userId, RewardID: rewardId, })) if err != nil { err = s.BE().MysqlError(err) return } s.appTrack().UnlockReward(time.Now().Unix(), rewardId, task.ID) return true, nil } type EventReward struct { PropMark string Chance int32 Reward *award.RewardRes } func (s *Player) eventOk(mark string) (ok bool, rewards []*EventReward, err error) { event, err := cache.NewCampaignEvent(s.Ctx).FindByCampaignId(s.user.CampaignID, mark) if err != nil { err = s.BE().CacheError(err) return } if event == nil { return } switch event.Mark { case mtype.FissionCampaignEventMarkFirstVisit: return s._eventFirstVisit(event) } return } func (s *Player) _eventFirstVisit(event *model.FissionCampaignEvent) (ok bool, rewards []*EventReward, err error) { eventKey := ckey.FissionUserEventFirstVisit(s.user.ID) if !app.Redis.Cache.SetNX(s.Ctx, eventKey, 1, lockdo.Rand2HourExp()).Val() { return } m := s.GetDao().FissionUserEvent fv, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(s.user.ID), m.Mark.Eq(mtype.FissionCampaignEventMarkFirstVisit)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if fv != nil { return } fv = withdefault.FissionUserEvent(&model.FissionUserEvent{ CampaignID: s.user.CampaignID, UserID: s.user.ID, Mark: mtype.FissionCampaignEventMarkFirstVisit, }) if err = m.WithContext(s.Ctx).Create(fv); err != nil { err = s.BE().MysqlError(err) } if event.Open == mtype.No { return } var rewardIds []int64 for _, reward := range event.GetRewards() { switch reward.PropMark { case mtype.FissionCampaignRewardPropMarkChance: rewards = append(rewards, &EventReward{ PropMark: reward.PropMark, Chance: reward.Chance, }) case mtype.FissionCampaignRewardPropMarkReward, mtype.FissionCampaignRewardPropMarkPoint: rewardIds = append(rewardIds, reward.RewardId) } } if len(rewardIds) == 0 { return } res, _, err := award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).RewardIdsBatch(rewardIds, true) if err != nil { return } for _, reward := range res { rewards = append(rewards, &EventReward{ PropMark: reward.Reward.PropMark, Reward: reward, }) } return } // _lotteryTask 抽奖任务通用处理 func (s *Player) _lotteryTask(checkManual bool, task *model.FissionCampaignTask) (next bool, t int8, source string, lotteryVal mtype.LotteryLogVal) { if checkManual && task.AwardMode == mtype.FissionCampaignTaskAwardModeManual { next = true return } lotteryVal.TaskId = task.ID switch task.PropMark { case mtype.FissionCampaignTaskPropMarkChance: lotteryVal.Chance = task.Chance t = mtype.LotteryFissionTypeAdd case mtype.FissionCampaignTaskPropMarkUnlockReward: ok, _ := s.unlockReward(task) if !ok { next = true return } t = mtype.LotteryFissionTypeUnlock lotteryVal.RewardId = task.RewardID } switch task.TaskType { case mtype.FissionCampaignTaskTypeRegister: source = mtype.FissionLogSourceRegister case mtype.FissionCampaignTaskTypeInstall: source = mtype.FissionLogSourceInstall case mtype.FissionCampaignTaskTypeCondAll: source = utils.Ternary[string](task.AwardMode == mtype.FissionCampaignTaskAwardModeManual, mtype.FissionLogSourceManualCondAll, mtype.FissionLogSourceCondAll) case mtype.FissionCampaignTaskTypeCondOne: source = utils.Ternary[string](task.AwardMode == mtype.FissionCampaignTaskAwardModeManual, mtype.FissionLogSourceManualCondOne, mtype.FissionLogSourceCondOne) } return } type TaskTip struct { PropMark string Chance int32 RecordId int64 } func GetTaskTip(ctx context.Context, userId int64) (list []*TaskTip, err error) { taskTipKey := ckey.FissionUserTaskTip(userId) strs, err := app.Redis.Cache.LRange(ctx, taskTipKey, 0, -1).Result() if err != nil { return } list = make([]*TaskTip, 0, len(strs)) for _, str := range strs { taskTip := &TaskTip{} if err = jsonm.StrUnmarshal(str, taskTip); err != nil { continue } list = append(list, taskTip) } return list, nil } func AddTaskTip(ctx context.Context, userId int64, propMark string, chance int32, recordId int64) { taskTipKey := ckey.FissionUserTaskTip(userId) taskTip := &TaskTip{ PropMark: propMark, Chance: chance, RecordId: recordId, } str, _ := jsonm.MarshalToString(taskTip) app.Redis.Cache.RPush(ctx, taskTipKey, str) app.Redis.Cache.Expire(ctx, taskTipKey, consts.TimeWeek) } func DelTaskTip(ctx context.Context, userId int64, l int32) { taskTipKey := ckey.FissionUserTaskTip(userId) for i := 0; i < int(l); i++ { app.Redis.Cache.LPop(ctx, taskTipKey) } }