package provider import ( "context" "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/consts/ckey" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/finder" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/provider/s2s" "joybuywork/internal/service/queue/producer" "joybuywork/internal/service/queue/types" "joybuywork/internal/utils" "joybuywork/internal/utils/lockdo" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/merr" "joybuywork/internal/utils/verify" "strings" "time" "joybuywork/proto/go_proto/workerr" ddevicepb "gits.branchcn.com/backend/deeplink/proto/go_proto/ddevice" "gits.branchcn.com/backend/microcode/plugins/derivative/lock" "gorm.io/gen/field" ) type FissionUser struct { logic.Context user *model.FissionUser cpn *model.FissionCampaign channel *model.FissionCampaignChannel conf *model.FissionCampaignConfig cpnControl *CampaignTZ upData, msUpData []field.AssignExpr changeLangMark bool changeLvMark bool subUserIdLock, deviceIdLock *lock.RedisLock } func NewFissionUser(ctx context.Context, cpn *model.FissionCampaign, channel *model.FissionCampaignChannel, opts ...logic.CtxOption) *FissionUser { user := &FissionUser{ cpn: cpn, channel: channel, } user.BaseInit(ctx, merr.NewBE(ctx), opts...) return user } func (s *FissionUser) NotifyUser(behaviorTime, noticeType int64, isInstall bool, noticeTag, userMark, uid, deviceId, lvMark, lang, platform, customParam string, webChannelId, channelId int64, expTime time.Duration, parent *model.FissionUser) (user, bindParent *model.FissionUser, newUser bool, err error) { defer s.lockRelease() cpn := s.cpn uct, err := s.userCampaignType(lang, lvMark, "") if err != nil { return } // 如果没有关联的上级,尝试找一下 defer func() { if err != nil || user == nil { return } err = s._refresh(user) if err != nil { return } if (parent == nil || parent.ID != user.ParentID) && user.ParentID > 0 { bindParent, err = finder.FissionUser(s.Ctx, cpn.ID, user.ParentID) if err != nil { err = s.BE().CacheError(err) return } } else { bindParent = parent } if newUser { return } }() user, err = s.notifyFindUser(noticeType, uct, userMark, uid, deviceId, platform, lang, lvMark, customParam, webChannelId) if err != nil || user != nil { return } if uid != "" { lc := s.getSubUserIdLock(uid) if lc.Error() != nil { err = workerr.BusyError return } } if deviceId != "" { lc := s.getDeviceIdLock(deviceId) if lc.Error() != nil { err = workerr.BusyError return } } // 再重新查一次 user, err = s.notifyFindUser(noticeType, uct, userMark, uid, deviceId, platform, lang, lvMark, customParam, webChannelId) if err != nil || user != nil { return } if cpn.FissionID == consts.IdKeep && user == nil && noticeType == types.NoticeTypeCustom { return } lvMark, err = s._lvMark(uid, lang, lvMark, mtype.LVApiLogSourceNotifyUser) if err != nil { return } //新的活动用户 newUser = true user = withdefault.FissionUser(&model.FissionUser{ CreatedAt: behaviorTime, CampaignID: cpn.ID, ChannelID: 0, ParentID: 0, Deep: 0, IsNew: utils.Ternary[int8](isInstall, mtype.Yes, mtype.No), SubUserID: uid, DeviceID: deviceId, UserMark: utils.Ternary[string](uid == "", deviceId, uid), UserType: mtype.FissionUserTypePlay, ParentIds: "", IsView: utils.Ternary[int8](uid == consts.FissionViewer, mtype.Yes, mtype.No), ShareLink: "", Source: 0, LangMark: lang, UserLang: uct.LangMark, LevelMark: lvMark, UserLevelMark: uct.LevelMark, UserLevel: uct.LevelId, }) switch noticeType { case types.NoticeTypeOpen: user.Source = mtype.FissionUserSourceOpen case types.NoticeTypeInstall: user.Source = mtype.FissionUserSourceInstall if noticeTag == types.NoticeTagWeb { user.Source = mtype.FissionUserSourceWebFirstOpen } case types.NoticeTypeRegister: user.Source = mtype.FissionUserSourceRegister case types.NoticeTypeCustom: user.Source = mtype.FissionUserSourceCustom case types.NoticeTypePay: user.Source = mtype.FissionUserSourcePay } if parent != nil { user.ParentID = parent.ID user.ChannelID = parent.ChannelID user.Deep = parent.Deep + 1 user.LangMark = utils.STernary[string](lang, "", parent.LangMark) if parent.ParentID > 0 { user.SetParentIds(append(parent.GetParentIds(), parent.ParentID)) } } // 如果没绑定上级的渠道,就绑定来源渠道 if channelId > 0 && user.ChannelID == 0 { user.ChannelID = channelId } err = s._create(user, uct, platform, lvMark, customParam, "", "", webChannelId) if err != nil { return } if user.SubUserID != "" { producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{ ProjectId: cpn.ProjectID, UserId: user.SubUserID, }) } app.Redis.Cache.Set(s.Ctx, ckey.FissionUserUniqMark(cpn.ID, userMark), user.ID, expTime) app.Redis.Cache.Set(s.Ctx, ckey.FissionUserIovdMark(user.ID), userMark, expTime) return } func (s *FissionUser) VisitUser(uid, platform, lang, lvMark, customParam, ip, centerHashId, moduleId string, device *ddevicepb.FindBlackboxRes, wbu *model.FissionWebUser) (user *model.FissionUser, err error) { defer s.lockRelease() defer func() { if err != nil { return } err = s._refresh(user) }() lvMark, err = s._lvMark(uid, lang, lvMark, mtype.LVApiLogSourceVisitUser) if err != nil { return } conf, err := s.getConf() if err != nil { return } var webChancelId int64 if wbu != nil { webChancelId = wbu.ChannelID } userId := utils.Ternary[string](uid == "", consts.FissionViewer, uid) // 白名单校验 err = s._white(conf, userId, lang) if err != nil { return } countryMark := s._country(conf.RewardGroup, ip) user, err = s._findBySubUserId(userId) if err != nil { return } uct, err := s.userCampaignType(lang, lvMark, countryMark) if err != nil { return } //有用户,或者活动结束了 if user != nil { user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId) return } var deviceId string var parent *model.FissionUser if userId != consts.FissionViewer { user, parent, deviceId, err = s._deviceUser(device, uid, conf) if err != nil || user != nil { user = s._refreshSubUserId(user, uid) user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId) return } } //加锁来创建 lc := s.getSubUserIdLock(userId).Lock() if lc.Error() != nil { err = workerr.BusyError return } if deviceId != "" { lc1 := s.getDeviceIdLock(deviceId).Lock() if lc1.Error() != nil { err = workerr.BusyError return } } //获取到锁再找一次 user, err = s._findBySubUserId(userId) if err != nil { err = s.BE().CacheError(err) return } if user != nil { user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId) return } //创建用户 user = withdefault.FissionUser(&model.FissionUser{ CampaignID: s.cpn.ID, SubUserID: userId, DeviceID: deviceId, UserMark: utils.Ternary[string](userId != "", userId, deviceId), UserType: utils.Ternary[int8](s.cpn.EndTime < time.Now().Unix(), mtype.FissionUserTypeVisitor, mtype.FissionUserTypePlay), IsView: utils.Ternary[int8](userId == consts.FissionViewer, mtype.Yes, mtype.No), Source: mtype.FissionUserSourceVisit, LangMark: lang, LevelMark: lvMark, UserLevelMark: uct.LevelMark, UserLevel: uct.LevelId, UserLang: uct.LangMark, UserCountry: uct.CountryMark, }) if s.channel != nil { user.ChannelID = s.channel.ID } if parent != nil { user.ParentID = parent.ID user.ChannelID = parent.ChannelID user.Deep = parent.Deep + 1 if parent.ParentID > 0 { user.SetParentIds(append(parent.GetParentIds(), parent.ParentID)) } } err = s._create(user, uct, platform, lvMark, customParam, centerHashId, moduleId, webChancelId) if err != nil { return } if user.SubUserID != "" { producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{ ProjectId: s.cpn.ProjectID, UserId: user.SubUserID, }) } return } func (s *FissionUser) GetChangeLangMark() bool { return s.changeLangMark } func (s *FissionUser) GetChangeLvMark() bool { return s.changeLvMark } func (s *FissionUser) getSubUserIdLock(userId string) *lock.RedisLock { if s.subUserIdLock == nil { s.subUserIdLock = app.NewRedisLock(s.Ctx, ckey.FissionUserStoreOnce(s.cpn.ID, userId)) } return s.subUserIdLock } func (s *FissionUser) getDeviceIdLock(deviceId string) *lock.RedisLock { if s.deviceIdLock == nil { s.deviceIdLock = app.NewRedisLock(s.Ctx, ckey.FissionUserStoreDeviceOnce(s.cpn.ID, deviceId)) } return s.deviceIdLock } func (s *FissionUser) lockRelease() { if s.subUserIdLock != nil { s.subUserIdLock.Release() } if s.deviceIdLock != nil { s.deviceIdLock.Release() } } func (s *FissionUser) getConf() (conf *model.FissionCampaignConfig, err error) { if s.conf == nil { s.conf, err = cache.NewCampaignConfig(s.Ctx).FindByCampaignId(s.cpn.ID) if err != nil { err = s.BE().CacheError(err) return } } return s.conf, nil } func (s *FissionUser) getTz(lang string) *CampaignTZ { if s.cpnControl == nil { s.cpnControl = NewCampaignTZ(s.Ctx, s.cpn, lang) } return s.cpnControl } func (s *FissionUser) _white(conf *model.FissionCampaignConfig, userId, lang string) (err error) { if userId == consts.FissionViewer { return } tz := s.getTz(lang) if tz.IsWait() && conf.HasWarmUp == mtype.No { return workerr.CNotRunError } if tz.IsPre() { userIdList := strings.Split(conf.WhiteList, ";") for _, id := range userIdList { if userId == id { return } } return workerr.CNotRunError } return } func (s *FissionUser) _refresh(user *model.FissionUser) (err error) { if len(s.upData) == 0 { return } m := s.GetDao().FissionUser _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(user.ID)).UpdateSimple(s.upData...) if err != nil { err = s.BE().MysqlError(err) return } ms := s.GetDao().FissionUser.SubTable(s.cpn.ID) _, err = ms.WithContext(s.Ctx).Where(ms.ID.Eq(user.ID)).UpdateSimple(s.msUpData...) if err != nil { err = s.BE().MysqlError(err) return } // 清除缓存 _ = cache.NewFissionUser(s.Ctx, user.CampaignID).Clear(user) return } func (s *FissionUser) _country(rewardGroup int8, ip string) string { var countryMark string if rewardGroup == mtype.FissionRewardGroupCountry && ip != "" { res, _ := app.NewDeepLinkDevice().FindIpAreaCode(s.Ctx, &ddevicepb.FindIpAreaCodeReq{Ip: ip}) countryMark = res.GetAreaCode() } return countryMark } func (s *FissionUser) _findBySubUserId(userId string) (user *model.FissionUser, err error) { if userId == "" { return } user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindBySubUserId(userId) if err != nil { err = s.BE().CacheError(err) return } return } func (s *FissionUser) _findByDeviceId(deviceId string) (user *model.FissionUser, err error) { if deviceId == "" { return } user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindByDeviceId(deviceId) if err != nil { err = s.BE().CacheError(err) return } return } func (s *FissionUser) _refreshDeviceId(user *model.FissionUser, deviceId string) *model.FissionUser { m := s.GetDao().FissionUser ms := s.GetDao().FissionUser.SubTable(s.cpn.ID) if user.DeviceID == "" && deviceId != "" { s.upData = append(s.upData, m.DeviceID.Value(deviceId)) s.msUpData = append(s.msUpData, ms.DeviceID.Value(deviceId)) user.DeviceID = deviceId if user.SubUserID == "" && user.UserMark == "" { s.upData = append(s.upData, m.UserMark.Value(deviceId)) s.msUpData = append(s.msUpData, ms.UserMark.Value(deviceId)) user.UserMark = deviceId } } return user } func (s *FissionUser) _refreshSubUserId(user *model.FissionUser, subUserId string) *model.FissionUser { m := s.GetDao().FissionUser ms := s.GetDao().FissionUser.SubTable(s.cpn.ID) if user.SubUserID == "" && subUserId != "" { s.upData = append(s.upData, m.SubUserID.Value(subUserId), m.UserMark.Value(subUserId)) s.msUpData = append(s.msUpData, ms.SubUserID.Value(subUserId), m.UserMark.Value(subUserId)) user.SubUserID = subUserId user.UserMark = subUserId if user.SubUserID != "" { producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{ ProjectId: s.cpn.ProjectID, UserId: user.SubUserID, }) } } return user } func (s *FissionUser) _refreshAttr(user *model.FissionUser, uct consts.CampaignUserType, platform, lang, lvMark, customParam, centerHashId, moduleId string, webChannelId int64) *model.FissionUser { attr := NewFissionUserAttr(s.Ctx, user) m := s.GetDao().FissionUser ms := s.GetDao().FissionUser.SubTable(s.cpn.ID) channel := s.channel // 如果用户没有绑定渠道,就绑定一下 if s.channel != nil && user.ChannelID == 0 { s.upData = append(s.upData, m.ChannelID.Value(channel.ID)) s.msUpData = append(s.msUpData, ms.ChannelID.Value(channel.ID)) user.ChannelID = channel.ID } // 更换匹配中的语种 if user.UserLang != uct.LangMark { s.upData = append(s.upData, m.UserLang.Value(uct.LangMark)) s.msUpData = append(s.msUpData, ms.UserLang.Value(uct.LangMark)) user.UserLang = uct.LangMark } // 更换匹配中的用户等级 if user.UserLevel != uct.LevelId && user.FixedUserLevel == mtype.No { s.upData = append(s.upData, m.UserLevelMark.Value(uct.LevelMark), m.UserLevel.Value(uct.LevelId)) s.msUpData = append(s.msUpData, ms.UserLevelMark.Value(uct.LevelMark), ms.UserLevel.Value(uct.LevelId)) user.UserLevelMark = uct.LevelMark user.UserLevel = uct.LevelId } // 更换匹配中的国家 if user.UserCountry != uct.CountryMark { s.upData = append(s.upData, m.UserCountry.Value(uct.CountryMark)) s.msUpData = append(s.msUpData, ms.UserCountry.Value(uct.CountryMark)) user.UserCountry = uct.CountryMark } // 用户带的语言标识 if lang != "" && user.LangMark != lang { s.upData = append(s.upData, m.LangMark.Value(lang)) s.msUpData = append(s.msUpData, ms.LangMark.Value(lang)) user.LangMark = lang s.changeLangMark = true _ = attr.Lang(lang) } if lvMark != "" { // 用户带的用户等级标识 oldLv := strings.Split(user.LevelMark, ",") setLv := strings.Split(lvMark, ",") // 如果数组相等,并且也没有切换语种,那就更新了 if !verify.SliceEq(oldLv, setLv) { s.upData = append(s.upData, m.LevelMark.Value(lvMark)) s.msUpData = append(s.msUpData, ms.LevelMark.Value(lvMark)) user.LevelMark = lvMark _ = attr.UserLv(uct.LevelMark, setLv) s.changeLvMark = true } } _ = attr.Platform(platform) _ = attr.Center(centerHashId) _ = attr.CenterModule(moduleId) _ = attr.CustomParam(customParam) if webChannelId > 0 { _ = attr.WebChannel(webChannelId) } return user } // 去拿deviceCode信息 func (s *FissionUser) _deviceUser(device *ddevicepb.FindBlackboxRes, uid string, conf *model.FissionCampaignConfig) (user, parent *model.FissionUser, deviceId string, err error) { if device.GetDeviceId() == "" { return } //看看能不能找到device的参与数据 user, err = s._findByDeviceId(device.GetDeviceId()) if err != nil { err = s.BE().CacheError(err) return } if user == nil { deviceId = device.GetDeviceId() if device.GetLastParentUserId() != "" { parent, err = s._findBySubUserId(device.GetLastParentUserId()) if err != nil { err = s.BE().CacheError(err) return } } return } // 这个deviceId被其他人绑定过的 if user.SubUserID == "" { return } if conf.DeviceOnce == mtype.Yes && user.SubUserID != uid { err = workerr.DeviceOnceError return } user = nil return } func (s *FissionUser) _findByUserMark(noticeType int64, userMark string) (user *model.FissionUser, err error) { if !verify.InArray[int64](noticeType, types.NoticeTypeInstall, types.NoticeTypeOpen) || userMark == "" { return } id, _ := app.Redis.Cache.Get(s.Ctx, ckey.FissionUserUniqMark(s.cpn.ID, userMark)).Int64() if id == 0 { return } user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindById(id) if err != nil { err = s.BE().CacheError(err) return } return } func (s *FissionUser) notifyFindUser(noticeType int64, uct consts.CampaignUserType, userMark, uid, deviceId, platform, lang, lvMark, customParam string, webChannelId int64) (user *model.FissionUser, err error) { defer func() { if user != nil { user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, "", "", webChannelId) } }() user, err = s._findByUserMark(noticeType, userMark) if err != nil || user != nil { return } user, err = s._findBySubUserId(uid) if err != nil || user != nil { return } if user != nil { user = s._refreshDeviceId(user, deviceId) return } user, err = s._findByDeviceId(deviceId) if err != nil || user == nil { return } // 相同设备,但是不同用户id, 当新用户处理 if uid != "" && user.SubUserID != "" && user.SubUserID != uid { user = nil return } user = s._refreshSubUserId(user, uid) return } func (s *FissionUser) _create(user *model.FissionUser, uct consts.CampaignUserType, platform, lvMark, customParam, centerHashId, moduleId string, webChannelId int64) (err error) { ms := s.GetDao().FissionUser.SubTable(user.CampaignID) if err = ms.WithContext(s.Ctx).Create(user); err != nil { err = s.BE().MysqlError(err) return } m := s.GetDao().FissionUser if err = m.WithContext(s.Ctx).Create(user); err != nil { err = s.BE().MysqlError(err) return } err = s.GetDao().FissionUserStat.WithContext(s.Ctx).Create(withdefault.FissionUserStat(&model.FissionUserStat{ UserID: user.ID, CampaignID: user.CampaignID, })) if err != nil { err = s.BE().MysqlError(err) return } attr := NewFissionUserAttr(s.Ctx, user) err = attr.WithUser( platform, user.LangMark, uct.LevelMark, lvMark, uct.CountryMark, centerHashId, moduleId, webChannelId, ) if err != nil { return } err = attr.CustomParam(customParam) if err != nil { return } return } // UserCampaignType 获取活动对应的用户所属类型数据 func (s *FissionUser) userCampaignType(langMark, lvMark, countryMark string) (uct consts.CampaignUserType, err error) { cpn := s.cpn conf, err := s.getConf() if err != nil { return } var userLang, userLevelMark, userCountry string var userLevel int64 defer func() { if userLevel > 0 && userLevelMark == "" { level, _ := cache.NewFissionUserLevel(s.Ctx).FindById(userLevel) userLevelMark = level.Mark } uct.LangMark, uct.LevelId, uct.LevelMark, uct.CountryMark = userLang, userLevel, userLevelMark, userCountry }() ful := finder.NewFuserLayer(conf) switch conf.RewardGroup { case mtype.FissionRewardGroupUserLevel: var dfId int64 lvMarks := strings.Split(lvMark, ",") for _, mark := range lvMarks { var lvId int64 lvId, err = cache.NewFissionUserLevel(s.Ctx).FindByMarkId(cpn.ProjectID, mark) if err != nil { err = s.BE().CacheError(err) return } if lvId == 0 { continue } if dfId == 0 { dfId = lvId } _, df := ful.Level().Selected(lvId) if !df { userLevel = lvId userLevelMark = mark break } } if userLevel == 0 { userLevel, _ = ful.Level().Selected(0) } case mtype.FissionRewardGroupLang: userLang, _ = ful.Lang().Selected(langMark) case mtype.FissionRewardGroupLangAndUserLevel: userLang, _ = ful.LangLevel().GetLang(langMark) var dfId int64 lvMarks := strings.Split(lvMark, ",") for _, mark := range lvMarks { var lvId int64 lvId, err = cache.NewFissionUserLevel(s.Ctx).FindByMarkId(cpn.ProjectID, mark) if err != nil { err = s.BE().CacheError(err) return } if lvId == 0 { continue } if dfId == 0 { dfId = lvId } _, df := ful.LangLevel().GetLevel(userLang, lvId) if !df { userLevel = lvId userLevelMark = mark } } if userLevel == 0 { userLevel, _ = ful.LangLevel().GetLevel(userLang, 0) } case mtype.FissionRewardGroupCountry: userCountry, _ = ful.Country().Selected(countryMark) } return } func (s *FissionUser) _lvMark(subUserId, langMark, lvMark, source string) (lv string, err error) { if subUserId == "" { return } project, err := cache.NewProject(s.Ctx).FindById(s.cpn.ProjectID) if err != nil { err = s.BE().CacheError(err) return } conf, err := NewFissionConfig(s.Ctx, project) if err != nil { return } url, err := conf.GetLvUrl() if err != nil { return } if url == nil { return lvMark, nil } key := ckey.FissionLvApiCache(project.ID, subUserId) err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) { if exist { lv = app.Redis.Cache.Get(s.Ctx, key).Val() return } sts, err := s2s.NewLvApi(s.Ctx, conf.Config()) if err != nil { return } data, err := sts.LvApi(s.cpn, subUserId, langMark, source) if err != nil { err = nil app.Redis.Cache.Set(s.Ctx, key, lv, 5*time.Second) return } lv = strings.Join(data.List, ",") app.Redis.Cache.Set(s.Ctx, key, lv, 30*time.Second) return }) if err != nil { err = s.BE().CacheError(err) return } return }