package statcampaign 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/service/cmm" "joybuywork/internal/service/provider" "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" "slices" "strings" "time" ) type Logic struct { logic.Logic Req *types.StatCampaignValue userMark string //用户标识 expTime time.Duration //基于活动结束时间的缓存过期时间 lang string channelId, webChannelId int64 } func (s *Logic) Exec() (err error) { cpn, err := s.campaign() if err != nil { return } err = s.statCampaign(cpn) if err != nil { return } err = s.channelStat(cpn) if err != nil { return } err = s.campaignNotice() if err != nil { return } err = s.other(cpn) if err != nil { return } return } func (s *Logic) campaign() (cpn *model.FissionCampaign, err error) { cpn, err = finder.Campaign(s.Ctx, s.Req.CampaignID, s.Req.Hid) if err != nil { err = s.BE().CacheError(err) return } if cpn == nil || cpn.IsDel == mtype.Yes { err = workerr.NotFound("cpn not found") return } // 让后面都可以用活动主键 s.Req.CampaignID = cpn.ID s.lang = s.Req.Language // 一些项目用户id特别处理 if s.Req.BehaviorType == mtype.FissionBehaviorTypeCustom && verify.InArray(cpn.ProjectID, 374, 143) { lg, _, _ := strings.Cut(s.Req.UserID, "_") if !verify.InArray(lg, cpn.GetLangMarks()...) { err = workerr.NotFound("not at rule") return } if s.lang == "" { s.lang = lg } } //构建标识 if s.Req.FromSrv { s.userMark = fmt.Sprintf("srv:%s", s.Req.UserID) } else { s.userMark = utils.Md5String(fmt.Sprintf("%v_%v_%v_%v", s.Req.IpAddress, s.Req.Os, s.Req.OsVersion, s.Req.DeviceModel)) } // 广告活动 if cpn.Target == mtype.FissionCampaignConfigTargetAdsGame { s.expTime = 7 * 24 * time.Hour return } tz := provider.NewCampaignTZ(s.Ctx, cpn, s.lang) if tz.IsEnd() { err = workerr.VerifyError("cpn stat end") return } if tz.IsWait() { err = workerr.VerifyError("cpn not start") return } s.expTime = time.Until(tz.EndTime()) // 不是予发布状态下或者没有用户id的都不用验证白名单 if !tz.IsPre() || s.Req.UserID == "" { return } // 验证白名单 conf, err := cache.NewCampaignConfig(s.Ctx).FindByCampaignId(cpn.ID) if err != nil { err = s.BE().CacheError(err) return } if slices.Contains(conf.GetWhileList(), s.Req.UserID) { return } return nil, workerr.CNotRunError } func (s *Logic) statCampaign(cpn *model.FissionCampaign) (err error) { //行为队列 if s.Req.BehaviorType == mtype.FissionBehaviorTypeCustom || cpn.Target != consts.FissionTargetAds { return } b := types.CampaignBehavior{ CampaignId: cpn.ID, BehaviorType: s.Req.BehaviorType, BehaviorTime: s.Req.BehaviorTime, UserMark: s.userMark, OtherData: nil, MarkId: "", } producer.NewCampaignNotice(s.Ctx).CampaignBehavior(b) return } func (s *Logic) channelStat(cpn *model.FissionCampaign) (err error) { if !verify.InArray(s.Req.BehaviorType, mtype.FissionBehaviorTypeAppOpen, mtype.FissionBehaviorTypeAppInstall) || cpn.Target != mtype.FissionCampaignConfigTargetAdsGame { return } var chId int64 defer func() { if chId == 0 { return } var bt int32 switch s.Req.BehaviorType { case mtype.FissionBehaviorTypeAppInstall: bt = mtype.FissionBehaviorTypeChannelInstall case mtype.FissionBehaviorTypeAppOpen: bt = mtype.FissionBehaviorTypeChannelOpen } producer.NewCampaignNotice(s.Ctx).CampaignBehavior(types.CampaignBehavior{ CampaignId: cpn.ID, BehaviorType: bt, BehaviorTime: s.Req.BehaviorTime, UserMark: s.userMark, MarkId: fmt.Sprint(chId), }) }() // 带渠道标识 if s.Req.Channel != "" { m := s.GetDao().FissionCampaignChannel var ch *model.FissionCampaignChannel ch, err = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(cpn.ID), m.Mark.Eq(s.Req.Channel)).Take() if err = dbcheck.DbError(err); err != nil { return s.BE().MysqlError(err) } if ch != nil { chId = ch.ID if ch.CType == mtype.FissionCampaignChannelTypeApp { s.channelId = ch.ID } else { s.webChannelId = ch.ID } } return } // 有上级用户,通过上级用户的绑定信息来统计 var parent *model.FissionUser if s.Req.OwnerID != "" { parent, err = cache.NewFissionUser(s.Ctx, cpn.ID).FindBySubUserId(s.Req.OwnerID) if err != nil { return s.BE().CacheError(err) } } // 有web_id就是站外渠道来源 if s.Req.WebId != "" { defer func() { s.webChannelId = chId }() // 拿上级用户的站外信息来统计站外渠道,因为产品定义,带来的下级全都归总到上级的渠道上 wbuM := s.GetDao().FissionWebUser var wbu *model.FissionWebUser if parent != nil { wbu, err = wbuM.WithContext(s.Ctx).Where(wbuM.CampaignID.Eq(cpn.ID), wbuM.UserID.Eq(parent.ID)).Take() if err = dbcheck.DbError(err); err != nil { return s.BE().MysqlError(err) } if wbu != nil { chId = wbu.ChannelID } } // 如果在上级的关系链赋予了数据,就直接返回统计 if chId > 0 { return } // 取当前web_id最后访问的渠道id chId, err = app.Redis.Cache.Get(s.Ctx, ckey.FissionWebLastChannel(cpn.ID, s.Req.WebId)).Int64() if err = dbcheck.RdError(err); err != nil { return s.BE().CacheError(err) } if chId > 0 { return } // webid对应的的站外记录 wbu, err = wbuM.WithContext(s.Ctx).Where(wbuM.CampaignID.Eq(cpn.ID), wbuM.WebID.Eq(s.Req.WebId)).Take() if err = dbcheck.DbError(err); err != nil { return s.BE().MysqlError(err) } if wbu != nil { chId = wbu.ChannelID _ = app.Redis.Cache.SetNX(s.Ctx, ckey.FissionWebLastChannel(cpn.ID, wbu.WebID), fmt.Sprint(chId), 2*time.Hour) } return } // 站内的数据 if parent != nil && parent.ChannelID > 0 { chId = parent.ChannelID s.channelId = parent.ChannelID return } if s.Req.UserID != "" { var user *model.FissionUser user, err = cache.NewFissionUser(s.Ctx, cpn.ID).FindBySubUserId(s.Req.UserID) if err != nil { return s.BE().CacheError(err) } if user != nil && user.ChannelID > 0 { chId = user.ChannelID } } // 归到默认渠道 m := s.GetDao().FissionCampaignChannel ch, err := m.WithContext(s.Ctx).Where( m.CampaignID.Eq(cpn.ID), m.CType.Eq(mtype.FissionCampaignChannelTypeApp), m.IsDefault.Eq(mtype.Yes), m.IsDel.Eq(mtype.No), ).Take() if err = dbcheck.DbError(err); err != nil { return s.BE().MysqlError(err) } if ch == nil { return } chId = ch.ID return } // 活动通知 func (s *Logic) campaignNotice() (err error) { // 不是需要通知的事件不通知 if !verify.InArray(s.Req.BehaviorType, mtype.FissionBehaviorTypeAppInstall, mtype.FissionBehaviorTypeAppOpen, mtype.FissionBehaviorTypeAppRegister, mtype.FissionBehaviorTypeCustom, mtype.FissionBehaviorTypeWebFirstOpen, mtype.FissionBehaviorTypePurchase, ) { return } var notice = types.CampaignNotice{ CampaignId: s.Req.CampaignID, DeviceId: s.Req.DeviceID, UserId: s.Req.UserID, ParentId: s.Req.OwnerID, IsInstall: false, BehaviorTime: s.Req.BehaviorTime, NoticeType: 0, NoticeTag: "", Event: s.Req.Event, EventData: s.Req.EventData, ProjectId: s.Req.ProjectID, Ip: s.Req.IpAddress, Os: s.Req.Os, OsVersion: s.Req.OsVersion, DeviceModel: s.Req.DeviceModel, Lang: s.lang, ChannelId: s.channelId, WebChannelId: s.webChannelId, UserMark: s.userMark, PurchasesData: s.Req.PurchasesData, UserLv: s.Req.Tags, } switch s.Req.BehaviorType { case mtype.FissionBehaviorTypeAppInstall: notice.NoticeType = types.NoticeTypeInstall notice.IsInstall = true case mtype.FissionBehaviorTypeAppOpen: notice.NoticeType = types.NoticeTypeOpen case mtype.FissionBehaviorTypeAppRegister: notice.NoticeType = types.NoticeTypeRegister case mtype.FissionBehaviorTypeCustom: notice.NoticeType = types.NoticeTypeCustom case mtype.FissionBehaviorTypeWebFirstOpen: notice.NoticeType = types.NoticeTypeInstall notice.IsInstall = true notice.NoticeTag = types.NoticeTagWeb case mtype.FissionBehaviorTypePurchase: notice.NoticeType = types.NoticeTypePay } // 安装,打开,注册,是新用户才通知 if verify.InArray(s.Req.BehaviorType, mtype.FissionBehaviorTypeAppInstall, mtype.FissionBehaviorTypeAppOpen, mtype.FissionBehaviorTypeAppRegister, mtype.FissionBehaviorTypeWebFirstOpen) { producer.NewCampaignNotice(s.Ctx).CampaignNotice(notice) return } // 自定义事件 if s.Req.UserID == "" && s.Req.DeviceID == "" { return } var keys []string for _, v := range s.Req.EventData { keys = append(keys, v.EventKey) } m := s.GetDao().FissionCampaignCond list, err := m.WithContext(s.Ctx).Where( m.CampaignID.Eq(s.Req.CampaignID), m.Event.Eq(s.Req.Event), m.EventKey.In(keys...), m.IsDel.Eq(mtype.No)).Find() if err != nil { return s.BE().MysqlError(err) } var hasCond bool for _, v := range list { switch v.TriggetType { case mtype.FissionCampaignCondTriggerTypeClick: if cmm.NewCMM(s.Ctx, s.GetDao()).EventCanTotal(v, s.Req.UserID, 0, 0, s.Req.BehaviorTime) { hasCond = true } case mtype.FissionCampaignCondTriggerTypePage: user, _ := cache.NewFissionUser(s.Ctx, s.Req.CampaignID).FindBySubUserId(s.Req.UserID) if user != nil && user.JoinAt > 0 && user.JoinAt <= s.Req.BehaviorTime { hasCond = true } case mtype.FissionCampaignCondTriggerTypeGlobal: hasCond = true } } if !hasCond { return } if verify.InArray[int32](s.Req.BehaviorType, mtype.FissionBehaviorTypeCustom, mtype.FissionBehaviorTypePurchase) { producer.NewCampaignNotice(s.Ctx).CampaignNotice(notice) return } return } func (s *Logic) other(cpn *model.FissionCampaign) (err error) { switch cpn.FissionID { case consts.IdChop: return s._chopHelp(cpn) } return } func (s *Logic) _chopHelp(cpn *model.FissionCampaign) (err error) { if s.Req.BehaviorType != mtype.FissionBehaviorTypeViewDownload || s.Req.OwnerID == "" { return } user, err := cache.NewFissionUser(s.Ctx, cpn.ID).FindBySubUserId(s.Req.OwnerID) if err != nil { err = s.BE().CacheError(err) return } if user == nil { return } producer.NewCampaignNotice(s.Ctx).ValidEvents(types.ValidEvents{ BehaviorTime: s.Req.BehaviorTime, NoticeType: types.NoticeTypeClick, CampaignId: cpn.ID, ParentId: user.ID, ClickInfo: &types.ValidEventsClickInfo{ UserMark: s.userMark, IpAddress: s.Req.IpAddress, Os: s.Req.Os, OsVersion: s.Req.OsVersion, DeviceModel: s.Req.DeviceModel, DeviceID: s.Req.DeviceID, WebId: s.Req.WebId, }, }) return }