Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"service_test.go",
"weeklyhonor_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/creative/model/weeklyhonor:go_default_library",
"//app/job/main/creative/conf:go_default_library",
"//app/service/main/up/api/v1:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"academy.go",
"databus.go",
"service.go",
"task.go",
"weeklyhonor.go",
],
importpath = "go-common/app/job/main/creative/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/creative/model/weeklyhonor:go_default_library",
"//app/interface/openplatform/article/model:go_default_library",
"//app/job/main/creative/conf:go_default_library",
"//app/job/main/creative/dao/academy:go_default_library",
"//app/job/main/creative/dao/archive:go_default_library",
"//app/job/main/creative/dao/monitor:go_default_library",
"//app/job/main/creative/dao/newcomer:go_default_library",
"//app/job/main/creative/dao/weeklyhonor:go_default_library",
"//app/job/main/creative/model:go_default_library",
"//app/service/main/archive/api:go_default_library",
"//app/service/main/up/api/v1:go_default_library",
"//library/conf/env:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,115 @@
package service
import (
"context"
"math"
"time"
"go-common/app/interface/openplatform/article/model"
"go-common/app/service/main/archive/api"
"go-common/library/log"
)
//FlushHot for compute archive hot value by time.
func (s *Service) FlushHot(bs int) {
var (
c = context.TODO()
id int64
limit = 30
)
for {
arcs, err := s.aca.Archives(c, id, bs, limit)
if err != nil {
log.Error("s.aca.Archives id(%d) error(%v)", id, err)
return
}
count := len(arcs)
if count == 0 {
id = 0 //重新按定时跑数据
time.Sleep(time.Hour * 1)
continue
}
oids := make([]int64, 0)
for _, a := range arcs {
oids = append(oids, a.OID)
id = a.ID //标记当前处理到的id
}
hots, err := s.computeHotByOIDs(c, oids, bs)
if err != nil {
log.Error("s.aca.Archives id(%d) error(%v)", id, err)
return
}
if s.aca.UPHotByAIDs(c, hots) != nil {
log.Error("s.aca.UPHotByAIDs hots(%+v) error(%v)", hots, err)
return
}
}
}
func (s *Service) computeHotByOIDs(c context.Context, oids []int64, bs int) (res map[int64]int64, err error) {
res = make(map[int64]int64)
if bs == 1 {
arcs, err := s.arc.Archives(c, oids)
if err != nil {
return nil, err
}
stat, err := s.arc.Stats(c, oids)
if err != nil {
log.Error("s.arc.Stats oids(%+v)|business(%d)|error(%v)", oids, bs, err)
return nil, err
}
for _, oid := range oids {
if v, ok := arcs[oid]; ok && v != nil {
if t, ok := stat[oid]; ok && t != nil {
res[oid] = countArcHot(t, int64(v.PubDate))
}
}
}
} else if bs == 2 {
arts, err := s.arc.ArticleMetas(c, oids)
if err != nil {
return nil, err
}
for _, oid := range oids {
if v, ok := arts[oid]; ok && v != nil {
res[oid] = countArtHot(v)
}
}
}
return
}
//countArcHot 视频=硬币*0.4+收藏*0.3+弹幕*0.4+评论*0.4+播放*0.25+点赞*0.4+分享*0.6 最新视频(一天内发布)提权[总值*1.5]
func countArcHot(t *api.Stat, ptime int64) int64 {
if t == nil {
return 0
}
hot := float64(t.Coin)*0.4 +
float64(t.Fav)*0.3 +
float64(t.Danmaku)*0.4 +
float64(t.Reply)*0.4 +
float64(t.View)*0.25 +
float64(t.Like)*0.4 +
float64(t.Share)*0.6
if ptime >= time.Now().AddDate(0, 0, -1).Unix() && ptime <= time.Now().Unix() {
hot *= 1.5
}
return int64(math.Floor(hot))
}
// countArtHot 专栏=硬币*0.4+收藏*0.3+评论*0.4+阅读*0.25+点赞*0.4+分享*0.6 最新专栏(一天内发布)提权[总值*1.5]
func countArtHot(t *model.Meta) int64 {
if t.Stats == nil {
return 0
}
hot := float64(t.Stats.Coin)*0.4 +
float64(t.Stats.Favorite)*0.3 +
float64(t.Stats.Reply)*0.4 +
float64(t.Stats.View)*0.25 +
float64(t.Stats.Like)*0.4 +
float64(t.Stats.Share)*0.6
if int64(t.PublishTime) >= time.Now().AddDate(0, 0, -1).Unix() && int64(t.PublishTime) <= time.Now().Unix() {
hot *= 1.5
}
return int64(math.Floor(hot))
}

View File

