package validevents import ( "joybuywork/internal/app" "joybuywork/internal/consts/ckey" "joybuywork/internal/plugins/jsonm" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/provider" "joybuywork/internal/service/provider/cachecurd" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/service/queue/types" "joybuywork/internal/utils" "joybuywork/internal/utils/lockdo" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/timez" "joybuywork/internal/utils/verify" "fmt" "math/rand" "strconv" "time" "gorm.io/gen/field" ) // 安装、注册事件是否有效处理 type taskEffectDo func() (err error) // 事件完成任务回调 type taskDoneCallback func(user *model.FissionUser, tasks []*model.FissionCampaignTask) (err error) type eventData struct { props map[string]int32 // 道具对应的发放数量,如: {"chance": 10, "fixed_roll": 1} (如果是大富翁表示,骰子个数获得10个,固定骰子获得1个) targetNum int32 // 当前推送的事件计算完毕后,总共完成了几次自定义事件 } type base struct { *logic.Logic Req *types.ValidEvents effectNum, taskValidNum int64 cpn *model.FissionCampaign tOffset int32 // 时区 reportType int8 // 报表类型 spLang []string // 支持的语种 condFinishData []*condFinishOne condTotalData []*condTotalOne } func (s *base) Event(user *model.FissionUser) (targetNum int32, setHelp bool, err error) { return } func (s *base) Register(helper, beHelper *model.FissionUser) (setHelp bool, err error) { return } func (s *base) Install(helper, beHelper *model.FissionUser) (setHelp bool, err error) { return } func (s *base) Open(helper, beHelper *model.FissionUser) (setHelp bool, err error) { return } func (s *base) Click(userMark string, beHelper *model.FissionUser) (err error) { return } // 条件结果处理 func (s *base) _event(user *model.FissionUser, callbacks ...taskDoneCallback) (data eventData, err error) { e := event{base: s} defer func() { if err == nil { err = e.dataSync() } }() return e.event(user, callbacks...) } // 任务是否有效完成 func (s *base) _effect(user *model.FissionUser, task *model.FissionCampaignTask, ideq ...string) (ok bool, err error) { taskLimit, chance, chanceLimit, targetNum := int64(task.TaskLimit), int64(task.Chance), int64(task.ChanceLimit), int64(task.Num) // 完成的任务 ftNum, err := s._taskFinishNum(user.ID, task.ID) if err != nil { return } // 统计有效邀请人数 var num int64 if task.PropMark == mtype.FissionCampaignTaskPropMarkChance { num, err = s._statDirectNum(user.CampaignID, user.ID) if err != nil { return } } else { num, err = s._statTaskValidNum(user.CampaignID, user.ID, task.ID, ideq...) if err != nil { return } } var indicatorData *mtype.IndicatorData switch s.Req.NoticeType { case types.NoticeTypeInstall: indicatorData = &mtype.IndicatorData{ InstallNum: num, } case types.NoticeTypeRegister: indicatorData = &mtype.IndicatorData{ RegisterNum: num, } default: } // 超过限制不能完成任务了 if (task.TaskLimit != 0 && ftNum >= taskLimit) || (task.ChanceLimit != 0 && (ftNum*chance) >= chanceLimit) { return false, s._taskRecord(user, task, indicatorData, mtype.Yes) } // 有效计算次数 //总邀请人数 减去 当前任务完成次数 * 任务目标次数 efNum := num - (ftNum * targetNum) if efNum < targetNum { return } return true, s._taskRecord(user, task, indicatorData, mtype.No) } // 任务有效就执行任务有效处理 func (s *base) _taskEffectDo(user *model.FissionUser, task *model.FissionCampaignTask, do taskEffectDo, ideq ...string) (err error) { ok, err := s._effect(user, task, ideq...) if err != nil { return } if !ok { return } return do() } // 统计有效邀请人数 func (s *base) _statDirectNum(cpnId, userId int64) (num int64, err error) { // 如果已经统计过了,就直接返回 if s.effectNum > 0 { return s.effectNum, nil } key := ckey.FissionUserInviteStat(userId) _, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) { m := s.GetDao().FissionUser.SubTable(cpnId) return m.WithContext(s.Ctx).Where( m.CampaignID.Eq(cpnId), m.ParentID.Eq(userId), m.IsHelp.Eq(mtype.Yes), ).Count() }) if err != nil { err = s.BE().CacheError(err) return } app.Redis.Cache.Expire(s.Ctx, key, 2*time.Hour) s.effectNum, err = app.Redis.Cache.Incr(s.Ctx, key).Result() if err != nil { err = s.BE().CacheError(err) return } return s.effectNum, nil } // 统计任务有效数 func (s *base) _statTaskValidNum(cpnId, userId, taskId int64, ideq ...string) (num int64, err error) { // 如果已经统计过了,就直接返回 if s.taskValidNum > 0 { return s.taskValidNum, nil } key := ckey.FissionUserTaskValidStat(userId, taskId) _, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) { m := s.GetDao().FissionUserTaskValid return m.WithContext(s.Ctx).Where( m.CampaignID.Eq(cpnId), m.UserID.Eq(userId), m.TaskID.Eq(taskId), ).Count() }) if err != nil { err = s.BE().CacheError(err) return } valid := withdefault.FissionUserTaskValid(&model.FissionUserTaskValid{ CampaignID: cpnId, UserID: userId, TaskID: taskId, }) valid.SetIdeq(ideq...) m := s.GetDao().FissionUserTaskValid has, err := m.WithContext(s.Ctx).Where(m.Ideq.Eq(valid.Ideq)).Count() if err != nil { err = s.BE().MysqlError(err) return } if has > 1 { return } if err = m.WithContext(s.Ctx).Create(valid); err != nil { err = s.BE().MysqlError(err) return } app.Redis.Cache.Expire(s.Ctx, key, 2*time.Hour) s.taskValidNum, err = app.Redis.Cache.Incr(s.Ctx, key).Result() if err != nil { err = s.BE().CacheError(err) return } return s.taskValidNum, nil } // 任务完成数 func (s *base) _taskFinishNum(userId, taskId int64) (num int64, err error) { key := ckey.FissionUserTaskCpnLimit(userId, taskId) num, err = lockdo.Int64(s.Ctx, key, func() (num int64, err error) { m := s.GetDao().FissionUserTaskRecord return m.WithContext(s.Ctx).Where(m.UserID.Eq(userId), m.TaskID.Eq(taskId)).Count() }) if err != nil { err = s.BE().CacheError(err) return } return } // 任务完成次数刷新缓存 func (s *base) _addTaskFinish(user *model.FissionUser, task *model.FissionCampaignTask, chance int64) (err error) { if task.TaskLimit > 0 || task.ChanceLimit > 0 { key := ckey.FissionUserTaskCpnLimit(user.ID, task.ID) err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) { if !exist { var num int64 m := s.GetDao().FissionUserTaskRecord num, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(user.ID), m.TaskID.Eq(task.ID)).Count() if err != nil { return } app.Redis.Cache.IncrBy(s.Ctx, key, num) } app.Redis.Cache.IncrBy(s.Ctx, key, chance) app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute) return }) if err != nil { err = s.BE().CacheError(err) return } } if task.DayLimit > 0 { day, start, end := timez.GMTStartAndEndTs(s.tOffset, s.Req.BehaviorTime) key := ckey.FissionUserTaskDayLimit(day, user.ID, task.ID) err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) { if !exist { var num int64 m := s.GetDao().FissionUserTaskRecord num, err = m.WithContext(s.Ctx).Where( m.UserID.Eq(user.ID), m.TaskID.Eq(task.ID), m.CreatedAt.Gte(start), m.CreatedAt.Lte(end), ).Count() if err != nil { return } app.Redis.Cache.IncrBy(s.Ctx, key, num) } app.Redis.Cache.IncrBy(s.Ctx, key, chance) app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute) return }) if err != nil { err = s.BE().CacheError(err) return } } if task.WeekLimit > 0 { start, end := timez.GetWeekEnd(s.tOffset, int32(task.WeekNum)) key := ckey.FissionUserWeekLimit(start, user.ID, task.ID) err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) { if !exist { var num int64 m := s.GetDao().FissionUserTaskRecord num, err = m.WithContext(s.Ctx).Where( m.UserID.Eq(user.ID), m.TaskID.Eq(task.ID), m.CreatedAt.Between(start, end), ).Count() if err != nil { return } app.Redis.Cache.IncrBy(s.Ctx, key, num) } app.Redis.Cache.IncrBy(s.Ctx, key, chance) app.Redis.Cache.Expire(s.Ctx, key, time.Duration(rand.Intn(60)+60)*time.Minute) return }) if err != nil { err = s.BE().CacheError(err) return } } return } // 一个任务完成了 func (s *base) _taskRecord(user *model.FissionUser, task *model.FissionCampaignTask, data *mtype.IndicatorData, limit int8) (err error) { record := withdefault.FissionUserTaskRecord(&model.FissionUserTaskRecord{ CampaignID: user.CampaignID, UserID: user.ID, TaskID: task.ID, IndicatorData: "", Chance: task.Chance, LimitProp: limit, }) record.SetIndicatorData(data) err = s._addUserTaskRecord(record) if err != nil { return } return s._addTaskFinish(user, task, 1) } // 添加任务完成记录 func (s *base) _addUserTaskRecord(records ...*model.FissionUserTaskRecord) (err error) { if len(records) == 0 { return } m := s.GetDao().FissionUserTaskRecord if err = m.WithContext(s.Ctx).Create(records...); err != nil { err = s.BE().MysqlError(err) return } return } // 完成事件统计推送 func (s *base) _condFinish(user *model.FissionUser, condId int64, total string, first bool) { val, _ := strconv.ParseFloat(total, 64) s.condFinishData = append(s.condFinishData, &condFinishOne{ condId: condId, num: 1, userNum: utils.Ternary[int64](first, 1, 0), }) if s.reportType == mtype.FissionCampaignReportTypeNew { s.MP().InApp(user).FinishCond(s.Req.BehaviorTime, condId, val, s.Req.Event, s.Req.EventData) } } func (s *base) _condTotal(condId int64, total string) { val, _ := strconv.ParseFloat(total, 64) s.condTotalData = append(s.condTotalData, &condTotalOne{ condId: condId, total: val, }) } // 获取安装事件的任务 func (s *base) _getInstallTasks(campaignId int64) (tasks []*model.FissionCampaignTask, setHelp bool, err error) { return s._getTaskAndHelp(campaignId, mtype.FissionCampaignTaskMarkInstall) } // 获取注册事件的任务 func (s *base) _getRegisterTasks(campaignId int64) (tasks []*model.FissionCampaignTask, setHelp bool, err error) { return s._getTaskAndHelp(campaignId, mtype.FissionCampaignTaskMarkRegister) } // 获取任务 func (s *base) _getTaskAndHelp(campaignId int64, mark string) (tasks []*model.FissionCampaignTask, setHelp bool, err error) { m := s.GetDao().FissionCampaignTask tasks, err = m.WithContext(s.Ctx).Where( m.CampaignID.Eq(campaignId), m.TaskMark.Eq(mark), m.IsDel.Eq(mtype.No), ).Find() if err != nil { err = s.BE().MysqlError(err) return } for _, task := range tasks { if task.PropMark == mtype.FissionCampaignTaskPropMarkChance { setHelp = true return } } for _, task := range tasks { if verify.InArray[string](task.PropMark, mtype.FissionCampaignTaskPropMarkFixedRoll, mtype.FissionCampaignTaskPropMarkRevive, mtype.FissionCampaignTaskPropMarkReward, ) { setHelp = true return } } return } // 同步数据 func (s *base) dataSync() (err error) { if len(s.condFinishData) > 0 { var data = make(map[int64]*condFinishOne) for _, v := range s.condFinishData { if _, ok := data[v.condId]; !ok { data[v.condId] = v } else { data[v.condId].num += v.num } } m := s.GetDao().FissionCampaignCond for _, v := range data { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(v.condId)).UpdateSimple(m.Num.Add(v.num), m.UserNum.Add(v.userNum)) if err != nil { err = s.BE().MysqlError(err) return } } } if len(s.condTotalData) > 0 { var data = make(map[int64]float64) for _, v := range s.condTotalData { data[v.condId] += v.total } m := s.GetDao().FissionCampaignCond for id, total := range data { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(id)).UpdateSimple(m.Total.Add(total)) if err != nil { err = s.BE().MysqlError(err) return } } } return } // 增加抽奖次数通知 func (s *base) addChanceNotify(userId, chance int64) { provider.NewFissionClue(s.Ctx, userId).AddChance(chance) } // 有邀请类的子活动 func (s *base) hasChildInvite(user *model.FissionUser, invitedId int64, done func() error) (err error) { child, err := cache.NewCampaignChild(s.Ctx).FindByUniqIdx(user.CampaignID, mtype.FissionCampaignChildTypeInvite) if err != nil { err = s.BE().CacheError(err) return } if child == nil { return } cpn, err := cache.NewCampaign(s.Ctx).FindById(child.ChildID) if err != nil { err = s.BE().CacheError(err) return } if cpn == nil || cpn.Status == mtype.FissionCampaignStatusCancel || cpn.IsDel == mtype.Yes || !timez.GMTInsideEndless(s.tOffset, cpn.TStart, cpn.TEnd) { return } player, err := cache.NewFissionChildUser(s.Ctx).FindByUniqIdx(user.ID, cpn.ID, cache.WithNilSet(func() ([]byte, error) { info := withdefault.FissionChildUser(&model.FissionChildUser{ PCpnID: cpn.Pid, CampaignID: cpn.ID, UserID: user.ID, ParentID: user.ParentID, ChildType: cpn.ChildType, }) err = s.GetDao().FissionChildUser.WithContext(s.Ctx).Create(info) if err != nil { return nil, err } return jsonm.Marshal(info) })) if err != nil { err = s.BE().CacheError(err) return } m := s.GetDao().FissionChildUser var up = []field.AssignExpr{ m.InviteNum.Add(1), } if player.JoinTime == 0 { up = append(up, m.JoinTime.Value(time.Now().Unix())) } _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(up...) if err != nil { err = s.BE().MysqlError(err) return } _ = cache.NewFissionChildUser(s.Ctx).Clear(player) recordM := s.GetDao().FissionChildUserRecord err = recordM.WithContext(s.Ctx).Create(withdefault.FissionChildUserRecord(&model.FissionChildUserRecord{ PCpnID: player.PCpnID, CampaignID: player.CampaignID, UserID: player.UserID, PlayerID: player.ID, ChildType: player.ChildType, Mark: mtype.FissionChildUserRecordSourceInviteOne, })) if err != nil { err = s.BE().MysqlError(err) return } ranker, err := cachecurd.NewRanker(s.Ctx, ckey.FissionChildRank(cpn.ID), cachecurd.SetChildBuild(cpn.ID, cpn.ChildType)) if err != nil { err = s.BE().CacheError(err) return } if player.InviteNum == 0 { err = ranker.InitMember(fmt.Sprint(player.UserID), player.CreatedAt) if err != nil { err = s.BE().CacheError(err) return } } else { err = ranker.Incr(fmt.Sprint(player.UserID)) if err != nil { err = s.BE().CacheError(err) return } } s.AddAfterRun(func() (err error) { fissionaction.NewMarkPoint(s.Ctx).InApp(user).JoinChild(time.Now().Unix(), player.CampaignID, player.ChildType) fissionaction.NewMarkPoint(s.Ctx).Child(player, user).ChildInvite(time.Now().Unix(), invitedId) return }) if done != nil { return done() } return }