package cmm 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/finder" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" pr "joybuywork/internal/service/provider" "joybuywork/internal/utils" "joybuywork/internal/utils/blink" "joybuywork/internal/utils/lockdo" "joybuywork/internal/utils/merr" "joybuywork/internal/utils/verify" "joybuywork/proto/go_proto/workerr" "strings" "time" deventpb "gits.branchcn.com/backend/deeplink/proto/go_proto/devent" bi_analyzepb "gits.branchcn.com/backend/fissionbi/proto/go_proto/bi_analyze" "gits.branchcn.com/backend/microcode/plugins/derivative/bucket" linkpb "gits.branchcn.com/backend/shortlink/proto/go_proto/link" "gorm.io/gen" "gorm.io/gen/field" ) type CMM struct { ctx context.Context be *merr.BusinessError dao *dao.Query } func NewCMM(ctx context.Context, dao *dao.Query) *CMM { return &CMM{ ctx: ctx, be: merr.NewBE(ctx), dao: dao, } } // GetDomain 获取项目域名 func (s *CMM) GetDomain() (domain string) { domain = app.NewLastingBucket(s.ctx, ckey.ProjectDomainBucket(), func(ctx context.Context) string { return utils.GetRandEnStr(6) }, bucket.UseCheckValue(func(ctx context.Context, val string) bool { m := dao.Project exist, err := m.WithContext(ctx).Where(m.Domain.Eq(val)).Take() if err = dbcheck.DbError(err); err != nil || exist != nil { return false } return true })).BucketOnly().GetValue() return } // GetFissionConfig 获取玩法配置 func (s *CMM) GetFissionConfig(projectId int64) (conf *model.FissionConfig, err error) { m := s.dao.FissionConfig conf, err = m.WithContext(s.ctx).Where(m.ProjectID.Eq(projectId)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if conf != nil { return } lc := app.NewRedisLock(s.ctx, ckey.FissionConfigOnce(projectId)).Lock() defer lc.Release() if lc.Error() != nil { err = workerr.BusyError return } conf, err = m.WithContext(s.ctx).Where(m.ProjectID.Eq(projectId)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if conf != nil { return } conf = withdefault.FissionConfig(&model.FissionConfig{ ProjectID: projectId, DynamicCampaign: 0, }) if err = m.WithContext(s.ctx).Create(conf); err != nil { err = s.be.MysqlError(err) return } return } // CreateContentLink 创建带内容的链接 func (s *CMM) CreateContentLink(user *model.FissionUser, markId int64, mark string) (ct *model.FissionUserCtlink, err error) { lc := app.NewRedisLock(s.ctx, ckey.FissionUserContentLinkStoreOnce(user.ID, markId, mark)) defer lc.Release() if !lc.SoftLock() { err = workerr.OnceLockError return } m := s.dao.FissionUserCtlink ct, err = m.WithContext(s.ctx).Where(m.UserID.Eq(user.ID), m.MarkID.Eq(markId), m.Mark.Eq(mark)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if ct != nil { return } cpn, err := finder.Campaign(s.ctx, user.CampaignID) if err != nil { err = s.be.CacheError(err) return } project, err := finder.Project(s.ctx, cpn.ProjectID) if err != nil { err = s.be.CacheError(err) return } channelM := s.dao.FissionCampaignChannel channel, err := channelM.WithContext(s.ctx).Where(channelM.ID.Eq(user.ChannelID)).Take() if err = dbcheck.DbError(err); err != nil { err = dbcheck.DbError(err) return } ct = withdefault.FissionUserCtlink(&model.FissionUserCtlink{ CampaignID: user.CampaignID, UserID: user.ID, Mark: mark, MarkID: markId, }) link := blink.NewCampaignLink(project.Domain, cpn.HashID, channel.Mark, blink.SetSubUserId(user.SubUserID), blink.SetCtLinkId(ct.HashID)).CampaignContentLink() data, _ := app.NewShortLinkNameLink().CreateShortLink(s.ctx, &linkpb.CreateShortLinkReq{ LongLink: link, Domain: project.Domain, }) if data.GetShortLink() == "" { return } ct.LinkURL = data.GetShortLink() err = m.WithContext(s.ctx).Create(ct) if err != nil { err = s.be.MysqlError(err) return } return } // UserCampaignType 获取活动对应的用户所属类型数据 func (s *CMM) UserCampaignType(cpn *model.FissionCampaign, conf *model.FissionCampaignConfig, langMark, lvMark, countryMark string) (uct consts.CampaignUserType, err error) { 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 } // UserBindChannel 获取用户要绑定的站内渠道 func (s *CMM) UserBindChannel(cpnId int64, cmark string) (channel *model.FissionCampaignChannel, err error) { clM := s.dao.FissionCampaignChannel if cmark != "" { channel, err = cache.NewCampaignChannel(s.ctx).FindByMark(cpnId, cmark) if err != nil { err = s.be.CacheError(err) return } if channel != nil { return } } data, err := lockdo.Bytes(s.ctx, ckey.FissionDefaultAppChannel(cpnId), func() (num []byte, err error) { //找不到 渠道就用默认渠道 channel, err = clM.WithContext(s.ctx).Where( clM.CampaignID.Eq(cpnId), clM.IsDefault.Eq(mtype.Yes), clM.IsDel.Eq(mtype.No), clM.CType.Eq(mtype.FissionCampaignChannelTypeApp), ).Take() if err = dbcheck.DbError(err); err != nil { return } return jsonm.MarshalCkZero(channel) }) if err != nil { err = s.be.CacheError(err) return } channel = &model.FissionCampaignChannel{} _ = jsonm.Unmarshal(data, channel) return } // UserAppReward 获取用户在app内的活动奖品 func (s *CMM) UserAppReward(user *model.FissionUser, orders ...field.Expr) (list []*model.FissionCampaignReward, err error) { conf, err := cache.NewCampaignConfig(s.ctx).FindByCampaignId(user.CampaignID) if err != nil { err = s.be.CacheError(err) return } userLang, userLevel, userCountry := user.UserLang, user.UserLevel, user.UserCountry m := s.dao.FissionCampaignReward var conds = []gen.Condition{ m.CampaignID.Eq(user.CampaignID), m.RewardType.Eq(mtype.FissionCampaignRewardTypeApp), m.IsDel.Eq(mtype.No), } switch conf.RewardGroup { case mtype.FissionRewardGroupLang: conds = append(conds, m.LangMark.Eq(userLang)) case mtype.FissionRewardGroupUserLevel: conds = append(conds, m.LevelID.Eq(userLevel)) case mtype.FissionRewardGroupLangAndUserLevel: conds = append(conds, m.LangMark.Eq(userLang), m.LevelID.Eq(userLevel)) case mtype.FissionRewardGroupCountry: conds = append(conds, m.CountryMark.Eq(userCountry)) default: } query := m.WithContext(s.ctx).Where(conds...) if len(orders) > 0 { query = query.Order(orders...) } else { query = query.Order(m.Sort, m.ID) } list, err = query.Find() if err != nil { err = s.be.MysqlError(err) return } return } type UserAppRewardInfo struct { RewardId int64 Title string ImgUrl string } // UserAppRewardInfo 获取用户在app内的活动奖品信息 func (s *CMM) UserAppRewardInfo(cpn *model.FissionCampaign, user *model.FissionUser) (list map[int64]*UserAppRewardInfo, err error) { rewards, err := s.UserAppReward(user) if err != nil { return } var brIds []int64 var dm = make(map[int64][]int64) for _, reward := range rewards { if _, ok := dm[reward.RewardID]; !ok { brIds = append(brIds, reward.RewardID) } dm[reward.RewardID] = append(dm[reward.RewardID], reward.ID) } preward, err := pr.NewRewards(s.ctx, cpn.ProjectID, brIds...) if err != nil { return } list = make(map[int64]*UserAppRewardInfo) for brid, rewardIds := range dm { info := preward.AutoLang(brid, user.LangMark) for _, v := range rewardIds { list[v] = &UserAppRewardInfo{ RewardId: v, Title: info.Title, ImgUrl: info.ImgUrl, } } } return } // CampaignCancelTask 活动下线要处理一些任务 func (s *CMM) CampaignCancelTask(cpn *model.FissionCampaign, archiveTask bool) (err error) { cpnId, projectId := cpn.ID, cpn.ProjectID // 需要更新一下统计任务的归档时间 if archiveTask { archiveTaskM := s.dao.FissionArchiveTask _, err = archiveTaskM.WithContext(s.ctx). Where(archiveTaskM.CampaignID.Eq(cpnId)). UpdateSimple(archiveTaskM.EndTime.Value(utils.CampaignArchiveEnd(time.Now().Unix()).Unix())) if err != nil { return s.be.MysqlError(err) } if cpn.ReportType == mtype.FissionCampaignReportTypeNew { // 同步到统计服务去 err = s.AnalyzeCampaign(projectId, cpnId, cpn.StartTime, time.Now().Unix(), 0) if err != nil { return } } } condM := s.dao.FissionCampaignCond _, err = condM.WithContext(s.ctx).Where( condM.CampaignID.Eq(cpnId), condM.IsDel.Eq(mtype.No), ).UpdateSimple(condM.DelUse.Value(mtype.Yes), condM.IsDel.Value(mtype.Yes)) if err != nil { return s.be.MysqlError(err) } // 把关联的奖品数据取消关联 relatedM := s.dao.RewardRelated _, err = relatedM.WithContext(s.ctx).Where( relatedM.RelateID.Eq(cpnId), relatedM.RelateTable.Eq(dao.FissionCampaign.TableName()), ).UpdateSimple(relatedM.CancelTime.Value(time.Now().Unix())) if err != nil { return s.be.MysqlError(err) } // 把关联的用户登记数据取消关联 lvRelatedM := s.dao.FissionUserLevelRelated _, err = lvRelatedM.WithContext(s.ctx).Where( lvRelatedM.RelateID.Eq(cpnId), lvRelatedM.RelateTable.Eq(dao.FissionCampaign.TableName()), ).UpdateSimple(lvRelatedM.CancelTime.Value(time.Now().Unix())) if err != nil { return s.be.MysqlError(err) } rewardAddM := s.dao.FissionCampaignRewardAdd _, err = rewardAddM.WithContext(s.ctx).Where( rewardAddM.CampaignID.Eq(cpnId), rewardAddM.IsDel.Eq(mtype.No), ).UpdateSimple(rewardAddM.FinishAt.Value(time.Now().Unix())) if err != nil { return s.be.MysqlError(err) } err = s.SaveEventCampaignTrigger(projectId) if err != nil { return } return } // SaveEventCampaignTrigger 同步事件触发 func (s *CMM) SaveEventCampaignTrigger(projectId int64) (err error) { m := s.dao.FissionCampaignCond list, err := m.WithContext(s.ctx).Where(m.ProjectID.Eq(projectId), m.EventType.In(mtype.FissionCampaignCondEventTypeCustom), m.IsDel.Eq(mtype.No), ).Find() if err != nil { err = s.be.MysqlError(err) return } var eventMap = make(map[string]bool) for _, v := range list { isGlobal, ok := eventMap[v.Event] if !ok || !isGlobal { eventMap[v.Event] = verify.InArray[int8](v.TriggetType, mtype.FissionCampaignCondTriggerTypeGlobal, mtype.FissionCampaignCondTriggerTypePage) } } var eventInfo = make([]*deventpb.SaveEventCampaignTriggerReq_EventInfo, 0, len(eventMap)) for event, isGlobal := range eventMap { eventInfo = append(eventInfo, &deventpb.SaveEventCampaignTriggerReq_EventInfo{ Event: event, HaveGlobal: utils.Ternary[int32](isGlobal, 1, 0), }) } _, err = app.NewDeepLinkEvent().SaveEventCampaignTrigger(s.ctx, &deventpb.SaveEventCampaignTriggerReq{ ProjectId: projectId, EventInfo: eventInfo, }) return } // EventCanTotal 事件是否可以被计算 func (s *CMM) EventCanTotal(cond *model.FissionCampaignCond, subUserId string, adsCondId, joinAt, actionTime int64) (can bool) { if cond.TriggetType == mtype.FissionCampaignCondTriggerTypeGlobal { return true } if cond.TriggetType == mtype.FissionCampaignCondTriggerTypePage { return joinAt > 0 && joinAt <= actionTime } if cond.EventType == mtype.FissionCampaignCondEventTypeAds { return cond.ID == adsCondId } _, err := app.NewDeepLinkEvent().FindCampaignTaskClick(s.ctx, &deventpb.FindCampaignTaskClickReq{ ProjectId: cond.ProjectID, UserId: subUserId, EventName: cond.Event, CondId: cond.ID, }) return err == nil } // AnalyzeCampaign 分析活动报表 func (s *CMM) AnalyzeCampaign(projectId, cpnId, startTime, endTime, idxId int64) (err error) { _, err = app.NewFissionBiAnalyze().Campaign(s.ctx, &bi_analyzepb.CampaignReq{ ProjectId: projectId, CampaignId: cpnId, StartTime: startTime, EndTime: endTime, IdxId: idxId, }) return } // GetUserMap 获取用户地图 func (s *CMM) GetUserMap(user *model.FissionUser) (run *model.FissionDiceRun, err error) { m := s.dao.FissionDiceMapGroup dmg, err := m.WithContext(s.ctx).Where( m.CampaignID.Eq(user.CampaignID), m.LangMark.Eq(user.UserLang), m.LevelID.Eq(user.UserLevel), m.CountryMark.Eq(user.UserCountry), ).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if dmg == nil { err = workerr.NotFound("not found dice map") return } lc := app.NewFollower(s.ctx, ckey.FissionUserRunCreateOnce(user.ID)).Lock() defer lc.Release() if lc.Error() != nil { err = workerr.BusyError return } runM := s.dao.FissionDiceRun run, err = runM.WithContext(s.ctx).Where(runM.UserID.Eq(user.ID), runM.DmgID.Eq(dmg.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if run != nil { return } run = withdefault.FissionDiceRun(&model.FissionDiceRun{ CampaignID: user.CampaignID, UserID: user.ID, DmgID: dmg.ID, }) if err = runM.WithContext(s.ctx).Create(run); err != nil { err = s.be.MysqlError(err) return } return } // LifeStatus 生命体状态修改 func (s *CMM) LifeStatus(life *model.FissionGrowLife, die bool, nextDie int64) (err error) { var up []field.AssignExpr var isDie int8 = mtype.No var mark = mtype.FissionGrowRecordGetRevive if die { isDie = mtype.Yes mark = mtype.FissionGrowRecordGetDie } lifeM := s.dao.FissionGrowLife up = append(up, lifeM.IsDie.Value(isDie)) if nextDie > 0 { up = append(up, lifeM.DieTime.Value(nextDie)) } _, err = lifeM.WithContext(s.ctx).Where(lifeM.ID.Eq(life.ID)).UpdateSimple(up...) if err != nil { err = s.be.MysqlError(err) return } recordM := s.dao.FissionGrowRecord err = recordM.WithContext(s.ctx).Create(withdefault.FissionGrowRecord(&model.FissionGrowRecord{ UserID: life.UserID, CampaignID: life.CampaignID, Mark: mark, VarData: "", })) if err != nil { err = s.be.MysqlError(err) return } return } func (s *CMM) GetCoinInfo(projectId int64, langMark string) (title, imgUrl, rewardTitle string, err error) { coin, err := cache.NewTCoin(s.ctx).FindByProjectId(projectId) if err != nil { err = s.be.CacheError(err) return } if coin == nil { return } m := s.dao.TCoinLang lang, err := m.WithContext(s.ctx).Where(m.TCoinID.Eq(coin.ID), m.LangMark.Eq(langMark)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if lang == nil { lang, err = m.WithContext(s.ctx).Where(m.TCoinID.Eq(coin.ID), m.IsDefault.Eq(mtype.Yes)).Take() if err = dbcheck.DbError(err); err != nil { err = s.be.MysqlError(err) return } if lang == nil { return } } return lang.Title, lang.ImgURL, coin.Title, nil }