@@ -0,0 +1,479 @@
package service
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"go-common/app/job/main/creative/model"
"go-common/library/log"
)
const (
_archive = "archive"
_insert = "insert"
_update = "update"
fromArchiveUp = 0
fromArchiveNewUp = 1
_relationMidTable = "user_relation_mid_"
_relationStatTable = "user_relation_stat_"
)
//pub up auth msg.
func (s *Service) pub(mid int64, from, isAuthor int) (err error) {
c := context.TODO()
msg := &model.Msg{
MID: mid,
From: from,
IsAuthor: isAuthor,
TimeStamp: time.Now().Unix(),
}
if err = s.upPub.Send(c, strconv.FormatInt(mid, 10), msg); err != nil {
log.Error("pub mid(%d) error(%v)", mid, err)
}
return
}
func (s *Service) arcNotifyCanalConsume() {
var err error
for msg := range s.arcNotifySub.Messages() {
msg.Commit()
s.arcNotifyMo++
m := &model.CanalMsg{}
if err = json.Unmarshal(msg.Value, m); err != nil {
log.Error("json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if m.Table == _archive {
s.arcNotifyMsg(m)
log.Info("arcNotifyCanalConsume key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
}
}
s.wg.Done()
}
func (s *Service) arcNotifyMsg(m *model.CanalMsg) {
var (
err error
newArc = &model.Archive{}
oldArc = &model.Archive{}
)
if err = json.Unmarshal(m.New, newArc); err != nil {
log.Error("arcNotifyMsg newMsg json.Unmarshal(%s) error(%v)", string(m.New), err)
return
}
mid := newArc.MID
if mid <= 0 {
log.Error("arcNotifyMsg mid (%d) error", mid)
return
}
if m.Action == _insert && newArc.State >= 0 { //0->1
s.pub(mid, fromArchiveUp, 1)
} else if m.Action == _update {
if err = json.Unmarshal(m.Old, oldArc); err != nil {
log.Error("arcNotifyMsg oldMsg json.Unmarshal(%s) error(%v)", string(m.Old), err)
return
}
if oldArc.State < 0 && (newArc.State >= 0 || newArc.State == -6) { //0->1
s.pub(mid, fromArchiveUp, 1)
}
if (oldArc.State >= 0 || oldArc.State == -6) && newArc.State < 0 { //1->0
cnt, err := s.arc.UpCount(context.Background(), mid)
if err != nil {
log.Error("arcNotifyMsg s.arc.UpCount(%d) error(%v)", mid, err)
return
}
if cnt <= 0 {
s.pub(mid, fromArchiveUp, 0)
}
}
}
}
func (s *Service) arcCanalConsume() {
var err error
for msg := range s.arcSub.Messages() {
msg.Commit()
s.arcMo++
m := &model.CanalMsg{}
if err = json.Unmarshal(msg.Value, m); err != nil {
log.Error("json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if m.Table == _archive && m.Action == _insert {
arc := &model.Archive{}
if err = json.Unmarshal(m.New, arc); err != nil {
log.Error("creative-job binglog newMsg json.Unmarshal(%s) error(%v)", m.New, err)
continue
}
if arc.MID > 0 {
s.pub(arc.MID, fromArchiveNewUp, 1)
}
log.Info("arcCanalConsume key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
}
}
s.wg.Done()
}
func (s *Service) task() {
for msg := range s.taskSub.Messages() {
msg.Commit()
log.Info("databus task key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.taskSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- msg
}
for _, c := range s.taskSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) share() {
for msg := range s.shareSub.Messages() {
msg.Commit()
m := &model.ShareMsg{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus share json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if m.TP != 3 {
continue
}
mid, authorMID := m.MID, s.getMIDByAID(m.OID)
if mid != authorMID { //不是该用户分享的稿件则不做任何处理
log.Warn("s.arc.Archive mid(%d)|author mid(%d)", mid, authorMID)
continue
}
log.Info("databus share key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.shareSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
for _, c := range s.shareSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) relation() {
var err error
newF, advancedF := s.c.Task.NewFollower, s.c.Task.AdvancedFollower
for msg := range s.relationSub.Messages() {
msg.Commit()
rl := &model.RelaMessage{}
if err = json.Unmarshal(msg.Value, rl); err != nil {
log.Error("databus relation json.Unmarshal (%v) error(%v)", msg.Value, err)
continue
}
if !strings.HasPrefix(rl.Table, _relationStatTable) && !strings.HasPrefix(rl.Table, _relationMidTable) {
continue
}
if strings.HasPrefix(rl.Table, _relationStatTable) {
ost := &model.Stat{}
st := &model.Stat{}
if rl.Action == "update" {
if err = json.Unmarshal(rl.Old, ost); err != nil {
log.Error("relation old msg json.Unmarshal(%s) error(%v)", string(rl.Old), err)
continue
}
}
if err = json.Unmarshal(rl.New, st); err != nil {
log.Error("relation new msg json.Unmarshal(%s) error(%v)", string(rl.New), err)
continue
}
isFollower := false
if ost.Follower < newF && st.Follower >= newF { //新手任务粉丝数限制
isFollower = true
}
if ost.Follower < advancedF && st.Follower >= advancedF { //进阶任务粉丝数限制
isFollower = true
}
if isFollower {
log.Info("databus relation key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.followerQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- st
}
}
if strings.HasPrefix(rl.Table, _relationMidTable) {
fl := &model.Relation{}
if err = json.Unmarshal(rl.New, fl); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", string(rl.New), err)
continue
}
if fl.FID != s.c.Task.BiliMID { //过滤关注哔哩哔哩创作中心
continue
}
log.Info("databus relation key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.relationQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- fl
}
}
for _, c := range s.relationQueue {
close(c)
}
for _, c := range s.followerQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statView() {
statView, statViewUp := s.c.Task.StatView, s.c.Task.StatViewUp
for msg := range s.statViewSub.Messages() {
msg.Commit()
m := &model.StatView{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statView json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") {
continue
}
if m.Count >= statView && m.Count <= statViewUp {
s.statViewSubQueue[s.shardingQueueIndex(msg.Key, s.statViewQueueLen)] <- m
}
}
for _, c := range s.statViewSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statLike() {
statLike, statLikeUp := s.c.Task.StatLike, s.c.Task.StatLikeUp
for msg := range s.statLikeSub.Messages() {
msg.Commit()
m := &model.StatLike{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statLike json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") {
continue
}
if m.Count >= statLike && m.Count <= statLikeUp {
s.statLikeSubQueue[s.shardingQueueIndex(msg.Key, s.statLikeQueueLen)] <- m
}
}
for _, c := range s.statLikeSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statShare() {
statShare, statShareUp := s.c.Task.StatShare, s.c.Task.StatShareUp
for msg := range s.statShareSub.Messages() {
msg.Commit()
m := &model.StatShare{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statShare json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") {
continue
}
if m.Count >= statShare && m.Count <= statShareUp {
log.Info("databus statShare key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.statShareSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
}
for _, c := range s.statShareSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statCoin() {
statCoin, statCoinUp := s.c.Task.StatCoin, s.c.Task.StatCoinUp
for msg := range s.statCoinSub.Messages() {
msg.Commit()
m := &model.StatCoin{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statCoin json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") {
continue
}
if m.Count >= statCoin && m.Count <= statCoinUp {
log.Info("databus statCoin key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.statCoinSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
}
for _, c := range s.statCoinSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statFav() {
statFav, statFavUp := s.c.Task.StatFav, s.c.Task.StatFavUp
for msg := range s.statFavSub.Messages() {
msg.Commit()
m := &model.StatFav{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statFav json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") {
continue
}
if m.Count >= statFav && m.Count <= statFavUp {
log.Info("databus statFav key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.statFavSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
}
for _, c := range s.statFavSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statReply() {
statReply, statReplyUp := s.c.Task.StatReply, s.c.Task.StatReplyUp
for msg := range s.statReplySub.Messages() {
msg.Commit()
m := &model.StatReply{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statReply json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") || m.Count < s.c.Task.StatReply {
continue
}
if m.Count >= statReply && m.Count <= statReplyUp {
log.Info("databus statReply key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.statReplySubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
}
for _, c := range s.statReplySubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) statDM() {
statDM, statDMUp := s.c.Task.StatDM, s.c.Task.StatDMUp
for msg := range s.statDMSub.Messages() {
msg.Commit()
m := &model.StatDM{}
if err := json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus statDM json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if !strings.EqualFold(m.Type, "archive") || m.Count < s.c.Task.StatDM {
continue
}
if m.Count >= statDM && m.Count <= statDMUp {
log.Info("databus statDM key(%s) value(%s) partition(%d) offset(%d) commit", msg.Key, msg.Value, msg.Partition, msg.Offset)
s.statDMSubQueue[s.shardingQueueIndex(msg.Key, s.databusQueueLen)] <- m
}
}
for _, c := range s.statDMSubQueue {
close(c)
}
s.wg.Done()
}
func (s *Service) newUp() {
var err error
for msg := range s.newUpSub.Messages() {
msg.Commit()
m := &model.CanalMsg{}
if err = json.Unmarshal(msg.Value, m); err != nil {
log.Error("databus newUp json.Unmarshal(%v) error(%v)", string(msg.Value), err)
continue
}
if m.Table != _archive {
continue
}
newArc := &model.Archive{}
if err = json.Unmarshal(m.New, newArc); err != nil {
log.Error("databus newUp newMsg json.Unmarshal(%s) error(%v)", string(m.New), err)
continue
}
mid, aid := newArc.MID, newArc.AID
if mid == 0 || aid == 0 {
log.Error("databus newUp mid(%d) | aid(%d) error", mid, aid)
continue
}
isUp := false
if m.Action == "insert" && newArc.State >= 0 {
isUp = true
} else if m.Action == "update" {
oldArc := &model.Archive{}
if err = json.Unmarshal(m.Old, oldArc); err != nil {
log.Error("newUp oldMsg json.Unmarshal(%s) error(%v)", string(m.Old), err)
continue
}
if oldArc.State < 0 && (newArc.State >= 0 || newArc.State == -6) { //0->1
isUp = true
}
}
if isUp {
av, err := s.arc.View(context.Background(), mid, aid) //获取投稿来源
if err != nil {
log.Error("newUp s.arc.View mid(%d) mid(%d) av(%+v) error(%v)", mid, aid, av, err)
}
if av != nil && av.Archive != nil && (av.Archive.UpFrom == 3 || av.Archive.UpFrom == 8 || av.Archive.UpFrom == 9) { // 3-App , 8-Android , 9-IOS
log.Info("databus mobile mid(%d) aid(%d) av(%+v)", mid, aid, av)
s.mobileUpQueue[s.shardingQueueIndex(strconv.FormatInt(mid, 10), s.databusQueueLen)] <- &model.Up{AID: aid, MID: mid}
}
cnt, err := s.arc.UpCount(context.Background(), mid)
if err != nil {
log.Error("newUp s.arc.UpCount(%d) error(%v)", mid, err)
continue
}
if cnt == 1 { //新手投下自己的第一个稿件
log.Info("databus newUp mid(%d) aid(%d) count(%d)", mid, aid, cnt)
s.newUpQueue[s.shardingQueueIndex(strconv.FormatInt(mid, 10), s.databusQueueLen)] <- &model.Up{AID: aid, MID: mid}
}
if cnt >= 5 { //进阶任务视频投稿超过5个
log.Info("databus oldUp mid(%d) aid(%d) count(%d)", mid, aid, cnt)
s.oldUpQueue[s.shardingQueueIndex(strconv.FormatInt(mid, 10), s.databusQueueLen)] <- &model.Up{AID: aid, MID: mid}
}
}
}
for _, c := range s.newUpQueue {
close(c)
}
for _, c := range s.oldUpQueue {
close(c)
}
for _, c := range s.mobileUpQueue {
close(c)
}
s.wg.Done()
}

View File

@@ -0,0 +1,259 @@
package service
import (
"context"
"sync"
"time"
"go-common/app/job/main/creative/conf"
"go-common/app/job/main/creative/dao/academy"
"go-common/app/job/main/creative/dao/archive"
"go-common/app/job/main/creative/dao/monitor"
"go-common/app/job/main/creative/dao/newcomer"
"go-common/app/job/main/creative/dao/weeklyhonor"
"go-common/app/job/main/creative/model"
"go-common/library/conf/env"
"go-common/library/queue/databus"
"go-common/library/xstr"
"github.com/robfig/cron"
)
// Service is service.
type Service struct {
c *conf.Config
//arc databus
arcSub *databus.Databus
arcNotifySub *databus.Databus
upPub *databus.Databus
// wait group
wg sync.WaitGroup
// monitor
monitor *monitor.Dao
arc *archive.Dao
arcNotifyMo int64
arcMo int64
// chan for mids
midsChan chan map[int64]int
//aca
aca *academy.Dao
// honDao
honDao *weeklyhonor.Dao
//task databus
newc *newcomer.Dao
taskSub, shareSub, relationSub, statLikeSub *databus.Databus
statShareSub, statCoinSub, statFavSub *databus.Databus
statReplySub, statDMSub, statViewSub, newUpSub *databus.Databus
taskSubQueue []chan *databus.Message
shareSubQueue []chan *model.ShareMsg
relationQueue []chan *model.Relation //用户关注队列
followerQueue []chan *model.Stat //粉丝数队列
newUpQueue []chan *model.Up //新投稿
oldUpQueue []chan *model.Up //进阶任务视频投稿超过5个
mobileUpQueue []chan *model.Up //手机投稿
databusQueueLen int //消费databus 队列长度
statViewQueueLen int //播放消费databus 队列长度
statLikeQueueLen int //点赞消费databus 队列长度
chanSize int //chan 缓冲长度
//单个稿件计数
statViewSubQueue []chan *model.StatView
statLikeSubQueue []chan *model.StatLike
statShareSubQueue []chan *model.StatShare
statCoinSubQueue []chan *model.StatCoin
statFavSubQueue []chan *model.StatFav
statReplySubQueue []chan *model.StatReply
statDMSubQueue []chan *model.StatDM
//db
taskQueue []chan []*model.UserTask
TaskCache []*model.Task
TaskMapCache map[int64]*model.Task
GiftRewardCache map[int8][]*model.GiftReward //gift-reward
//notify
taskNotifyQueue []chan []int64
rewardNotifyQueue []chan []int64
testNotifyMids map[int64]struct{}
}
// New is go-common/app/service/videoup service implementation.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
arcSub: databus.New(c.ArcSub),
arcNotifySub: databus.New(c.ArcNotifySub),
upPub: databus.New(c.UpPub),
monitor: monitor.New(c),
midsChan: make(chan map[int64]int, c.ChanSize),
arc: archive.New(c),
aca: academy.New(c),
honDao: weeklyhonor.New(c),
//task
newc: newcomer.New(c),
taskQueue: make([]chan []*model.UserTask, c.Task.TableConsumeNum),
TaskMapCache: make(map[int64]*model.Task),
GiftRewardCache: make(map[int8][]*model.GiftReward),
//notify
taskNotifyQueue: make([]chan []int64, c.Task.TaskTableConsumeNum),
rewardNotifyQueue: make([]chan []int64, c.Task.RewardTableConsumeNum),
testNotifyMids: make(map[int64]struct{}),
chanSize: c.Task.ChanSize,
}
s.newTaskDatabus()
if c.Consume {
s.wg.Add(1)
go s.arcCanalConsume()
s.wg.Add(1)
go s.arcNotifyCanalConsume()
go s.monitorConsume()
}
if c.HotSwitch {
go s.FlushHot(model.BusinessForArchvie) //计算视频稿件的hot
go s.FlushHot(model.BusinessForArticle) //计算专栏稿件的hot
}
s.taskConsume()
return
}
// InitCron init cron
func (s *Service) InitCron() {
c := cron.New()
if s.c.HonorSwitch {
c.AddFunc(s.c.HonorFlushSpec, s.FlushHonor)
c.AddFunc(s.c.HonorMSGSpec, s.SendMsg)
}
c.Start()
}
func (s *Service) newTaskDatabus() {
s.databusQueueLen = s.c.Task.DatabusQueueLen
s.statViewQueueLen = s.c.Task.StatViewQueueLen //播放
s.statLikeQueueLen = s.c.Task.StatLikeQueueLen //点赞
s.taskSub = databus.New(s.c.TaskSub)
s.shareSub = databus.New(s.c.ShareSub) //分享自己的稿件
s.relationSub = databus.New(s.c.RelationSub)
s.newUpSub = databus.New(s.c.NewUpSub) //新投稿up主
//单个稿件计数
s.statViewSub = databus.New(s.c.StatViewSub)
s.statLikeSub = databus.New(s.c.StatLikeSub)
s.statShareSub = databus.New(s.c.StatShareSub) //计数分享
s.statCoinSub = databus.New(s.c.StatCoinSub)
s.statFavSub = databus.New(s.c.StatFavSub)
s.statReplySub = databus.New(s.c.StatReplySub)
s.statDMSub = databus.New(s.c.StatDMSub)
s.taskSubQueue = make([]chan *databus.Message, s.databusQueueLen) //设置水印、观看创作学院视频、参加激励计划、开通粉丝勋章
s.shareSubQueue = make([]chan *model.ShareMsg, s.databusQueueLen) //分享自己的稿件
s.relationQueue = make([]chan *model.Relation, s.databusQueueLen) //用户关注队列
s.followerQueue = make([]chan *model.Stat, s.databusQueueLen) //粉丝数队列
s.newUpQueue = make([]chan *model.Up, s.databusQueueLen) //新投稿up主
s.oldUpQueue = make([]chan *model.Up, s.databusQueueLen) //进阶任务视频投稿超过5个
s.mobileUpQueue = make([]chan *model.Up, s.databusQueueLen) //进阶任务手机投稿
//单个稿件计数
s.statViewSubQueue = make([]chan *model.StatView, s.statViewQueueLen)
s.statLikeSubQueue = make([]chan *model.StatLike, s.statLikeQueueLen)
s.statShareSubQueue = make([]chan *model.StatShare, s.databusQueueLen)
s.statCoinSubQueue = make([]chan *model.StatCoin, s.databusQueueLen)
s.statFavSubQueue = make([]chan *model.StatFav, s.databusQueueLen)
s.statReplySubQueue = make([]chan *model.StatReply, s.databusQueueLen)
s.statDMSubQueue = make([]chan *model.StatDM, s.databusQueueLen)
}
// TaskClose close task sub.
func (s *Service) TaskClose() {
s.taskSub.Close() //水印、激励、观看创作学院、开通粉丝勋章
s.shareSub.Close() //个人稿件分享
s.relationSub.Close() //关注哔哩哔哩创组中心,新手和进阶粉丝数
s.newUpSub.Close() //投第一个稿件
//计数
s.statViewSub.Close()
s.statLikeSub.Close()
s.statShareSub.Close()
s.statCoinSub.Close()
s.statFavSub.Close()
s.statReplySub.Close()
s.statDMSub.Close()
}
func (s *Service) taskConsume() {
s.loadTasks() //定时缓存所有任务
s.loadGiftRewards() //定时缓存所有奖励
go s.loadProc()
//非实时任务状态变更
s.initTaskQueue()
go s.commitTask()
//过期任务通知
if s.c.Task.SwitchMsgNotify {
mids, _ := xstr.SplitInts(s.c.Task.TestNotifyMids)
for _, mid := range mids {
s.testNotifyMids[mid] = struct{}{} //test mids
}
s.initTaskNotifyQueue()
go s.expireTaskNotify()
//奖励领取通知
s.initRewardNotifyQueue()
go s.rewardReceiveNotify()
}
//实时任务状态变更
if s.c.Task.SwitchHighQPS { //消息qps 较高的消费
s.initStatViewQueue()
s.initStatLikeQueue()
s.wg.Add(2)
go s.statView() //1
go s.statLike() //2
}
if s.c.Task.SwitchDatabus { //消息qps 较少的消费
s.wg.Add(9)
s.initDatabusQueue()
go s.task() //1
go s.share() //2
go s.relation() //3
go s.statShare() //4
go s.statCoin() //5
go s.statFav() //6
go s.statReply() //7
go s.statDM() //8
go s.newUp() //9
}
}
// Ping service
func (s *Service) Ping(c context.Context) (err error) {
return
}
func (s *Service) monitorConsume() {
if s.c.Env != env.DeployEnvProd {
return
}
var arcNotifyMo, arcmo int64
for {
time.Sleep(time.Minute * 1)
if s.arcNotifyMo-arcNotifyMo == 0 {
s.monitor.Send(context.TODO(), s.c.Monitor.UserName, "creative-job did not consume within a minute, moni url"+s.c.Monitor.Moni)
}
arcNotifyMo = s.arcNotifyMo
if s.arcMo-arcmo == 0 {
s.monitor.Send(context.TODO(), s.c.Monitor.UserName, "creative-job did not consume within a minute, moni url"+s.c.Monitor.Moni)
}
arcmo = s.arcMo
}
}
// Close sub.
func (s *Service) Close() {
s.arcSub.Close()
s.arcNotifySub.Close()
s.upPub.Close()
close(s.midsChan)
s.TaskClose() //task databus close
s.wg.Wait()
}

View File

@@ -0,0 +1,40 @@
package service
import (
"flag"
"path/filepath"
"testing"
"time"
"go-common/app/job/main/creative/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/creative-job.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() {})
f(s)
}
}
func Test_Pub(t *testing.T) {
Convey("pub", t, WithService(func(s *Service) {
Convey("pub", func() {
err := s.pub(int64(2089809), 0, 1)
So(err, ShouldBeNil)
})
}))
}

View File

@@ -0,0 +1,655 @@
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)
}
}

View File

@@ -0,0 +1,285 @@
package service
import (
"context"
"strings"
"time"
wkhmdl "go-common/app/interface/main/creative/model/weeklyhonor"
upgrpc "go-common/app/service/main/up/api/v1"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
var (
forbidMidMap = map[int64]struct{}{
24: {},
2: {},
517999: {},
9099524: {},
208259: {},
202466803: {},
40016273: {},
245482023: {},
84089650: {},
31465698: {},
22160843: {},
3098848: {},
39592268: {},
223931175: {},
}
honorMap map[int][]*wkhmdl.HonorWord
)
// SendMsg .
func (s *Service) SendMsg() {
log.Info("Start SendMsg")
var (
c = context.TODO()
lastid int64
size = 1000
)
for {
var (
upActives []*upgrpc.UpActivity
newid int64
err error
)
for i := 0; i < 5; i++ {
upActives, newid, err = s.honDao.UpActivesList(c, lastid, size)
if err == nil {
break
}
}
if err != nil {
log.Error("s.honDao.UpActivesList(%d,%d) error(%v)", lastid, size, err)
break
}
// filter mid
mids, err := s.filterMids(c, upActives)
if err != nil {
continue
}
var errMids []int64
if len(mids) > 0 {
for i := 1; i < 3; i++ {
if errMids, err = s.honDao.SendNotify(c, mids); err != nil {
log.Error("s.honDao.SendNotify(%v) error(%v)", mids, err)
continue
}
break
}
}
go s.infocMsgStat(mids, errMids)
if len(upActives) < size {
break
}
lastid = newid
time.Sleep(time.Second)
}
log.Info("Finish SendMsg")
}
// FlushHonor .
func (s *Service) FlushHonor() {
log.Info("FlushHonor Start")
var (
c = context.TODO()
lastid int64
size = 1000
batchSize = s.c.HonorStep
)
for {
upActives, newid, err := s.honDao.UpActivesList(c, lastid, size)
if err != nil {
log.Error("s.honDao.UpActivesList(%d,%d) error(%v)", lastid, size, err)
time.Sleep(time.Millisecond * 100)
continue
}
var mids []int64
for _, v := range upActives {
mids = append(mids, v.Mid)
}
g := new(errgroup.Group)
var pmids []int64
routines := len(mids)/batchSize + 1
for i := 0; i < routines; i++ {
if i == routines-1 {
pmids = mids[i*batchSize:]
} else {
pmids = mids[i*batchSize : (i+1)*batchSize]
}
t := pmids
g.Go(func() (err error) {
err = s.upsertHonor(c, t)
if err != nil {
log.Error("s.upsertHonor(%v) error(%v)", t, err)
return err
}
return nil
})
}
g.Wait()
if len(mids) < size {
break
}
lastid = newid
}
log.Info("FlushHonor done")
}
func (s *Service) upsertHonor(c context.Context, mids []int64) error {
now := time.Now()
day := int(now.Weekday())
date := now.AddDate(0, 0, -day-1)
saturday := date.Format("20060102")
LOOP:
for _, mid := range mids {
var hls map[int]*wkhmdl.HonorLog
hls, err := s.honDao.HonorLogs(c, mid)
if err != nil {
log.Error("s.honDao.HonorLogs(%d) error(%v)", mid, err)
time.Sleep(time.Millisecond * 100)
continue
}
var lastHid int
for _, v := range hls {
if int64(v.MTime) > date.Unix() && int64(v.MTime) < date.AddDate(0, 0, 7).Unix() {
continue LOOP
}
if int64(v.MTime) < date.Unix() && int64(v.MTime) > date.AddDate(0, 0, -7).Unix() {
lastHid = v.HID
}
}
var hs *wkhmdl.HonorStat
for i := 0; i < 3; i++ {
hs, err = s.honDao.HonorStat(c, mid, saturday)
if err != nil {
log.Error("s.honDao.HonorStat(%d,%v) error(%v)", mid, saturday, err)
continue
}
if hs != nil {
break
}
}
if hs == nil {
log.Error("FlushHonor nil hs mid(%d)", mid)
time.Sleep(time.Millisecond * 100)
continue
}
newHid := hs.GenHonor(mid, lastHid)
affected, err := s.honDao.UpsertCount(c, mid, newHid)
if err != nil || affected == 0 {
log.Error("s.honDao.UpsertCount(%d,%d) affceted(%d) error(%v)", mid, newHid, affected, err)
}
log.Info("FlushHonor mid(%d)", mid)
}
return nil
}
// TestSendMsg .
func (s *Service) TestSendMsg(c context.Context, mids []int64) (err error) {
for i := 1; i < 3; i++ {
if _, err = s.honDao.SendNotify(c, mids); err != nil {
log.Error("s.honDao.SendNotify(%v) error(%v)", mids, err)
continue
} else {
break
}
}
return
}
func (s *Service) filterMids(c context.Context, upActives []*upgrpc.UpActivity) (mids []int64, err error) {
if len(upActives) == 0 {
return
}
var rawMids []int64
for _, v := range upActives {
rawMids = append(rawMids, v.Mid)
}
hls, err := s.honDao.LatestHonorLogs(c, rawMids)
if err != nil {
log.Error("failed to get latest honor logs err(%v)", err)
return
}
midClickMap, err := s.honDao.ClickCounts(c, rawMids)
if err != nil {
log.Error("failed to get honor click count err (%v)", err)
return
}
highLevMap := highLevMidMap(hls)
for _, v := range upActives {
var subState uint8
if subState, err = s.honDao.GetUpSwitch(c, v.Mid); subState == wkhmdl.HonorUnSub {
continue
}
if err != nil {
log.Error("s.honDao.GetUpSwitch mid(%d) err(%v)", v.Mid, err)
}
var cnt int
if cnt, err = s.honDao.UpCount(c, v.Mid); err == nil && cnt == 0 {
continue
}
if highLevMap[v.Mid] {
mids = append(mids, v.Mid)
continue
}
if _, ok := forbidMidMap[v.Mid]; ok {
continue
}
if v.Activity > 3 {
continue
}
if _, ok := midClickMap[v.Mid]; ok {
mids = append(mids, v.Mid)
continue
}
if sunday := wkhmdl.LatestSunday(); s.c.SendEveryWeek || isOddWeek(sunday) {
mids = append(mids, v.Mid)
}
}
return mids, err
}
func isOddWeek(date time.Time) bool {
_, w := date.ISOWeek()
return w%2 != 0
}
func highLevMidMap(hls []*wkhmdl.HonorLog) (res map[int64]bool) {
if honorMap == nil {
honorMap = wkhmdl.HMap()
}
res = make(map[int64]bool)
for _, h := range hls {
hws, ok := honorMap[h.HID]
if !ok || len(hws) == 0 {
continue
}
last := len(hws) - 1
res[h.MID] = isHighLev(hws[last].Priority)
}
return
}
func isHighLev(p string) bool {
return strings.HasPrefix(p, "A") || strings.HasPrefix(p, "R") || strings.HasPrefix(p, "S")
}
func (s *Service) infocMsgStat(mids, errMids []int64) {
errMidsMap := make(map[int64]bool)
for _, mid := range errMids {
errMidsMap[mid] = true
}
for _, mid := range mids {
var success int32
if !errMidsMap[mid] {
success = 1
}
err := s.honDao.HonorInfoc(context.Background(), mid, success)
if err != nil {
log.Error("failed to log honor infoc,mid(%d),success(%d),err(%v)", mid, success, err)
}
}
}

View File

@@ -0,0 +1,172 @@
package service
import (
"context"
"testing"
wkhmdl "go-common/app/interface/main/creative/model/weeklyhonor"
upgrpc "go-common/app/service/main/up/api/v1"
xtime "go-common/library/time"
"github.com/smartystreets/goconvey/convey"
)
var (
mtime = xtime.Time(wkhmdl.LatestSunday().Unix())
c = context.Background()
rawUpActives = []*upgrpc.UpActivity{
{Mid: 1, Activity: 4}, {Mid: 2, Activity: 4}, {Mid: 3, Activity: 4}, {Mid: 4, Activity: 3}, {Mid: 5, Activity: 4}, {Mid: 6, Activity: 1}, {Mid: 7, Activity: 1}, {Mid: 24, Activity: 2},
}
mockHls = []*wkhmdl.HonorLog{
// lev:SSR clicked
{
ID: 1,
MID: 1,
HID: 1,
MTime: mtime,
},
// lev:SR & BlackList clicked
{
ID: 2,
MID: 2,
HID: 9,
MTime: mtime,
},
// lev:R clicked
{
ID: 3,
MID: 3,
HID: 17,
MTime: mtime,
},
// lev:A
{
ID: 4,
MID: 4,
HID: 26,
MTime: mtime,
},
// lev:B clicked
{
ID: 5,
MID: 5,
HID: 35,
MTime: mtime,
},
// lev:C clicked
{
ID: 6,
MID: 6,
HID: 46,
MTime: mtime,
},
// lev:D
{
ID: 7,
MID: 7,
HID: 56,
MTime: mtime,
},
// BlackList clicked
{
ID: 8,
MID: 24,
HID: 50,
},
}
clickMap = map[int64]int32{
1: 1,
2: 1,
3: 1,
5: 1,
6: 1,
24: 1,
}
)
func TestServiceSendMsg(t *testing.T) {
convey.Convey("SendMsg", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
// mock
s.honDao.MockUpActivesList(rawUpActives, 0, nil)
s.honDao.MockUpCount(1, nil)
s.honDao.MockLatestHonorLogs(mockHls, nil)
s.honDao.MockClickCounts(clickMap, nil)
s.honDao.MockSendNotify(nil)
// test
s.SendMsg()
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}
func TestServiceFlushHonor(t *testing.T) {
convey.Convey("FlushHonor", t, func(ctx convey.C) {
ctx.Convey("When everything gose positive", func(ctx convey.C) {
// mock
s.honDao.MockUpActivesList(rawUpActives, 0, nil)
mockStat := wkhmdl.HonorStat{
Play: 100,
PlayLastW: 100,
}
s.honDao.MockHonorStat(&mockStat, nil)
// test
s.FlushHonor()
ctx.Convey("No return values", func(ctx convey.C) {
})
})
})
}
func TestServiceupsertHonor(t *testing.T) {
convey.Convey("upsertHonor", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := s.upsertHonor(c, mids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceTestSendMsg(t *testing.T) {
convey.Convey("TestSendMsg", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := s.TestSendMsg(c, mids)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServicefilterMids(t *testing.T) {
convey.Convey("filterUnActiveMids", t, func(ctx convey.C) {
var (
filteredMids = []int64{1, 2, 3, 4, 6}
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
s.honDao.MockUpCount(1, nil)
s.honDao.MockLatestHonorLogs(mockHls, nil)
s.honDao.MockClickCounts(clickMap, nil)
mids, err := s.filterMids(c, rawUpActives)
ctx.Convey("Then err should be nil.mids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
sunday := wkhmdl.LatestSunday()
if isOddWeek(sunday) {
filteredMids = append(filteredMids, 7)
}
ctx.So(mids, convey.ShouldResemble, filteredMids)
})
})
})
}