go-common/app/job/main/creative/service/task.go
2019-04-22 18:49:16 +08:00

656 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}