bilibili-backup/app/interface/main/app-intl/service/feed/index.go
2019-04-22 02:59:20 +00:00

439 lines
12 KiB
Go

package feed
import (
"context"
"encoding/json"
"time"
cdm "go-common/app/interface/main/app-card/model"
"go-common/app/interface/main/app-card/model/card"
"go-common/app/interface/main/app-card/model/card/ai"
"go-common/app/interface/main/app-card/model/card/banner"
"go-common/app/interface/main/app-card/model/card/cm"
"go-common/app/interface/main/app-card/model/card/operate"
"go-common/app/interface/main/app-intl/model"
"go-common/app/interface/main/app-intl/model/feed"
tag "go-common/app/interface/main/tag/model"
account "go-common/app/service/main/account/model"
"go-common/app/service/main/archive/model/archive"
locmdl "go-common/app/service/main/location/model"
relation "go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
"go-common/library/text/translate/chinese"
)
// Index is.
func (s *Service) Index(c context.Context, buvid string, mid int64, plat int8, param *feed.IndexParam, now time.Time, style int) (is []card.Handler, userFeature json.RawMessage, isRcmd, newUser bool, code int, autoPlay, clean int8, autoPlayInfoc string, err error) {
var (
rs []*ai.Item
adm map[int]*cm.AdInfo
adAidm map[int64]struct{}
banners []*banner.Banner
version string
blackAidm map[int64]struct{}
adInfom map[int]*cm.AdInfo
follow *operate.Card
ip = metadata.String(c, metadata.RemoteIP)
info *locmdl.Info
isTW = model.TWLocale(param.Locale)
)
// 国际版不做abtest
clean = 0
autoPlay = 2
group := s.group(mid, buvid)
if info, err = s.loc.Info(c, ip); err != nil {
log.Warn("s.loc.Info(%v) error(%v)", ip, err)
err = nil
}
if !s.c.Feed.Index.Abnormal {
g, ctx := errgroup.WithContext(c)
g.Go(func() error {
rs, userFeature, isRcmd, newUser, code = s.indexRcmd(ctx, plat, param.Build, buvid, mid, group, param.LoginEvent, info, param.Interest, param.Network, style, param.Column, param.Flush, autoPlayInfoc, now)
if isTW {
for _, r := range rs {
if r.RcmdReason != nil {
r.RcmdReason.Content = chinese.Convert(ctx, r.RcmdReason.Content)
}
}
}
return nil
})
g.Go(func() (err error) {
if blackAidm, err = s.BlackList(ctx, mid); err != nil {
log.Error("%+v", err)
err = nil
}
return
})
if err = g.Wait(); err != nil {
return
}
rs, adInfom = s.mergeItem(c, mid, rs, adm, adAidm, banners, version, blackAidm, plat, follow)
} else {
count := s.indexCount(plat)
rs = s.recommendCache(count)
log.Warn("feed index show disaster recovery data len(%d)", len(is))
}
is, isRcmd, err = s.dealItem(c, param.Column, mid, buvid, plat, param.Build, rs, isRcmd, param.MobiApp, param.Device, param.Network, param.OpenEvent, param.AdExtra, param.Qn, param.Fnver, param.Fnval, follow, isTW, now)
s.dealAdLoc(is, param, adInfom, now)
return
}
// indexRcmd is.
func (s *Service) indexRcmd(c context.Context, plat int8, build int, buvid string, mid int64, group int, loginEvent int, info *locmdl.Info, interest, network string, style int, column cdm.ColumnStatus, flush int, autoPlay string, now time.Time) (is []*ai.Item, userFeature json.RawMessage, isRcmd, newUser bool, code int) {
count := s.indexCount(plat)
if buvid != "" || mid != 0 {
var (
err error
zoneID int64
)
if info != nil {
zoneID = info.ZoneID
}
if is, userFeature, code, newUser, err = s.rcmd.Recommend(c, plat, buvid, mid, build, loginEvent, zoneID, group, interest, network, style, column, flush, autoPlay, now); err != nil {
log.Error("%+v", err)
} else if len(is) != 0 {
isRcmd = true
}
var fromCache bool
if len(is) == 0 && mid != 0 && !ecode.ServiceUnavailable.Equal(err) {
if is, err = s.indexCache(c, mid, count); err != nil {
log.Error("%+v", err)
}
if len(is) != 0 {
s.pHit.Incr("index_cache")
} else {
s.pMiss.Incr("index_cache")
}
fromCache = true
}
if len(is) == 0 || (fromCache && len(is) < count) {
is = s.recommendCache(count)
}
} else {
is = s.recommendCache(count)
}
return
}
// mergeItem is.
func (s *Service) mergeItem(c context.Context, mid int64, rs []*ai.Item, adm map[int]*cm.AdInfo, adAidm map[int64]struct{}, banners []*banner.Banner, version string, blackAids map[int64]struct{}, plat int8, follow *operate.Card) (is []*ai.Item, adInfom map[int]*cm.AdInfo) {
if len(rs) == 0 {
return
}
if len(banners) != 0 {
rs = append([]*ai.Item{{Goto: model.GotoBanner, Banners: banners, Version: version}}, rs...)
}
is = make([]*ai.Item, 0, len(rs)+len(adm))
adInfom = make(map[int]*cm.AdInfo, len(adm))
for _, r := range rs {
if r.Goto == model.GotoAv {
if _, ok := blackAids[r.ID]; ok {
continue
} else if _, ok := s.blackCache[r.ID]; ok {
continue
}
if _, ok := adAidm[r.ID]; ok {
continue
}
} else if r.Goto == model.GotoBanner && len(is) != 0 {
// banner 必须在第一位
continue
} else if r.Goto == model.GotoLogin && mid != 0 {
continue
}
is = append(is, r)
}
return
}
// dealAdLoc is.
func (*Service) dealAdLoc(is []card.Handler, param *feed.IndexParam, adInfom map[int]*cm.AdInfo, now time.Time) {
il := len(is)
if il == 0 {
return
}
if param.Idx < 1 {
param.Idx = now.Unix()
}
for i, h := range is {
if param.Pull {
h.Get().Idx = param.Idx + int64(il-i)
} else {
h.Get().Idx = param.Idx - int64(i+1)
}
if ad, ok := adInfom[i]; ok {
h.Get().AdInfo = ad
} else if h.Get().AdInfo != nil {
h.Get().AdInfo.CardIndex = i
}
}
}
// dealItem is.
func (s *Service) dealItem(c context.Context, column cdm.ColumnStatus, mid int64, buvid string, plat int8, build int, rs []*ai.Item, isRcmd bool, mobiApp, device, network, openEvent, adExtra string, qn, fnver, fnval int, follow *operate.Card, isTW bool, now time.Time) (is []card.Handler, isAI bool, err error) {
if len(rs) == 0 {
is = []card.Handler{}
return
}
var (
aids, tids []int64
upIDs, avUpIDs, rmUpIDs, mtUpIDs []int64
am map[int64]*archive.ArchiveWithPlayer
tagm map[int64]*tag.Tag
rank *operate.Card
cardm map[int64]*account.Card
statm map[int64]*relation.Stat
isAtten map[int64]int8
arcOK bool
)
followm := map[int64]*operate.Card{}
isAI = isRcmd
for _, r := range rs {
if r == nil {
continue
}
if isTW && r.RcmdReason != nil {
r.RcmdReason.Content = chinese.Convert(c, r.RcmdReason.Content)
}
switch r.Goto {
case model.GotoAv, model.GotoPlayer, model.GotoUpRcmdAv:
if r.ID != 0 {
aids = append(aids, r.ID)
}
if r.Tid != 0 {
tids = append(tids, r.Tid)
}
case model.GotoRank:
os, aid := s.RankCard(plat)
rank = &operate.Card{}
rank.FromRank(os)
aids = append(aids, aid...)
case model.GotoChannelRcmd:
cardm, aid, tid := s.channelRcmdCard(c, r.ID)
for id, card := range cardm {
followm[id] = card
}
aids = append(aids, aid...)
tids = append(tids, tid...)
}
}
g, ctx := errgroup.WithContext(c)
if len(aids) != 0 {
g.Go(func() (err error) {
if am, err = s.ArchivesWithPlayer(ctx, aids, qn, mobiApp, fnver, fnval); err != nil {
return
}
arcOK = true
for _, a := range am {
avUpIDs = append(avUpIDs, a.Author.Mid)
if isTW {
out := chinese.Converts(ctx, a.Title, a.Desc, a.TypeName)
a.Title = out[a.Title]
a.Desc = out[a.Desc]
a.TypeName = out[a.TypeName]
}
}
return
})
}
if len(tids) != 0 {
g.Go(func() (err error) {
if tagm, err = s.tg.InfoByIDs(ctx, mid, tids); err != nil {
log.Error("%+v", err)
err = nil
}
return
})
}
if err = g.Wait(); err != nil {
log.Error("%+v", err)
if isRcmd {
count := s.indexCount(plat)
rs = s.recommendCache(count)
}
} else {
upIDs = append(upIDs, avUpIDs...)
upIDs = append(upIDs, rmUpIDs...)
upIDs = append(upIDs, mtUpIDs...)
g, ctx = errgroup.WithContext(c)
if len(upIDs) != 0 {
g.Go(func() (err error) {
if cardm, err = s.acc.Cards3(ctx, upIDs); err != nil {
log.Error("%+v", err)
err = nil
}
return
})
g.Go(func() (err error) {
if statm, err = s.rel.Stats(ctx, upIDs); err != nil {
log.Error("%+v", err)
err = nil
}
return
})
if mid != 0 {
g.Go(func() error {
isAtten = s.acc.IsAttention(ctx, upIDs, mid)
return nil
})
}
}
g.Wait()
}
isAI = isAI && arcOK
var cardTotal int
is = make([]card.Handler, 0, len(rs))
for _, r := range rs {
if r == nil {
continue
}
var (
main interface{}
cardType cdm.CardType
)
op := &operate.Card{}
op.From(cdm.CardGt(r.Goto), r.ID, r.Tid, plat, build)
h := card.Handle(plat, cdm.CardGt(r.Goto), cardType, column, r, tagm, isAtten, statm, cardm)
if h == nil {
continue
}
switch r.Goto {
case model.GotoAv, model.GotoPlayer, model.GotoUpRcmdAv:
if !arcOK {
if r.Archive != nil {
if isTW {
out := chinese.Converts(c, r.Archive.Title, r.Archive.Desc, r.Archive.TypeName, r.Archive.Author.Name)
r.Archive.Title = out[r.Archive.Title]
r.Archive.Desc = out[r.Archive.Desc]
r.Archive.TypeName = out[r.Archive.TypeName]
}
am = map[int64]*archive.ArchiveWithPlayer{r.Archive.Aid: {Archive3: r.Archive}}
}
if r.Tag != nil {
tagm = map[int64]*tag.Tag{r.Tag.ID: r.Tag}
op.Tid = r.Tag.ID
}
}
if a, ok := am[r.ID]; ok && (a.AttrVal(archive.AttrBitOverseaLock) == 0 || !model.IsOverseas(plat)) {
main = am
op.TrackID = r.TrackID
}
case model.GotoRank:
main = map[cdm.Gt]interface{}{cdm.GotoAv: am}
op = rank
case model.GotoChannelRcmd:
main = am
case model.GotoLogin:
op.FromLogin(r.ID)
default:
log.Warn("unexpected goto(%s) %+v", r.Goto, r)
continue
}
h.From(main, op)
// 卡片不正常要continue
if !h.Get().Right {
continue
}
is, cardTotal = s.appendItem(plat, is, h, column, cardTotal)
}
// 双列末尾卡片去空窗
if !model.IsIPad(plat) {
if cdm.Columnm[column] == cdm.ColumnSvrDouble {
is = is[:len(is)-cardTotal%2]
}
} else {
// 复杂的ipad去空窗逻辑
if cardTotal%4 == 3 {
if is[len(is)-2].Get().CardLen == 2 {
is = is[:len(is)-2]
} else {
is = is[:len(is)-3]
}
} else if cardTotal%4 == 2 {
if is[len(is)-1].Get().CardLen == 2 {
is = is[:len(is)-1]
} else {
is = is[:len(is)-2]
}
} else if cardTotal%4 == 1 {
is = is[:len(is)-1]
}
}
if len(is) == 0 {
is = []card.Handler{}
return
}
return
}
// appendItem is.
func (s *Service) appendItem(plat int8, rs []card.Handler, h card.Handler, column cdm.ColumnStatus, cardTotal int) (is []card.Handler, total int) {
h.Get().ThreePointFrom()
// 国际版暂不支持稿件反馈
if h.Get().ThreePoint != nil {
h.Get().ThreePoint.Feedbacks = nil
}
if !model.IsIPad(plat) {
// 双列大小卡换位去空窗
if cdm.Columnm[column] == cdm.ColumnSvrDouble {
// 通栏卡
if h.Get().CardLen == 0 {
if cardTotal%2 == 1 {
is = card.SwapTwoItem(rs, h)
} else {
is = append(rs, h)
}
} else {
is = append(rs, h)
}
} else {
is = append(rs, h)
}
} else {
// ipad卡片不展示标签
h.Get().DescButton = nil
// ipad大小卡换位去空窗
if h.Get().CardLen == 0 {
// 通栏卡
if cardTotal%4 == 3 {
is = card.SwapFourItem(rs, h)
} else if cardTotal%4 == 2 {
is = card.SwapThreeItem(rs, h)
} else if cardTotal%4 == 1 {
is = card.SwapTwoItem(rs, h)
} else {
is = append(rs, h)
}
} else if h.Get().CardLen == 2 {
// 半栏卡
if cardTotal%4 == 3 {
is = card.SwapTwoItem(rs, h)
} else if cardTotal%4 == 2 {
is = append(rs, h)
} else if cardTotal%4 == 1 {
is = card.SwapTwoItem(rs, h)
} else {
is = append(rs, h)
}
} else {
is = append(rs, h)
}
}
total = cardTotal + h.Get().CardLen
return
}
// indexCount is.
func (s *Service) indexCount(plat int8) (count int) {
if plat == model.PlatIPad {
count = s.c.Feed.Index.IPadCount
} else {
count = s.c.Feed.Index.Count
}
return
}