go-common/app/interface/main/space/service/dynamic.go
2019-04-22 18:49:16 +08:00

403 lines
11 KiB
Go

package service
import (
"context"
"sort"
"time"
"go-common/app/interface/main/space/model"
arcmdl "go-common/app/service/main/archive/api"
coinmdl "go-common/app/service/main/coin/api"
thumbup "go-common/app/service/main/thumbup/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
)
const (
_dyTypeCoin = -1
_dyTypeLike = -2
_dyTypeMerge = -3
_businessLike = "archive"
_likeVideoCnt = 100
_dyListCnt = 20
_dyDefaultQn = 16
_dyFoldNum = 3
)
var dyTypeFoldMap = map[int]struct{}{_dyTypeCoin: {}, _dyTypeLike: {}, _dyTypeMerge: {}}
// DynamicList get dynamic list.
func (s Service) DynamicList(c context.Context, arg *model.DyListArg) (dyTotal *model.DyTotal, err error) {
var (
list, actList []*model.DyItem
mergeList []*model.DyActItem
dyList *model.DyList
topDy *model.DyCard
dyListTs, lastCoinTs, lastLikeTs int64
topErr, dyErr error
hasCoin, hasDy, hasLike, top bool
)
fp := arg.Pn == 1
repeatDyIDs := make(map[int64]int64, 1)
group, errCtx := errgroup.WithContext(c)
group.Go(func() error {
if topDy, topErr = s.topDynamic(errCtx, arg.Vmid, arg.Qn); topErr == nil && topDy != nil {
top = fp
repeatDyIDs[topDy.Desc.DynamicID] = topDy.Desc.DynamicID
}
return nil
})
group.Go(func() error {
if dyList, dyErr = s.dao.DynamicList(errCtx, arg.Mid, arg.Vmid, arg.DyID, arg.Qn, arg.Pn); dyErr != nil {
log.Error("s.dao.DynamicList(mid:%d,vmid:%d,dyID:%d,qn:%d,pn:%d) error(%+v)", arg.Mid, arg.Vmid, arg.DyID, arg.Qn, arg.Pn, dyErr)
}
return nil
})
group.Go(func() error {
lastCoinTs, lastLikeTs, mergeList = s.actList(errCtx, arg.Mid, arg.Vmid)
return nil
})
if e := group.Wait(); e != nil {
log.Error("DynamicList group.Wait mid(%d) error(%v)", arg.Vmid, e)
}
// rm repeat data
if dyErr == nil && dyList != nil && len(dyList.Cards) > 0 {
for _, v := range dyList.Cards {
if _, ok := repeatDyIDs[v.Desc.DynamicID]; ok {
continue
}
item := new(model.DyResult)
item.FromCard(v)
list = append(list, &model.DyItem{Type: v.Desc.Type, Card: item, Ctime: v.Desc.Timestamp})
}
hasDy = dyList.HasMore == 1
dyListTs = dyList.Cards[len(dyList.Cards)-1].Desc.Timestamp
}
if len(mergeList) > 0 {
hasCoin, hasLike, actList = s.filterActList(c, lastCoinTs, lastLikeTs, arg.LastTime, dyListTs, mergeList, fp)
list = append(list, actList...)
}
sort.Slice(list, func(i, j int) bool { return list[i].Ctime > list[j].Ctime })
dyTotal = new(model.DyTotal)
if top {
topItem := new(model.DyResult)
topItem.FromCard(topDy)
dyTotal.List = append(dyTotal.List, &model.DyItem{Type: topDy.Desc.Type, Top: true, Card: topItem, Ctime: topDy.Desc.Timestamp})
}
dyTotal.HasMore = hasDy || hasCoin || hasLike
dyTotal.List = append(dyTotal.List, list...)
if s.c.Rule.ActFold {
dyTotal.List = foldDyActItem(dyTotal.List)
}
return
}
func (s *Service) actList(c context.Context, mid, vmid int64) (lastCoinTs, lastLikeTs int64, mergeList []*model.DyActItem) {
var (
coinList, likeList, preList []*model.DyActItem
coinErr, likeErr error
coinPcy, likePcy bool
)
group, errCtx := errgroup.WithContext(c)
privacy := s.privacy(c, vmid)
if value, ok := privacy[model.PcyCoinVideo]; ok && value != _defaultPrivacy {
coinPcy = true
}
if value, ok := privacy[model.PcyLikeVideo]; ok && value != _defaultPrivacy {
likePcy = true
}
// coin video
if mid == vmid || !coinPcy {
group.Go(func() error {
coinList, coinErr = s.coinVideos(errCtx, vmid, coinPcy)
return nil
})
}
// like video
if mid == vmid || !likePcy {
group.Go(func() error {
likeList, likeErr = s.likeVideos(errCtx, vmid, likePcy)
return nil
})
}
group.Wait()
if coinErr == nil {
if l := len(coinList); l > 0 {
preList = append(preList, coinList...)
lastCoinTs = coinList[l-1].ActionTime
}
}
if likeErr == nil {
if l := len(likeList); l > 0 {
preList = append(preList, likeList...)
lastLikeTs = likeList[l-1].ActionTime
}
}
if len(preList) == 0 {
return
}
sort.Slice(preList, func(i, j int) bool { return preList[i].ActionTime > preList[j].ActionTime })
if s.c.Rule.Merge {
mergeList = mergeDyActItem(preList)
} else {
mergeList = preList
}
return
}
func (s *Service) filterActList(c context.Context, lastCoinTs, lastLikeTs, lastTime, dyListTs int64, mergeList []*model.DyActItem, fp bool) (hasCoin, hasLike bool, list []*model.DyItem) {
var (
actList []*model.DyActItem
actAids []int64
coinTs, likeTs int64
)
for _, v := range mergeList {
if dyListTs == 0 && len(actList) >= _dyListCnt {
lastActTs := actList[len(actList)-1].ActionTime
penultActTs := actList[len(actList)-2].ActionTime
y1, m1, d1 := time.Unix(lastActTs, 0).Date()
y2, m2, d2 := time.Unix(penultActTs, 0).Date()
if d1 != d2 || m1 != m2 || y1 != y2 {
actList = actList[:len(actList)-1]
break
}
}
if fp {
if dyListTs > 0 {
if v.ActionTime >= dyListTs {
actList = append(actList, v)
actAids = append(actAids, v.Aid)
}
} else {
actList = append(actList, v)
actAids = append(actAids, v.Aid)
}
} else {
if dyListTs > 0 {
if v.ActionTime >= dyListTs && v.ActionTime < lastTime {
actList = append(actList, v)
actAids = append(actAids, v.Aid)
}
} else {
if v.ActionTime < lastTime {
actList = append(actList, v)
actAids = append(actAids, v.Aid)
}
}
}
switch v.Type {
case _dyTypeCoin:
coinTs = v.ActionTime
case _dyTypeLike:
likeTs = v.ActionTime
}
}
if coinTs > lastCoinTs {
hasCoin = true
}
if likeTs > lastLikeTs {
hasLike = true
}
if arcsReply, err := s.arcClient.Arcs(c, &arcmdl.ArcsRequest{Aids: actAids}); err != nil {
log.Error("DynamicList s.arcClient.Arcs(%v) error(%v)", actAids, err)
} else {
for _, v := range actList {
if arc, ok := arcsReply.Arcs[v.Aid]; ok && arc != nil && arc.IsNormal() {
video := new(model.VideoItem)
video.FromArchive(arc)
video.ActionTime = v.ActionTime
list = append(list, &model.DyItem{Type: v.Type, Archive: video, Ctime: v.ActionTime, Privacy: v.Privacy})
}
}
}
return
}
func mergeDyActItem(preList []*model.DyActItem) (mergeList []*model.DyActItem) {
type privacy struct {
Num int
Coin bool
Like bool
}
aidNumMap := make(map[int64]*privacy, len(preList))
aidExist := make(map[int64]struct{}, len(preList))
for _, v := range preList {
if _, exist := aidNumMap[v.Aid]; !exist {
aidNumMap[v.Aid] = new(privacy)
}
aidNumMap[v.Aid].Num++
switch v.Type {
case _dyTypeCoin:
aidNumMap[v.Aid].Coin = v.Privacy
case _dyTypeLike:
aidNumMap[v.Aid].Like = v.Privacy
}
}
for _, v := range preList {
num := aidNumMap[v.Aid].Num
if num > 1 {
if _, ok := aidExist[v.Aid]; !ok {
v.Type = _dyTypeMerge
v.Privacy = aidNumMap[v.Aid].Coin && aidNumMap[v.Aid].Like
mergeList = append(mergeList, v)
}
aidExist[v.Aid] = struct{}{}
} else {
mergeList = append(mergeList, v)
}
}
return
}
func foldDyActItem(list []*model.DyItem) (foldList []*model.DyItem) {
l := len(list)
if l == 0 {
foldList = make([]*model.DyItem, 0)
return
}
if l < _dyFoldNum {
foldList = list
return
}
var preCk bool
for index, v := range list {
if index == 0 {
foldList = append(foldList, v)
continue
}
last := index == l-1
if index >= _dyFoldNum-1 {
_, tpCheck := dyTypeFoldMap[v.Type]
y1, m1, d1 := time.Unix(v.Ctime, 0).Date()
_, preTpCheck := dyTypeFoldMap[list[index-1].Type]
y2, m2, d2 := time.Unix(list[index-1].Ctime, 0).Date()
_, check := dyTypeFoldMap[list[index-2].Type]
y3, m3, d3 := time.Unix(list[index-2].Ctime, 0).Date()
ck := tpCheck && preTpCheck && check && (y1 == y2 && m1 == m2 && d1 == d2) && (y1 == y3 && m1 == m3 && d1 == d3)
// append pre item to fold if ck or preCk
if ck || preCk {
foldList[len(foldList)-1].Fold = append(foldList[len(foldList)-1].Fold, list[index-1])
if last {
foldList[len(foldList)-1].Fold = append(foldList[len(foldList)-1].Fold, v)
}
} else {
foldList = append(foldList, list[index-1])
if last {
foldList = append(foldList, v)
}
}
preCk = ck
}
}
return foldList
}
func (s *Service) coinVideos(c context.Context, vmid int64, pcy bool) (list []*model.DyActItem, err error) {
var (
coinReply *coinmdl.ListReply
aids []int64
)
if coinReply, err = s.coinClient.List(c, &coinmdl.ListReq{Mid: vmid, Business: _businessCoin, Ts: time.Now().Unix()}); err != nil {
log.Error("s.coinClient.List(%d) error(%v)", vmid, err)
return
}
existArcs := make(map[int64]*coinmdl.ModelList, len(coinReply.List))
for _, v := range coinReply.List {
if len(aids) > _coinVideoLimit {
break
}
if _, ok := existArcs[v.Aid]; ok {
continue
}
if v.Aid > 0 {
list = append(list, &model.DyActItem{Aid: v.Aid, Type: _dyTypeCoin, ActionTime: v.Ts, Privacy: pcy})
existArcs[v.Aid] = v
}
}
return
}
func (s *Service) likeVideos(c context.Context, mid int64, pcy bool) (list []*model.DyActItem, err error) {
var (
likes *thumbup.UserTotalLike
ip = metadata.String(c, metadata.RemoteIP)
)
arg := &thumbup.ArgUserLikes{Mid: mid, Business: _businessLike, Pn: 1, Ps: _likeVideoCnt, RealIP: ip}
if likes, err = s.thumbup.UserTotalLike(c, arg); err != nil {
log.Error("s.thumbup.UserTotalLike(%d) error(%v)", mid, err)
return
}
if likes != nil {
for _, v := range likes.List {
if v.MessageID > 0 {
list = append(list, &model.DyActItem{Aid: v.MessageID, Type: _dyTypeLike, ActionTime: int64(v.Time), Privacy: pcy})
}
}
}
return
}
// topDynamic get top dynamic.
func (s *Service) topDynamic(c context.Context, mid int64, qn int) (res *model.DyCard, err error) {
var (
dyID int64
)
if dyID, err = s.dao.TopDynamic(c, mid); err != nil {
return
}
if dyID == 0 {
err = ecode.NothingFound
return
}
if res, err = s.dao.Dynamic(c, mid, dyID, qn); err != nil || res == nil {
log.Error("Dynamic s.dao.Dynamic mid(%d) dyID(%d) error(%v)", mid, dyID, err)
err = ecode.NothingFound
}
return
}
// SetTopDynamic set top dynamic.
func (s *Service) SetTopDynamic(c context.Context, mid, dynamicID int64) (err error) {
var (
dynamic *model.DyCard
preDyID int64
)
if dynamic, err = s.dao.Dynamic(c, mid, dynamicID, _dyDefaultQn); err != nil || dynamic == nil {
log.Error("SetTopDynamic s.dao.Dynamic(%d) error(%v)", dynamicID, err)
return
}
if dynamic.Desc.UID != mid {
err = ecode.RequestErr
return
}
if preDyID, err = s.dao.TopDynamic(c, mid); err != nil {
return
}
if preDyID == dynamicID {
err = ecode.NotModified
return
}
if err = s.dao.AddTopDynamic(c, mid, dynamicID); err == nil {
s.dao.AddCacheTopDynamic(c, mid, dynamicID)
}
return
}
// CancelTopDynamic cancel top dynamic.
func (s *Service) CancelTopDynamic(c context.Context, mid int64, now time.Time) (err error) {
var dyID int64
if dyID, err = s.dao.TopDynamic(c, mid); err != nil {
return
}
if dyID == 0 {
err = ecode.RequestErr
return
}
if err = s.dao.DelTopDynamic(c, mid, now); err == nil {
s.dao.AddCacheTopDynamic(c, mid, -1)
}
return
}