package campaignnotice import ( "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/consts/ckey" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/dbcheck" "joybuywork/internal/repository/finder" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/cmm" "joybuywork/internal/service/provider" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/service/queue/producer" "joybuywork/internal/service/queue/types" "joybuywork/internal/utils" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/verify" "joybuywork/proto/go_proto/workerr" "fmt" "strconv" "strings" "time" "gits.branchcn.com/backend/microcode/plugins/derivative/lock" "gorm.io/gen/field" ) const ( campaignNoticeEtNone = iota // 无效用户 campaignNoticeEtInvite // 邀请有效 campaignNoticeEtSelf // 自身完成有效任务 campaignNoticeEtParent // 上级完成有效任务 ) const ( campaignNoticeResTypeNone = iota campaignNoticeResTypeNotNext // 没有下一级 campaignNoticeResTypeLack // 库存不足 ) type Notice struct { logic.Logic Req *types.CampaignNotice //项目id projectId int64 //活动hash_id cpnHashId string //父级的统计 pStat *model.FissionUserStat expTime time.Duration // 归档结束用来当缓存过期时间 resendReg bool // 重新触发注册事件 } func (s *Notice) Exec() (err error) { cpn, err := s.campaign() if err != nil { return } parent, err := s.parent() if err != nil { return } user, parent, newUser, err := s.user(cpn, parent) if err != nil || user == nil { return } if cpn.ReportType == mtype.FissionCampaignReportTypeNew { var dems []fissionaction.CusDem if s.Req.WebChannelId > 0 { dems = append(dems, fissionaction.SetChannelId(s.Req.WebChannelId)) } switch s.Req.NoticeType { case types.NoticeTypeOpen: s.MP().InApp(user, dems...).AppOpen(s.Req.BehaviorTime, s.Req.Ip, s.Req.Os, s.Req.OsVersion, s.Req.DeviceModel, newUser) case types.NoticeTypeInstall: if s.Req.NoticeTag == types.NoticeTagWeb { s.MP().InApp(user, dems...).WebFirstOpen(s.Req.BehaviorTime, s.Req.Ip, s.Req.Os, s.Req.OsVersion, s.Req.DeviceModel, newUser) } else { s.MP().InApp(user, dems...).AppInstall(s.Req.BehaviorTime, s.Req.Ip, s.Req.Os, s.Req.OsVersion, s.Req.DeviceModel, newUser) } case types.NoticeTypeRegister: s.MP().InApp(user).AppRegister(s.Req.BehaviorTime, s.Req.Ip, s.Req.Os, s.Req.OsVersion, s.Req.DeviceModel, newUser) case types.NoticeTypeCustom: case types.NoticeTypePay: s.MP().InApp(user).Pay(s.Req.BehaviorTime, s.Req.PurchasesData) } } if cpn.Target == mtype.FissionCampaignConfigTargetAdsGame || s.change(cpn, user, parent, newUser) { return } et, targetNum, setHelp, err := s.effective(cpn, user, parent, newUser) if err != nil { return } if et == campaignNoticeEtSelf { err = s.targetStatNum(user, targetNum) // 自己的达标数 if err != nil { return } } else { err = s.parentStatNum(parent, newUser, setHelp, et, targetNum) if err != nil { return } } switch et { case campaignNoticeEtInvite: // 好友有效 err = s.game(cpn, user, parent) if err != nil { return } case campaignNoticeEtSelf: // 自己完成任务有效 err = s.selfGame(cpn, user, targetNum) if err != nil { return } case campaignNoticeEtParent: // 上级完成任务有效 err = s.selfGame(cpn, parent, targetNum) if err != nil { return } } return } // 活动数据 func (s *Notice) campaign() (cpn *model.FissionCampaign, err error) { cpn, err = finder.Campaign(s.Ctx, s.Req.CampaignId) if err != nil { err = s.BE().CacheError(err) return } if cpn == nil || cpn.IsDel == mtype.Yes { err = workerr.NotFound("not found cpn") return } s.projectId = cpn.ProjectID s.cpnHashId = cpn.HashID s.expTime = provider.NewCampaignTZ(s.Ctx, cpn, s.Req.Lang).EndTime().Sub(time.Now()) return } // 找父亲 func (s *Notice) parent() (parent *model.FissionUser, err error) { if s.Req.ParentId == "" { return } fuCache := cache.NewFissionUser(s.Ctx, s.Req.CampaignId) parent, err = fuCache.FindBySubUserId(s.Req.ParentId) if err != nil { err = s.BE().CacheError(err) return } return } // 通过用户唯一标识找参与用户 func (s *Notice) _findByUserMark(cpnId int64, uct consts.CampaignUserType) (user *model.FissionUser, err error) { if !verify.InArray(s.Req.NoticeType, types.NoticeTypeInstall, types.NoticeTypeOpen) || s.Req.UserMark == "" { return } id, _ := app.Redis.Cache.Get(s.Ctx, ckey.FissionUserUniqMark(cpnId, s.Req.UserMark)).Int64() if id == 0 { return } user, err = cache.NewFissionUser(s.Ctx, cpnId).FindById(id) if err != nil { err = s.BE().CacheError(err) return } if user == nil { return } ms := s.GetDao().FissionUser.SubTable(cpnId) m := s.GetDao().FissionUser var upData []field.AssignExpr var upDataMs []field.AssignExpr // 没绑定device_id就绑定 if user.DeviceID == "" && s.Req.DeviceId != "" { upData = append(upData, m.DeviceID.Value(s.Req.DeviceId)) upDataMs = append(upDataMs, ms.DeviceID.Value(s.Req.DeviceId)) user.DeviceID = s.Req.DeviceId } // 没绑定user_id就绑定 if user.SubUserID == "" && s.Req.UserId != "" { upData = append(upData, m.SubUserID.Value(s.Req.UserId), m.UserMark.Value(s.Req.UserId)) upDataMs = append(upDataMs, ms.SubUserID.Value(s.Req.UserId), ms.UserMark.Value(s.Req.UserId)) user.SubUserID = s.Req.UserId } if user.LangMark != s.Req.Lang { upData = append(upData, m.LangMark.Value(s.Req.Lang), m.UserLang.Value(uct.LangMark)) upDataMs = append(upDataMs, ms.LangMark.Value(s.Req.Lang), ms.UserLang.Value(uct.LangMark)) user.LangMark, user.UserLang = s.Req.Lang, uct.LangMark } // 更换匹配中的用户等级 if user.UserLevel != uct.LevelId && user.FixedUserLevel == mtype.No { upData = append(upData, m.UserLevelMark.Value(uct.LevelMark), m.UserLevel.Value(uct.LevelId)) upDataMs = append(upDataMs, ms.UserLevelMark.Value(uct.LevelMark), ms.UserLevel.Value(uct.LevelId)) user.UserLevelMark = uct.LevelMark user.UserLevel = uct.LevelId } lvMark := strings.Join(s.Req.UserLv, ",") if user.LevelMark != lvMark { upData = append(upData, m.LevelMark.Value(lvMark)) upDataMs = append(upDataMs, ms.LevelMark.Value(lvMark)) user.LevelMark = lvMark } if len(upData) > 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(user.ID)).UpdateSimple(upData...) if err != nil { err = s.BE().MysqlError(err) return } _, err = ms.WithContext(s.Ctx).Where(ms.ID.Eq(user.ID)).UpdateSimple(upDataMs...) if err != nil { err = s.BE().MysqlError(err) return } _ = cache.NewFissionUser(s.Ctx, cpnId).Clear(user) } return } // 通过user_id找参与用户 func (s *Notice) _findByOwnerId(uct consts.CampaignUserType) (user *model.FissionUser, err error) { if s.Req.UserId == "" { return } fuCache := cache.NewFissionUser(s.Ctx, s.Req.CampaignId) user, err = fuCache.FindBySubUserId(s.Req.UserId) if err != nil { err = s.BE().CacheError(err) return } if user == nil { return } ms := s.GetDao().FissionUser.SubTable(user.CampaignID) m := s.GetDao().FissionUser var upData []field.AssignExpr var upDataMs []field.AssignExpr // 没绑定device_id就绑定 if user.DeviceID == "" && s.Req.DeviceId != "" { upData = append(upData, m.DeviceID.Value(s.Req.DeviceId)) upDataMs = append(upDataMs, ms.DeviceID.Value(s.Req.DeviceId)) user.DeviceID = s.Req.DeviceId } // 没绑定user_id就绑定 if user.SubUserID == "" && s.Req.UserId != "" { upData = append(upData, m.SubUserID.Value(s.Req.UserId), m.UserMark.Value(s.Req.UserId)) upDataMs = append(upDataMs, ms.SubUserID.Value(s.Req.UserId), ms.UserMark.Value(s.Req.UserId)) user.SubUserID = s.Req.UserId } if user.LangMark != s.Req.Lang { upData = append(upData, m.LangMark.Value(s.Req.Lang), m.UserLang.Value(uct.LangMark)) upDataMs = append(upDataMs, ms.LangMark.Value(s.Req.Lang), ms.UserLang.Value(uct.LangMark)) user.LangMark, user.UserLang = s.Req.Lang, uct.LangMark } // 更换匹配中的用户等级 if user.UserLevel != uct.LevelId && user.FixedUserLevel == mtype.No { upData = append(upData, m.UserLevelMark.Value(uct.LevelMark), m.UserLevel.Value(uct.LevelId)) upDataMs = append(upDataMs, ms.UserLevelMark.Value(uct.LevelMark), ms.UserLevel.Value(uct.LevelId)) user.UserLevelMark = uct.LevelMark user.UserLevel = uct.LevelId } lvMark := strings.Join(s.Req.UserLv, ",") if user.LevelMark != lvMark { upData = append(upData, m.LevelMark.Value(lvMark)) upDataMs = append(upDataMs, ms.LevelMark.Value(lvMark)) user.LevelMark = lvMark } if len(upData) > 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(user.ID)).UpdateSimple(upData...) if err != nil { err = s.BE().MysqlError(err) return } _, err = ms.WithContext(s.Ctx).Where(ms.ID.Eq(user.ID)).UpdateSimple(upDataMs...) if err != nil { err = s.BE().MysqlError(err) return } _ = fuCache.Clear(user) } return } // 通过device找活动的参与用户 func (s *Notice) _findByDeviceId(uct consts.CampaignUserType) (user *model.FissionUser, err error) { if s.Req.DeviceId == "" { return } fuCache := cache.NewFissionUser(s.Ctx, s.Req.CampaignId) user, err = fuCache.FindByDeviceId(s.Req.DeviceId) if err != nil { err = s.BE().CacheError(err) return } if user == nil || user.SubUserID == s.Req.UserId { return } // 如果同一个设备多个用户,就当成一个新用户处理吧 if user.SubUserID != "" && s.Req.UserId != "" { return nil, nil } ms := s.GetDao().FissionUser.SubTable(user.CampaignID) m := s.GetDao().FissionUser var upData []field.AssignExpr var upDataMs []field.AssignExpr // 没绑定device_id就绑定 if user.DeviceID == "" && s.Req.DeviceId != "" { upData = append(upData, m.DeviceID.Value(s.Req.DeviceId)) upDataMs = append(upDataMs, ms.DeviceID.Value(s.Req.DeviceId)) user.DeviceID = s.Req.DeviceId } // 没绑定user_id就绑定 if user.SubUserID == "" && s.Req.UserId != "" { upData = append(upData, m.SubUserID.Value(s.Req.UserId), m.UserMark.Value(s.Req.UserId)) upDataMs = append(upDataMs, ms.SubUserID.Value(s.Req.UserId), ms.UserMark.Value(s.Req.UserId)) user.SubUserID = s.Req.UserId } if user.LangMark != s.Req.Lang { upData = append(upData, m.LangMark.Value(s.Req.Lang), m.UserLang.Value(uct.LangMark)) upDataMs = append(upDataMs, ms.LangMark.Value(s.Req.Lang), ms.UserLang.Value(uct.LangMark)) user.LangMark, user.UserLang = s.Req.Lang, uct.LangMark } // 更换匹配中的用户等级 if user.UserLevel != uct.LevelId && user.FixedUserLevel == mtype.No { upData = append(upData, m.UserLevelMark.Value(uct.LevelMark), m.UserLevel.Value(uct.LevelId)) upDataMs = append(upDataMs, ms.UserLevelMark.Value(uct.LevelMark), ms.UserLevel.Value(uct.LevelId)) user.UserLevelMark = uct.LevelMark user.UserLevel = uct.LevelId } lvMark := strings.Join(s.Req.UserLv, ",") if user.LevelMark != lvMark { upData = append(upData, m.LevelMark.Value(lvMark)) upDataMs = append(upDataMs, ms.LevelMark.Value(lvMark)) user.LevelMark = lvMark } if len(upData) > 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(user.ID)).UpdateSimple(upData...) if err != nil { err = s.BE().MysqlError(err) return } _, err = ms.WithContext(s.Ctx).Where(ms.ID.Eq(user.ID)).UpdateSimple(upDataMs...) if err != nil { err = s.BE().MysqlError(err) return } _ = fuCache.Clear(user) } return } // 参与用户 func (s *Notice) user(cpn *model.FissionCampaign, parent *model.FissionUser) (user, bindParent *model.FissionUser, newUser bool, err error) { return provider.NewFissionUser(s.Ctx, cpn, nil).NotifyUser( s.Req.BehaviorTime, s.Req.NoticeType, s.Req.IsInstall, s.Req.NoticeTag, s.Req.UserMark, s.Req.UserId, s.Req.DeviceId, strings.Join(s.Req.UserLv, ","), s.Req.Lang, s.platform(), s.Req.CustomParam, s.Req.WebChannelId, s.Req.ChannelId, s.expTime, parent, ) } func (s *Notice) platform() string { platform, dm := strings.ToLower(s.Req.Os), strings.ToLower(s.Req.DeviceModel) if platform == "macos" { platform = consts.PlatformIOS } if verify.InArray(platform, consts.PlatformAndroid, consts.PlatformIOS, consts.PlatformWeb) { // 创建一些可变更的属性 if platform == consts.PlatformIOS && dm == consts.PlatformIPad { platform = consts.PlatformIPad } } return platform } func (s *Notice) effective(cpn *model.FissionCampaign, user, parent *model.FissionUser, newUser bool) (et int, targetNum int32, setHelp bool, err error) { conf, err := cache.NewCampaignConfig(s.Ctx).FindByCampaignId(user.CampaignID) if err != nil { err = s.BE().CacheError(err) return } // 设置用户助力成功,仅当首次完成的触发条件的时候会执行 defer func() { if parent != nil && user.IsHelp == mtype.No && et != campaignNoticeEtNone { userMs := s.GetDao().FissionUser.SubTable(cpn.ID) res, err1 := userMs.WithContext(s.Ctx).Where( userMs.ID.Eq(user.ID), userMs.IsHelp.Eq(mtype.No), ).UpdateSimple(userMs.IsHelp.Value(mtype.Yes)) if err1 != nil { err = s.BE().MysqlError(err1) return } _ = cache.NewFissionUser(s.Ctx, cpn.ID).Clear(user) setHelp = res.RowsAffected > 0 } langTitle, _ := provider.NewLangTitle(s.Ctx, cpn, user.LangMark) pageTitle, _ := langTitle.GetPageTitle() // 助力成功回调 if setHelp { switch s.Req.NoticeType { case types.NoticeTypeInstall: producer.NewOplatformOther(s.Ctx).OpoCampaignHelpOk(types.OpoCampaignHelpOk{ ProjectId: cpn.ProjectID, CampaignHashId: cpn.HashID, CampaignTitle: cpn.Title, PageTitle: pageTitle, HelperUserId: user.SubUserID, HelperDeviceId: user.DeviceID, BeHelperUserId: parent.SubUserID, BeHelperDeviceId: parent.DeviceID, Source: types.HelpSourceInstall, KvData: make([]*types.KV, 0), }, producer.SetHeaderLang(user.LangMark)) case types.NoticeTypeRegister: producer.NewOplatformOther(s.Ctx).OpoCampaignHelpOk(types.OpoCampaignHelpOk{ ProjectId: cpn.ProjectID, CampaignHashId: cpn.HashID, CampaignTitle: cpn.Title, PageTitle: pageTitle, HelperUserId: user.SubUserID, HelperDeviceId: user.DeviceID, BeHelperUserId: parent.SubUserID, BeHelperDeviceId: parent.DeviceID, Source: types.HelpSourceRegister, KvData: make([]*types.KV, 0), }, producer.SetHeaderLang(user.LangMark)) case types.NoticeTypeCustom: var kv = make([]*types.KV, 0, len(s.Req.EventData)) for _, v := range s.Req.EventData { kv = append(kv, &types.KV{ Event: s.Req.Event, Key: v.EventKey, Value: v.EventVal, }) } producer.NewOplatformOther(s.Ctx).OpoCampaignHelpOk(types.OpoCampaignHelpOk{ ProjectId: cpn.ProjectID, CampaignHashId: cpn.HashID, CampaignTitle: cpn.Title, PageTitle: pageTitle, HelperUserId: user.SubUserID, HelperDeviceId: user.DeviceID, BeHelperUserId: parent.SubUserID, BeHelperDeviceId: parent.DeviceID, Source: types.HelpSourceCustom, KvData: kv, }, producer.SetHeaderLang(user.LangMark)) } } }() if cpn.FissionID == consts.IdSignIn { if parent == nil { // 没有上级不处理 return } if s.Req.NoticeType == types.NoticeTypeInstall { et = utils.Ternary[int](newUser, campaignNoticeEtInvite, campaignNoticeEtNone) return } return } switch conf.Option { case mtype.FissionCampaignConfigTargetOptionApp: if parent == nil { // 没有上级不处理 return } if conf.AppEvent == mtype.FissionCampaignConfigTargetAppEventInstall && s.Req.NoticeType == types.NoticeTypeInstall { et = utils.Ternary[int](newUser, campaignNoticeEtInvite, campaignNoticeEtNone) return } if conf.AppEvent == mtype.FissionCampaignConfigTargetAppEventRegister && s.Req.NoticeType == types.NoticeTypeRegister && user.IsHelp == mtype.No { // 防止并发操作 once := app.Redis.Cache.SetNX(s.Ctx, ckey.FissionRegisterOnce(user.ID), s.Req.BehaviorTime, 20*time.Second).Val() et = utils.Ternary[int](once, campaignNoticeEtInvite, campaignNoticeEtNone) return } return case mtype.FissionCampaignConfigTargetOptionFriend: if parent == nil || len(s.Req.EventData) == 0 { // 没有上级不处理 return } targetNum, err = s._eventEffective(user, conf.BothType) if err != nil { return } // 如果是老带新和一起赢奖励,那么一个用户达标只能算一次助力成功 if verify.InArray(cpn.FissionID, consts.IdWinTogether, consts.IdReferral) { if targetNum > 0 && user.IsHelp == mtype.No { once := app.Redis.Cache.SetNX(s.Ctx, ckey.FissionEventEffectiveOnce(user.ID), s.Req.BehaviorTime, 20*time.Second).Val() et = utils.Ternary[int](once, campaignNoticeEtInvite, campaignNoticeEtNone) return } return } et = utils.Ternary[int](targetNum > 0, campaignNoticeEtParent, campaignNoticeEtNone) return case mtype.FissionCampaignConfigTargetOptionSelf, mtype.FissionCampaignConfigTargetOptionUse: if len(s.Req.EventData) == 0 { return } targetNum, err = s._eventEffective(user, conf.BothType) if err != nil { return } et = utils.Ternary[int](targetNum > 0, campaignNoticeEtSelf, campaignNoticeEtNone) return case mtype.FissionCampaignConfigTargetOptionTask: if len(s.Req.EventData) == 0 { return } targetNum, err = s._eventEffective(user, conf.BothType) if err != nil { return } } return } // 事件触发是否有效,返回达标次数 func (s *Notice) _eventEffective(user *model.FissionUser, bothType int8) (targetNum int32, err error) { //自定义事件处理逻辑 m := s.GetDao().FissionCampaignCond conds, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(user.CampaignID), m.IsDel.Eq(mtype.No)).Find() if err != nil { err = s.BE().MysqlError(err) return } if len(conds) == 0 { return } // 用户只能同时存在一个计算有效 lc := app.NewRedisLock(s.Ctx, ckey.FissionCustomEffectiveOnce(user.ID)).Lock() defer lc.Release() if lc.Error() != nil { err = workerr.BusyError return } var target = make(map[int64]*utils.CondTotalFinishRes) // 完成的任务 var condValMap = make(map[int64]*model.FissionCampaignCond) // 条件id对应的任务数据 var condMap = make(map[string][]*model.FissionCampaignCond) // event+key对应的条件数据集合 for _, cond := range conds { key := fmt.Sprintf("%s_%s", cond.Event, cond.EventKey) condMap[key] = append(condMap[key], cond) target[cond.ID] = nil // 默认任务条件都没有完成 condValMap[cond.ID] = cond } cm := cmm.NewCMM(s.Ctx, s.GetDao()) // 只获取活动相关的条件 var condTotal = make(map[int64]string) // 条件累计数据 var records []*model.FissionUserCondRecord for _, v := range s.Req.EventData { key := fmt.Sprintf("%s_%s", s.Req.Event, v.EventKey) eCond, ok := condMap[key] // 找到event+key对应的条件数据集合 if !ok { // 如果找不到就不是我们想要的自定义事件 continue } for _, cond := range eCond { if !cm.EventCanTotal(cond, user.SubUserID, 0, user.JoinAt, s.Req.BehaviorTime) { continue } switch cond.IfCond { case mtype.FissionCampaignCondIfCondTotal: if total, ok1 := condTotal[cond.ID]; ok1 { // 如果存在当前条件的数据就累加 condTotal[cond.ID] = utils.StrAdd(total, v.EventVal) } else { condTotal[cond.ID] = v.EventVal } records = append(records, withdefault.FissionUserCondRecord(&model.FissionUserCondRecord{ CampaignID: user.CampaignID, UserID: user.ID, CondID: cond.ID, Val: v.EventVal, IsUse: mtype.Yes, // 因为累加过了,所以要标识为已使用 BehaviorTime: s.Req.BehaviorTime, })) s.condFinish(user.ID, cond.ID, v.EventVal) case mtype.FissionCampaignCondIfCondEq: if cond.EventVal == v.EventVal { // 等式下,必须要值相等才成立条件 if total, ok1 := condTotal[cond.ID]; ok1 { // 如果存在当前条件的数据就累加 condTotal[cond.ID] = utils.StrAdd(total, v.EventVal) } else { condTotal[cond.ID] = v.EventVal } records = append(records, withdefault.FissionUserCondRecord(&model.FissionUserCondRecord{ CampaignID: user.CampaignID, UserID: user.ID, CondID: cond.ID, Val: v.EventVal, BehaviorTime: s.Req.BehaviorTime, })) s.condFinish(user.ID, cond.ID, v.EventVal) } case mtype.FissionCampaignCondIfCondGte: if utils.NumberStrCompareGte(v.EventVal, cond.EventVal) { // 大于等于下,必须要值大于等于才成立条件 if total, ok1 := condTotal[cond.ID]; ok1 { // 如果存在当前条件的数据就累加 condTotal[cond.ID] = utils.StrAdd(total, v.EventVal) } else { condTotal[cond.ID] = v.EventVal } records = append(records, withdefault.FissionUserCondRecord(&model.FissionUserCondRecord{ CampaignID: user.CampaignID, UserID: user.ID, CondID: cond.ID, Val: v.EventVal, BehaviorTime: s.Req.BehaviorTime, })) s.condFinish(user.ID, cond.ID, v.EventVal) } } } } // 没有完成的条件肯定不会触发有效的 if len(records) == 0 { return } ucrM := s.GetDao().FissionUserCondRecord // 先去找还被计算的记录 ucr, err := ucrM.WithContext(s.Ctx).Where(ucrM.UserID.Eq(user.ID), ucrM.IsUse.Eq(mtype.No)).Find() if err != nil { err = s.BE().MysqlError(err) return } // 把新创建的记录入库 err = ucrM.WithContext(s.Ctx).Create(records...) if err != nil { err = s.BE().MysqlError(err) return } // 找到条件总揽 fucM := s.GetDao().FissionUserCond fuc, err := fucM.WithContext(s.Ctx).Where(fucM.UserID.Eq(user.ID)).Find() if err != nil { err = s.BE().MysqlError(err) return } // 把总揽条件构建一个map var uCondMap = make(map[int64]*model.FissionUserCond) for _, uCond := range fuc { uCondMap[uCond.CondID] = uCond if total, ok1 := condTotal[uCond.CondID]; ok1 { // 这里要把之前累计的都放到累计map上 condTotal[uCond.CondID] = utils.StrAdd(total, uCond.Total) } else { condTotal[uCond.CondID] = uCond.Total } } var incCond = make(map[int64]int64) // 完成次数需要+1的总揽数据 for _, record := range records { cond := condValMap[record.CondID] info, ok := uCondMap[cond.ID] if !ok { info = withdefault.FissionUserCond(&model.FissionUserCond{ CampaignID: user.CampaignID, UserID: user.ID, CondID: record.CondID, }) err = fucM.WithContext(s.Ctx).Create(info) if err != nil { err = s.BE().MysqlError(err) return } uCondMap[cond.ID] = info } switch cond.IfCond { case mtype.FissionCampaignCondIfCondEq, mtype.FissionCampaignCondIfCondGte: incCond[info.ID]++ } } for condId, total := range condTotal { cond := condValMap[condId] if cond.IfCond != mtype.FissionCampaignCondIfCondTotal { continue } info := uCondMap[cond.ID] if utils.NumberStrCompareGte(total, cond.EventVal) { // 这里代表完成了一次累计任务,要重新开始累计 res := utils.CondTotalFinishNum(total, cond.EventVal) // 处理值问题 _, err = fucM.WithContext(s.Ctx). Where(fucM.ID.Eq(info.ID)). UpdateSimple(fucM.Num.Add(int64(res.Num)), fucM.CountNum.Add(int64(res.Num)), fucM.Total.Value(res.Remain)) if err != nil { err = s.BE().MysqlError(err) return } target[cond.ID] = res uCondMap[cond.ID].CountNum += int64(res.Num) } else { // 这里是没有完成累计任务,记录最新的累计值 _, err = fucM.WithContext(s.Ctx).Where(fucM.ID.Eq(info.ID)).UpdateSimple(fucM.Total.Value(total)) if err != nil { err = s.BE().MysqlError(err) return } } } // 完成的任务数 if len(incCond) > 0 { for id, num := range incCond { _, err = fucM.WithContext(s.Ctx).Where(fucM.ID.Eq(id)).UpdateSimple(fucM.Num.Add(num)) if err != nil { err = s.BE().MysqlError(err) return } } } // 整合新的和旧的所有没有计算的记录 for _, v := range ucr { records = append(records, v) } if len(records) == 0 { return } // 把计算完毕的记录都标识成已计算,下一次就不找这些数据 var useRecord []int64 var countMap = make(map[int32][]int64) defer func() { if targetNum == 0 { return } if len(countMap) > 0 { for k, v := range countMap { _, err = fucM.WithContext(s.Ctx).Where(fucM.ID.In(v...)).UpdateSimple(fucM.CountNum.Sub(int64(k))) if err != nil { err = s.BE().MysqlError(err) return } } } if len(useRecord) > 0 { _, err = ucrM.WithContext(s.Ctx).Where(ucrM.ID.In(useRecord...)).UpdateSimple(ucrM.IsUse.Value(mtype.Yes)) if err != nil { err = s.BE().MysqlError(err) return } } }() // 分条件计算 switch bothType { case mtype.FissionCampaignConfigTargetBothTypeOne: // 达标统计 var condStat []int64 for _, v := range records { cond, ok := condValMap[v.CondID] if !ok { continue } switch cond.IfCond { case mtype.FissionCampaignCondIfCondGte, mtype.FissionCampaignCondIfCondEq: targetNum++ useRecord = append(useRecord, v.ID) condStat = append(condStat, cond.ID) } } for condId, res := range target { if res == nil || res.Num <= 0 { continue } targetNum += res.Num // 这个事件完成的次数 for i := 0; i < int(res.Num); i++ { condStat = append(condStat, condId) } countMap[res.Num] = append(countMap[res.Num], uCondMap[condId].ID) } // 达标统计 if len(condStat) > 0 { producer.NewCampaignNotice(s.Ctx).CampaignTargetStat(types.CampaignTargetStat{ CampaignId: user.CampaignID, UserId: user.ID, CondIds: condStat, FinishTime: s.Req.BehaviorTime, }) } return case mtype.FissionCampaignConfigTargetBothTypeAll: var groupIdx = make(map[int64]int) var groupFinish = make(map[int]map[int64]int64) // 完成任务分组 for _, v := range records { cond, ok := condValMap[v.CondID] if !ok { continue } switch cond.IfCond { case mtype.FissionCampaignCondIfCondGte, mtype.FissionCampaignCondIfCondEq: idx := groupIdx[v.CondID] _, ok1 := groupFinish[idx] if !ok1 { groupFinish[idx] = make(map[int64]int64) } groupFinish[idx][v.CondID] = v.ID groupIdx[v.CondID]++ } } var useCount []int64 for condId := range target { fuc1, ok1 := uCondMap[condId] if !ok1 { continue } for i := 0; i < int(fuc1.CountNum); i++ { idx := groupIdx[condId] _, ok2 := groupFinish[idx] if !ok2 { groupFinish[idx] = make(map[int64]int64) } groupFinish[idx][condId] = 0 groupIdx[condId]++ } useCount = append(useCount, fuc1.ID) } var condStat []int64 for _, cond := range conds { condStat = append(condStat, cond.ID) } for _, v := range groupFinish { if len(v) != len(conds) { continue } for _, recordId := range v { if recordId > 0 { useRecord = append(useRecord, recordId) } } targetNum++ producer.NewCampaignNotice(s.Ctx).CampaignTargetStat(types.CampaignTargetStat{ CampaignId: user.CampaignID, UserId: user.ID, CondIds: condStat, FinishTime: s.Req.BehaviorTime, }) } if len(useCount) > 0 { countMap[targetNum] = useCount } return } return } func (s *Notice) condFirst(userId, condId int64) (yes bool, err error) { key := ckey.FissionUserCondFirst(userId, condId) exist, err := app.Redis.Exists(s.Ctx, key) if err != nil { err = s.BE().RedisError(err) return } if exist { return } yes = app.Redis.Cache.SetNX(s.Ctx, key, 1, s.expTime).Val() if !yes { return } m := s.GetDao().FissionUserCondRecord record, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(userId), m.CondID.Eq(condId)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } return record == nil, nil } // 完成事件 func (s *Notice) condFinish(userId, condId int64, total string) { val, _ := strconv.ParseFloat(total, 64) first, _ := s.condFirst(userId, condId) producer.NewCampaignNotice(s.Ctx).CampaignCondStat(types.CampaignCondStat{ UserId: userId, CondId: condId, Total: val, BehaviorTime: s.Req.BehaviorTime, First: first, Event: s.Req.Event, EventData: s.Req.EventData, }) } // 邀请目标人数换活动机会数 (使用:大转盘、盲盒、九宫格、老虎机、赛事竞猜、签到) func (s *Notice) _targetToChance(userId int64, taskChance, targetNum, chance int32) (fnh int32, err error) { //获取到当前的任务次数,需要完成多少轮任务 oldNum := taskChance / chance num, err := s._statDirectNum(userId) if err != nil { return } //当前可计算完成任务的人数 nowInvite := int32(num) - (oldNum * targetNum) //当前完成的次数 fnh = nowInvite / targetNum return } // 统计有效邀请人数 func (s *Notice) _statDirectNum(userId int64) (num int64, err error) { key := ckey.FissionUserInviteStat(userId) if app.Redis.Cache.Exists(s.Ctx, key).Val() == 0 { //锁住计算获得的人次 lc := app.NewRedisLock(s.Ctx, ckey.ForLock(key), lock.SetExpire(5*time.Second)).Lock() defer lc.Release() if lc.Error() != nil { err = workerr.BusyError return } if app.Redis.Cache.Exists(s.Ctx, key).Val() == 0 { num = app.Redis.Cache.IncrBy(s.Ctx, key, s.pStat.EffectiveNum).Val() } } //当前邀请人数+1 num = app.Redis.Cache.Incr(s.Ctx, key).Val() app.Redis.Cache.Expire(s.Ctx, key, 2*time.Hour) return } // 玩法数据处理 func (s *Notice) game(cpn *model.FissionCampaign, user, parent *model.FissionUser) (err error) { switch cpn.FissionID { case consts.IdPrizeQuiz: //有奖竞猜 return s.prizeQuiz(cpn, user, parent) case consts.IdPrizeAnswer, consts.IdQa, consts.IdQaVideo, consts.IdQaAudio: //趣味问答 return s.prizeAnswer(user, parent) case consts.IdSignIn: //签到 return s.sign(user, parent) } return } // 有奖竞猜 func (s *Notice) prizeQuiz(cpn *model.FissionCampaign, user, parent *model.FissionUser) (err error) { m := s.GetDao().FissionCampaignQuiz fcq, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(cpn.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } fqM := s.GetDao().FissionQuiz pqUser, err := fqM.WithContext(s.Ctx).Where(fqM.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if pqUser == nil { //创建用户的赛事竞猜玩法数据 err = fqM.WithContext(s.Ctx).Create(withdefault.FissionQuiz(&model.FissionQuiz{ UserID: user.ID, CampaignID: user.CampaignID, Chance: fcq.InitChance, })) if err != nil { err = s.BE().MysqlError(err) return } } //防止并发获取投票次数 lc := app.NewRedisLock(s.Ctx, ckey.FissionParentInviteOnce(parent.ID), lock.SetExpire(5*time.Second)).Lock() defer lc.Release() if lc.Error() != nil { return } //是新用户,完成目标 ppqUser, err := fqM.WithContext(s.Ctx).Where(fqM.UserID.Eq(parent.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if ppqUser == nil { return } //默认一人给一次 var fnh int32 = 1 //添加机会数 defer func() { if err != nil || fnh == 0 { return } addChance := fnh * fcq.Chance _, err = fqM.WithContext(s.Ctx).Where(fqM.ID.Eq(ppqUser.ID)).UpdateSimple(fqM.Chance.Add(addChance)) if err != nil { err = s.BE().MysqlError(err) return } }() //目标数小于,直接一个因为这里就是一个人进来的 if fcq.TargetNum <= 1 { return } //如果多人给一次,借助redis.incr来记录一下 fnh, err = s._targetToChance( parent.ID, ppqUser.TaskChance, fcq.TargetNum, fcq.Chance, ) return } func (s *Notice) prizeAnswer(user *model.FissionUser, parent *model.FissionUser) (err error) { m := s.GetDao().FissionCampaignQa qa, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(user.CampaignID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } //看看有没有活动用户数据 fqM := s.GetDao().FissionQa player, err := fqM.WithContext(s.Ctx).Where(fqM.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if player == nil { //创建用户的玩法用户数据 err = fqM.WithContext(s.Ctx).Create(withdefault.FissionQa(&model.FissionQa{ UserID: user.ID, CampaignID: user.CampaignID, Chance: qa.InitChance, })) if err != nil { err = s.BE().MysqlError(err) return } } //防止并发获取答题次数 lc := app.NewRedisLock(s.Ctx, ckey.FissionParentInviteOnce(parent.ID), lock.SetExpire(5*time.Second)).Lock() defer lc.Release() if lc.Error() != nil { return } //是新用户,完成目标 pPlayer, err := fqM.WithContext(s.Ctx).Where(fqM.UserID.Eq(parent.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if pPlayer == nil { pPlayer = withdefault.FissionQa(&model.FissionQa{ UserID: parent.ID, CampaignID: parent.CampaignID, Chance: qa.InitChance, }) //这里是防止后面有一些需求,导致上级用户可以不进入活动页面页能拉新的情况,不让一般都不会走进来这个if if err = fqM.WithContext(s.Ctx).Create(pPlayer); err != nil { err = s.BE().MysqlError(err) return } } //假设邀请了完成了一次获取抽奖机会的任务 var fnh int32 = 1 //添加机会数 defer func() { if err != nil || fnh == 0 { return } addChance := fnh * qa.Chance _, err = fqM.WithContext(s.Ctx).Where(fqM.ID.Eq(pPlayer.ID)).UpdateSimple( fqM.Chance.Add(addChance), fqM.TaskChance.Add(addChance), ) if err != nil { err = s.BE().MysqlError(err) return } }() //目标数小于等于1,直接当完成,因为这里就是一个人进来的 if qa.TargetNum <= 1 { return } //如果多人给一次,借助redis.incr来记录一下 fnh, err = s._targetToChance( pPlayer.UserID, pPlayer.TaskChance, qa.TargetNum, qa.Chance, ) return } func (s *Notice) selfGame(cpn *model.FissionCampaign, user *model.FissionUser, targetNum int32) (err error) { switch cpn.FissionID { case consts.IdPrizeQuiz: //有奖竞猜 return s.selfPrizeQuiz(user, targetNum) case consts.IdPrizeAnswer, consts.IdQa, consts.IdQaVideo, consts.IdQaAudio: //趣味问答 return s.selfPrizeAnswer(user, targetNum) } return } func (s *Notice) selfPrizeQuiz(user *model.FissionUser, targetNum int32) (err error) { m := s.GetDao().FissionCampaignQuiz conf, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(user.CampaignID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } //看看有没有活动用户数据 playerM := s.GetDao().FissionQuiz player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } var chance = conf.Chance * targetNum if player == nil { //创建用户的玩法用户数据 err = playerM.WithContext(s.Ctx).Create(withdefault.FissionQuiz(&model.FissionQuiz{ UserID: user.ID, CampaignID: user.CampaignID, Chance: conf.InitChance + chance, TaskChance: chance, })) if err != nil { err = s.BE().MysqlError(err) return } return } _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(playerM.TaskChance.Add(chance), playerM.Chance.Add(chance)) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Notice) selfPrizeAnswer(user *model.FissionUser, targetNum int32) (err error) { m := s.GetDao().FissionCampaignQa conf, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(user.CampaignID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } //看看有没有活动用户数据 playerM := s.GetDao().FissionQa player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } var chance = conf.Chance * targetNum if player == nil { //创建用户的玩法用户数据 err = playerM.WithContext(s.Ctx).Create(withdefault.FissionQa(&model.FissionQa{ UserID: user.ID, CampaignID: user.CampaignID, Chance: conf.InitChance + chance, TaskChance: chance, })) if err != nil { err = s.BE().MysqlError(err) return } return } _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(playerM.TaskChance.Add(chance), playerM.Chance.Add(chance)) if err != nil { err = s.BE().MysqlError(err) return } return } // 上级统计数据 func (s *Notice) parentStatNum(parent *model.FissionUser, newUser, setHelp bool, et int, targetNum int32) (err error) { if parent == nil { return } m := s.GetDao().FissionUserStat var userIds = []int64{parent.ID} if parent.ParentID > 0 { userIds = append(userIds, parent.ParentID) } stat, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(parent.ID)).Take() if err = dbcheck.DbError(err); err != nil { return s.BE().MysqlError(err) } s.pStat = stat // 直接要更新的数据, 间接要更新的数据 var upData, indirectData []field.AssignExpr if newUser { upData = append(upData, m.DirectNum.Add(1)) if parent.ParentID > 0 { indirectData = append(indirectData, m.IndirectNum.Add(1)) } } // 当前用户变成助力成功 if setHelp { upData = append(upData, m.EffectiveNum.Add(1)) if parent.ParentID > 0 { indirectData = append(indirectData, m.IndirectEffectiveNum.Add(1)) } } // 达标数+1 if et != 0 { upData = append(upData, m.TargetNum.Add(int64(targetNum))) if parent.ParentID > 0 { indirectData = append(indirectData, m.IndirectTargetNum.Add(int64(targetNum))) } } // 直接数据更新 if len(upData) > 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(stat.ID)).UpdateSimple(upData...) if err != nil { err = s.BE().MysqlError(err) return } } // 间接数据更新 if len(indirectData) > 0 { _, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(parent.ParentID)).UpdateSimple(indirectData...) if err != nil { err = s.BE().MysqlError(err) return } } return } // 达标数统计 func (s *Notice) targetStatNum(user *model.FissionUser, targetNum int32) (err error) { m := s.GetDao().FissionUserStat // 直接达标数 _, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(user.ID)).UpdateSimple(m.TargetNum.Add(int64(targetNum))) if err != nil { err = s.BE().MysqlError(err) return } // 间接达标数 if user.ParentID > 0 { _, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(user.ParentID)).UpdateSimple(m.IndirectTargetNum.Add(int64(targetNum))) if err != nil { err = s.BE().MysqlError(err) return } } return } func (s *Notice) sign(user *model.FissionUser, parent *model.FissionUser) (err error) { m := s.GetDao().FissionCampaignSign fcs, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(user.CampaignID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } //看看有没有活动用户数据 playerM := s.GetDao().FissionSign player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if player == nil { //创建用户的玩法用户数据 err = playerM.WithContext(s.Ctx).Create(withdefault.FissionSign(&model.FissionSign{ UserID: user.ID, CampaignID: user.CampaignID, })) if err != nil { err = s.BE().MysqlError(err) return } } //防止并发获取骰子次数 lc := app.NewRedisLock(s.Ctx, ckey.FissionParentInviteOnce(parent.ID), lock.SetExpire(5*time.Second)).Lock() defer lc.Release() if lc.Error() != nil { return } //是新用户,完成目标 pfb, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(parent.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if pfb == nil { pfb = withdefault.FissionSign(&model.FissionSign{ UserID: parent.ID, CampaignID: parent.CampaignID, }) //这里是防止后面有一些需求,导致上级用户可以不进入活动页面页能拉新的情况,不让一般都不会走进来这个if if err = playerM.WithContext(s.Ctx).Create(pfb); err != nil { err = s.BE().MysqlError(err) return } } if fcs.HasCard == mtype.No { return } //假设邀请了完成了一次获取补签卡机会的任务 var fnh int32 = 1 //添加机会数 defer func() { if err != nil || fnh == 0 { return } addChance := fnh _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(pfb.ID)).UpdateSimple( playerM.CardNum.Add(addChance), playerM.TaskCard.Add(addChance), ) if err != nil { err = s.BE().MysqlError(err) return } }() //目标数小于等于1,直接当完成,因为这里就是一个人进来的 if fcs.InviteNum <= 1 { return } //如果多人给一次,借助redis.incr来记录一下 fnh, err = s._targetToChance( pfb.UserID, pfb.TaskCard, fcs.InviteNum, 1, ) return } // 切换处理方式 func (s *Notice) change(cpn *model.FissionCampaign, user *model.FissionUser, parent *model.FissionUser, newUser bool) (ok bool) { if !consts.NewEventCpn(cpn.FissionID) { return false } switch s.Req.NoticeType { case types.NoticeTypeInstall, types.NoticeTypeRegister, types.NoticeTypeOpen: if parent == nil { return } producer.NewCampaignNotice(s.Ctx).ValidEvents(types.ValidEvents{ UserId: user.ID, NewUser: newUser, BehaviorTime: s.Req.BehaviorTime, NoticeType: s.Req.NoticeType, CampaignId: s.Req.CampaignId, ParentId: user.ParentID, }) if s.resendReg { producer.NewCampaignNotice(s.Ctx).ValidEvents(types.ValidEvents{ UserId: user.ID, NewUser: false, BehaviorTime: user.RegisterAt, NoticeType: types.NoticeTypeRegister, CampaignId: user.CampaignID, ParentId: user.ParentID, }) } case types.NoticeTypeCustom, types.NoticeTypePay: if user == nil { return } var eventType int32 = mtype.FissionCampaignCondEventTypeCustom if s.Req.NoticeType == types.NoticeTypePay { eventType = mtype.FissionCampaignCondEventTypeStandard } producer.NewCampaignNotice(s.Ctx).ValidEvents(types.ValidEvents{ UserId: user.ID, NewUser: newUser, BehaviorTime: s.Req.BehaviorTime, NoticeType: s.Req.NoticeType, Event: s.Req.Event, EventData: s.Req.EventData, EventType: eventType, CampaignId: s.Req.CampaignId, ParentId: user.ParentID, }) } return true }