package service import ( "context" "encoding/json" "fmt" "hash/crc32" "time" "go-common/app/job/main/creative/model" "go-common/library/ecode" "go-common/library/log" "go-common/library/queue/databus" ) //commitTask for commit task func (s *Service) commitTask() { for i := 0; i < s.c.Task.TableJobNum; i++ { go func(i int) { s.dispatchData(fmt.Sprintf("%02d", i)) }(i) } } //shardingQueueIndex sharding queue index func (s *Service) shardingQueueIndex(name string, ql int) (i int) { ////注:使用校验和取模的原因是:使得获取的消息均匀散入不同的worker队列中 ch := crc32.ChecksumIEEE([]byte(name)) i = int(ch) % ql return } func (s *Service) dispatchData(index string) { //index 分表后缀名 var id int64 limit := s.c.Task.RowLimit for { res, err := s.newc.UserTasks(context.Background(), index, id, limit) if err != nil { log.Error("s.newc.UserTasks table index(%s)|id(%d)|limit(%d)|err(%v)", index, id, limit, err) return } if len(res) == 0 { time.Sleep(600 * time.Second) id = 0 //reset id continue } id = res[len(res)-1].ID tks := make([]*model.UserTask, 0, len(res)) for _, v := range res { target, ok := s.TaskMapCache[v.TaskID] if !ok || target == nil { continue } //过滤 非T+1的任务 if target.TargetType != model.TargetType004 && target.TargetType != model.TargetType005 && target.TargetType != model.TargetType006 && target.TargetType != model.TargetType007 && target.TargetType != model.TargetType008 && target.TargetType != model.TargetType009 { continue } tks = append(tks, v) } if len(tks) > 0 { s.taskQueue[s.shardingQueueIndex(index, s.c.Task.TableConsumeNum)] <- tks } time.Sleep(5 * time.Second) } } func (s *Service) initTaskQueue() { for i := 0; i < s.c.Task.TableConsumeNum; i++ { ut := make(chan []*model.UserTask, s.chanSize) s.taskQueue[i] = ut go func(ch chan []*model.UserTask) { s.updateTaskStateByGRPC(ch) }(ut) } } //updateTaskStateByGRPC for check task by call grpc. func (s *Service) updateTaskStateByGRPC(c chan []*model.UserTask) { for msg := range c { for _, v := range msg { mid, tid := v.MID, v.TaskID reply, err := s.newc.CheckTaskState(context.Background(), mid, tid) if err != nil { if ec := ecode.Cause(err); ec.Code() == ecode.ServiceUnavailable.Code() { log.Error("s.newc.CheckTaskState mid(%d)|task id(%d)|err(%v)", mid, tid, err) return } log.Warn("s.newc.CheckTaskState mid(%d)|task id(%d)|err(%v)", mid, tid, err) continue } log.Info("updateTaskStateByGRPC mid(%d)|task id(%d)", mid, tid) if reply != nil && reply.FinishState { _, err := s.newc.UpUserTask(context.Background(), mid, tid) if err != nil { log.Error("DriveStateByUser s.newc.UpUserTask mid(%d)|task id(%d)|err(%v)", mid, tid, err) return } } time.Sleep(50 * time.Millisecond) } } } func (s *Service) initStatViewQueue() { for i := 0; i < s.statViewQueueLen; i++ { view := make(chan *model.StatView, s.chanSize) s.statViewSubQueue[i] = view go func(m chan *model.StatView) { //播放 for v := range m { log.Info("StatView v(%+v)|指标:该UID下任意avid的获得-点击量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType015, v.Count) time.Sleep(time.Millisecond * 10) } }(view) } } func (s *Service) initStatLikeQueue() { for i := 0; i < s.statLikeQueueLen; i++ { li := make(chan *model.StatLike, s.chanSize) s.statLikeSubQueue[i] = li go func(m chan *model.StatLike) { //点赞 for v := range m { log.Info("StatLike v(%+v)|指标:该UID下任意avid的获得-点赞量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType020, v.Count) time.Sleep(time.Millisecond * 10) } }(li) } } func (s *Service) initDatabusQueue() { for i := 0; i < s.databusQueueLen; i++ { tk := make(chan *databus.Message, s.chanSize) sh := make(chan *model.ShareMsg, s.chanSize) fl := make(chan *model.Stat, s.chanSize) //粉丝数 rela := make(chan *model.Relation, s.chanSize) //关注 np := make(chan *model.Up, s.chanSize) //最新投稿 op := make(chan *model.Up, s.chanSize) //最新投稿 mp := make(chan *model.Up, s.chanSize) //手机投稿 //单个稿件计数 stsh := make(chan *model.StatShare, s.chanSize) coin := make(chan *model.StatCoin, s.chanSize) fav := make(chan *model.StatFav, s.chanSize) rep := make(chan *model.StatReply, s.chanSize) dm := make(chan *model.StatDM, s.chanSize) s.taskSubQueue[i] = tk s.shareSubQueue[i] = sh s.followerQueue[i] = fl //粉丝 s.relationQueue[i] = rela //关注 s.newUpQueue[i] = np //新投稿 s.oldUpQueue[i] = op //投下5个稿 s.mobileUpQueue[i] = mp //手机投稿 //单个稿件计数 s.statShareSubQueue[i] = stsh s.statCoinSubQueue[i] = coin s.statFavSubQueue[i] = fav s.statReplySubQueue[i] = rep s.statDMSubQueue[i] = dm //水印设置、观看创作学院视频、参加激励计划 go func(m chan *databus.Message) { s.startByTask(m) }(tk) //分享自己的稿件 go func(m chan *model.ShareMsg) { for v := range m { log.Info("startByShare mid(%d)|v(%+v)|指标:该UID分享自己视频的次数≥1", v.MID, v) s.completeUserTask(v.MID, v.OID, model.TargetType002, 1) time.Sleep(time.Millisecond * 10) } }(sh) //关注哔哩哔哩创作中心和粉丝数判断 go func(m chan *model.Stat) { for v := range m { log.Info("followerStat mid(%d)|v(%+v)|指标:该UID的粉丝数≥10 或者 1000", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType010, v.Follower) //新手任务粉丝数 s.completeUserTask(v.MID, 0, model.TargetType022, v.Follower) //进阶任务粉丝数 } }(fl) go func(m chan *model.Relation) { for v := range m { log.Info("relationMID mid(%d)|v(%+v)|指标:该UID的关注列表含有“哔哩哔哩创作中心", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType012, 1) } }(rela) // 该UID下开放浏览的稿件≥1 go func(m chan *model.Up) { for v := range m { log.Info("newUP mid(%d)|v(%+v)|指标:该UID下开放浏览的稿件≥1", v.MID, v) s.completeUserTask(v.MID, v.AID, model.TargetType001, 1) } }(np) // 该UID下开放浏览的稿件≥5 go func(m chan *model.Up) { for v := range m { log.Info("oldUP mid(%d)|v(%+v)|指标:该UID下开放浏览的稿件≥5", v.MID, v) s.completeUserTask(v.MID, v.AID, model.TargetType014, 1) } }(op) //单个稿件计数 go func(m chan *model.StatReply) { for v := range m { log.Info("StatReply v(%+v)|指标:该UID下任意avid的获得-评论量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType016, v.Count) time.Sleep(time.Millisecond * 10) } }(rep) go func(m chan *model.StatShare) { for v := range m { log.Info("StatShare v(%+v)|指标:该UID下任意avid的获得-分享量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType017, v.Count) time.Sleep(time.Millisecond * 10) } }(stsh) go func(m chan *model.StatFav) { for v := range m { log.Info("StatFav v(%+v)|指标:该UID下任意avid的获得-收藏量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType018, v.Count) time.Sleep(time.Millisecond * 10) } }(fav) go func(m chan *model.StatCoin) { for v := range m { log.Info("StatCoin v(%+v)|指标:该UID下任意avid的获得-硬币量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType019, v.Count) time.Sleep(time.Millisecond * 10) } }(coin) go func(m chan *model.StatDM) { for v := range m { log.Info("StatDM v(%+v)|指标:该UID下任意avid的获得-弹幕量(%d)", v, v.Count) s.completeUserTask(s.getMIDByAID(v.ID), v.ID, model.TargetType021, v.Count) time.Sleep(time.Millisecond * 10) } }(dm) go func(m chan *model.Up) { for v := range m { log.Info("Mobile mid(%d)|v(%+v)|指标:该UID通过手机投稿的稿件≥1", v.MID, v) s.completeUserTask(v.MID, v.AID, model.TargetType013, 1) time.Sleep(time.Millisecond * 10) } }(mp) } } func (s *Service) startByTask(c chan *databus.Message) { for msg := range c { v := &model.TaskMsg{} if err := json.Unmarshal(msg.Value, v); err != nil { log.Error("startByTask json.Unmarshal(%v) error(%v)", string(msg.Value), err) continue } switch v.From { case model.MsgForWaterMark: log.Info("startByTask WaterMark mid(%d)|v(%+v)|指标:任务完成期间该UID的水印开关为打开状态", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType011, v.Count) case model.MsgForAcademyFavVideo: log.Info("startByTask AcademyFavVideo mid(%d)|v(%+v)|指标:该UID在创作学院的观看记录≥1", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType003, v.Count) case model.MsgForGrowAccount: log.Info("startByTask GrowAccount mid(%d)|v(%+v)|指标:该UID的激励计划状态为已开通", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType023, v.Count) case model.MsgForOpenFansMedal: log.Info("startByTask OpenFansMedal mid(%d)|v(%+v)|指标:该UID粉丝勋章为开启状态", v.MID, v) s.completeUserTask(v.MID, 0, model.TargetType024, v.Count) } time.Sleep(time.Millisecond * 10) } } func (s *Service) getMIDByAID(aid int64) (mid int64) { arc, err := s.arc.Archive(context.Background(), aid) if err != nil || arc == nil { log.Error("getMIDByAID s.arc.Archive aid(%d)|err(%v)", aid, err) return } mid = arc.Author.Mid return } func (s *Service) getTaskIDByTargetType(mid int64, ty int8) (tid int64) { userTasks, err := s.newc.UserTasksByMIDAndState(context.Background(), mid, model.TaskIncomplete) if err != nil { log.Error("s.newc.UserTasksByMIDAndState mid(%d)|err(%v)", mid, err) return } if len(userTasks) == 0 { return } for _, v := range userTasks { t, ok := s.TaskMapCache[v.TaskID] if ok && t.TargetType == ty { tid = v.TaskID break } } return } // completeUserTask update user task to complete func (s *Service) completeUserTask(mid, aid int64, ty int8, count int64) { tid := s.getTaskIDByTargetType(mid, ty) if tid == 0 { return } target, ok := s.TaskMapCache[tid] if !ok || target == nil { return } if count >= target.TargetValue { if _, err := s.newc.UpUserTask(context.Background(), mid, tid); err != nil { log.Error("s.newc.UpUserTask mid(%d)|tid(%d)|err(%v)", mid, tid, err) return } log.Info("completeUserTask mid(%d)|aid(%d)|count(%d)|taskID(%d)|targetType(%d)|targetValue(%d)", mid, aid, count, tid, ty, target.TargetValue) } } // 对第30天未完成新手任务的UP主,发送消息通知;记录时间点为用户加入任务成就的时间;该消息有且仅发送一次。 func (s *Service) expireTaskNotify() { for i := 0; i < s.c.Task.TaskTableJobNum; i++ { go func(i int) { s.dispatchTasksNotify(fmt.Sprintf("%02d", i)) }(i) } } func (s *Service) dispatchTasksNotify(index string) { var id int64 ext := s.c.Task.TaskExpireTime th := s.c.Task.TaskSendHour tm := s.c.Task.TaskSendMiniute ts := s.c.Task.TaskSendSecond limit := s.c.Task.TaskRowLimitNum batchSize := s.c.Task.TaskBatchMidNum //每次发送mid数量 for { now := time.Now() if now.Hour() != th || now.Minute() != tm || now.Second() != ts { // log.Info("dispatchTasksNotify minuts(%d) second(%d)", now.Minute(), now.Second()) time.Sleep(1 * time.Second) continue } ctime := now.Unix() - ext //检查任务是否超过30天未完成 year, month, day := time.Unix(ctime, 0).Date() start := time.Date(year, month, day, 0, 0, 0, 0, time.Local).Format("2006-01-02 15:04:05") end := time.Date(year, month, day, 23, 59, 59, 999, time.Local).Format("2006-01-02 15:04:05") log.Info("dispatchTasksNotify now(%s)|start(%s)|end(%s)", now.Format("2006-01-02 15:04:05"), start, end) midMap := make(map[int64]*model.UserTask) for { res, err := s.newc.UserTasksNotify(context.Background(), index, id, start, end, limit) if err != nil { log.Error("s.newc.UserTasksNotify table index(%s)|start(%s)|end(%s)|limit(%d)|err(%v)", index, start, end, limit, err) return } if len(res) == 0 { id = 0 break } for _, v := range res { midMap[v.MID] = v } id = res[len(res)-1].ID //next limit time.Sleep(1 * time.Second) } if len(midMap) == 0 { continue } mids := make([]int64, 0, len(midMap)) for mid := range midMap { mids = append(mids, mid) } var tmids []int64 count := len(mids)/batchSize + 1 for i := 0; i < count; i++ { if i == count-1 { tmids = mids[i*batchSize:] } else { tmids = mids[i*batchSize : (i+1)*batchSize] } if len(tmids) > 0 { s.taskNotifyQueue[s.shardingQueueIndex(index, s.c.Task.TaskTableConsumeNum)] <- tmids } } } } func (s *Service) initTaskNotifyQueue() { for i := 0; i < s.c.Task.TaskTableConsumeNum; i++ { ut := make(chan []int64, s.chanSize) s.taskNotifyQueue[i] = ut go func(ch chan []int64) { s.sendTaskNotify(ch) }(ut) } } func (s *Service) sendTaskNotify(c chan []int64) { for mids := range c { if len(mids) == 0 { time.Sleep(time.Second * 60) continue } for i := 1; i < 3; i++ { if err := s.newc.SendNotify(context.Background(), mids, s.c.Task.TaskMsgCode, s.c.Task.TaskTitle, s.c.Task.TaskContent); err != nil { log.Error("sendTaskNotify s.newc.SendNotify(%v) error(%v)", mids, err) time.Sleep(time.Millisecond * 10) continue } else { log.Info("sendTaskNotify s.newc.SendNotify mids(%+v)", mids) break } } time.Sleep(time.Millisecond * 10) } } // loadproc func (s *Service) loadProc() { for { time.Sleep(3 * time.Minute) s.loadTasks() s.loadGiftRewards() } } //load tags func (s *Service) loadTasks() { res, err := s.newc.Tasks(context.Background()) if err != nil { log.Error("s.newc.Tasks error(%v)", err) return } if len(res) == 0 { return } s.TaskCache = res temp := make(map[int64]*model.Task) for _, v := range s.TaskCache { temp[v.ID] = v } s.TaskMapCache = temp } //load gift-reward func (s *Service) loadGiftRewards() { res, err := s.newc.AllGiftRewards(context.Background()) if err != nil { log.Error("s.newc.AllGiftRewards error(%v)", err) return } if len(res) == 0 { return } s.GiftRewardCache = res } // 检查用户是否有奖励可领取 func (s *Service) checkRewardReceive(c context.Context, mid int64) (is bool) { tasks, err := s.newc.UserTasksByMID(c, mid) if err != nil { log.Error("s.newc.UserTasksByMID mid(%v)|error(%v)", mid, err) return } groupMap := make(map[int64][]*model.UserTask) giftMap := make(map[int8][]*model.UserTask) for _, v := range tasks { groupMap[v.TaskGroupID] = append(groupMap[v.TaskGroupID], v) if _, ok := s.GiftRewardCache[v.TaskType]; ok { giftMap[v.TaskType] = append(giftMap[v.TaskType], v) } } groupNum := 0 // 组奖励 已完成个数 giftNum := make(map[int8]bool) // 礼包奖励 已完成个数 for _, ts := range groupMap { for _, t := range ts { if t.State == model.TaskIncomplete { groupNum++ if _, ok := s.GiftRewardCache[t.TaskType]; ok { giftNum[t.TaskType] = true } break } } } r1, err := s.newc.BaseRewardCount(c, mid) // 组奖励 已领取个数 if err != nil { log.Error("s.newc.BaseRewardCount mid(%v)|error(%v)", mid, err) return } r2, err := s.newc.GiftRewardCount(c, mid) // 礼包奖励 已领取个数 if err != nil { log.Error("s.newc.GiftRewardCount mid(%v)|error(%v)", mid, err) return } total := len(groupMap) + len(giftMap) //奖励总数 untotal := groupNum + len(giftNum) //未完成的奖励 receive := r1 + r2 //已领取奖励 // 可领取的奖励 = 奖励总数 -未完成的奖励 - 已领取奖励 count := total - untotal - receive log.Info("checkRewardReceive mid(%d)|奖励总数(%d)|未完成奖励总数(%d)|已领取奖励总数(%d)|可领取奖励总数(%d)", mid, total, untotal, receive, count) if count > 0 { is = true } return } // 该消息每周最多发送 1 条,发送时间为每周六的20:00,用户为上周周六18:00 - 本周周六17:59所有达到领取奖励且 未领取 的用户。 // 通知仅限用户有未领取的奖励时发送:若在该时间段,用户已领取全部可领取的奖励, // 则不发送通知,如果用户已领取部分可领取的奖励,仍有部分奖励未领取,则仍然发送通知 func (s *Service) rewardReceiveNotify() { for i := 0; i < s.c.Task.RewardTableJobNum; i++ { go func(i int) { s.dispatchRewardNotify(fmt.Sprintf("%02d", i)) }(i) } } func (s *Service) dispatchRewardNotify(index string) { var id int64 week := s.c.Task.RewardWeek //星期几 ld := s.c.Task.RewardLastDay //从过去多少天开始查询 lh := s.c.Task.RewardLastHour //几点开始查询 lm := s.c.Task.RewardLastMiniute //几分开始查询 ls := s.c.Task.RewardLastSecond //几秒开始查询 nh := s.c.Task.RewardNowHour //从当前时间几点开始 nm := s.c.Task.RewardNowMiniute //从当前时间几分开始 ns := s.c.Task.RewardNowSecond //从当前时间几秒开始 limit := s.c.Task.RewardRowLimitNum batchSize := s.c.Task.RewardBatchMidNum //每次发送mid数量 for { now := time.Now() if int(now.Weekday()) != week || now.Hour() != nh || now.Minute() != nm || now.Second() != ns { // log.Info("dispatchRewardNotify Weekday(%d) Hour(%d) Minute(%d) Second(%d)", now.Weekday(), now.Hour(), now.Minute(), now.Second()) time.Sleep(1 * time.Second) continue } last := now.AddDate(0, 0, ld).Add(time.Hour * time.Duration(lh)).Add(time.Minute * time.Duration(lm)).Add(time.Second * time.Duration(ls)) log.Info("dispatchRewardNotify last(%s) now(%s)\n", last.Format("2006-01-02 15:04:05"), now.Format("2006-01-02 15:04:05")) midMap := make(map[int64]*model.UserTask) for { res, err := s.newc.CheckTasksForRewardNotify(context.Background(), index, id, last, now, limit) if err != nil { log.Error("s.newc.CheckTasksForRewardNotify table index(%s)|id(%d)|limit(%d)|err(%v)", index, id, limit, err) return } if len(res) == 0 { id = 0 break } for _, v := range res { midMap[v.MID] = v } id = res[len(res)-1].ID //next limit time.Sleep(1 * time.Second) } if len(midMap) == 0 { continue } mids := make([]int64, 0, len(midMap)) for mid := range midMap { if s.checkRewardReceive(context.Background(), mid) { mids = append(mids, mid) } } var tmids []int64 count := len(mids)/batchSize + 1 for i := 0; i < count; i++ { if i == count-1 { tmids = mids[i*batchSize:] } else { tmids = mids[i*batchSize : (i+1)*batchSize] } if len(tmids) > 0 { s.rewardNotifyQueue[s.shardingQueueIndex(index, s.c.Task.RewardTableConsumeNum)] <- tmids } } } } func (s *Service) initRewardNotifyQueue() { for i := 0; i < s.c.Task.RewardTableConsumeNum; i++ { ut := make(chan []int64, s.chanSize) s.rewardNotifyQueue[i] = ut go func(ch chan []int64) { s.sendRewardNotify(ch) }(ut) } } func (s *Service) sendRewardNotify(c chan []int64) { for mids := range c { if len(mids) == 0 { time.Sleep(time.Second * 1) continue } for i := 1; i < 3; i++ { if err := s.newc.SendNotify(context.Background(), mids, s.c.Task.RewardMsgCode, s.c.Task.RewardTitle, s.c.Task.RewardContent); err != nil { log.Error("sendRewardNotify s.newc.SendNotify mids(%+v) error(%v)", mids, err) time.Sleep(time.Millisecond * 100) continue } else { log.Info("sendRewardNotify s.newc.SendNotify mids(%+v)", mids) break } } time.Sleep(time.Millisecond * 10) } }