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

636 lines
15 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 channel
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"time"
cdm "go-common/app/interface/main/app-card/model"
cardm "go-common/app/interface/main/app-card/model/card"
"go-common/app/interface/main/app-card/model/card/operate"
"go-common/app/interface/main/app-channel/model"
"go-common/app/interface/main/app-channel/model/card"
"go-common/app/interface/main/app-channel/model/channel"
"go-common/app/interface/main/app-channel/model/tab"
tag "go-common/app/interface/main/tag/model"
locmdl "go-common/app/service/main/location/model"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/sync/errgroup"
"github.com/dgryski/go-farm"
)
const (
_initRegionKey = "region_key_%d_%v"
_initlanguage = "hans"
_initVersion = "region_version"
_regionRepeat = "r_%d_%d"
_maxAtten = 10 //展示最多10个我的订阅
)
var (
_tabList = []*channel.TabList{
&channel.TabList{
Name: "推荐",
URI: "bilibili://pegasus/channel/feed/%d",
TabID: "multiple",
},
&channel.TabList{
Name: "话题",
URI: "bilibili://following/topic_detail?id=%d&name=%s",
TabID: "topic",
},
}
)
// Tab channel tab
func (s *Service) Tab(c context.Context, tid, mid int64, tname string, plat int8) (res *channel.Tab, err error) {
var (
t *tag.ChannelDetail
)
if t, err = s.tg.ChannelDetail(c, mid, tid, tname, s.isOverseas(plat)); err != nil || t == nil {
log.Error("s.tag.ChannelDetail(%d, %d, %v) error(%v)", mid, tid, tname, err)
return
}
res = &channel.Tab{}
res.SimilarTagChange(t)
res.TabList = s.tablist(t)
return
}
//SubscribeAdd subscribe add
func (s *Service) SubscribeAdd(c context.Context, mid, id int64, now time.Time) (err error) {
if err = s.tg.SubscribeAdd(c, mid, id, now); err != nil {
log.Error("s.tg.SubscribeAdd(%d,%d) error(%v)", mid, id, err)
return
}
return
}
//SubscribeCancel subscribe channel
func (s *Service) SubscribeCancel(c context.Context, mid, id int64, now time.Time) (err error) {
if err = s.tg.SubscribeCancel(c, mid, id, now); err != nil {
log.Error("s.tg.SubscribeCancel(%d,%d) error(%v)", mid, id, err)
return
}
return
}
// SubscribeUpdate subscribe update
func (s *Service) SubscribeUpdate(c context.Context, mid int64, ids string) (err error) {
if err = s.tg.SubscribeUpdate(c, mid, ids); err != nil {
log.Error("s.tg.SubscribeUpdate(%d,%s) error(%v)", mid, ids, err)
return
}
return
}
// List 频道tab页
func (s *Service) List(c context.Context, mid int64, plat int8, build, limit int, ver, mobiApp, device, lang string) (res *channel.List, err error) {
var (
rec, atten []*channel.Channel
top, bottom []*channel.Region
max = 3
)
g, _ := errgroup.WithContext(c)
//获取推荐的三个频道
g.Go(func() (err error) {
rec, err = s.Recommend(c, mid, plat)
if err != nil {
log.Error("%+v", err)
err = nil
}
return
})
//获取我的订阅
if mid > 0 {
g.Go(func() (err error) {
atten, err = s.Subscribe(c, mid, limit)
if err != nil {
log.Error("%+v", err)
err = nil
}
return
})
}
//获取分区
g.Go(func() (err error) {
top, bottom, _, err = s.RegionList(c, plat, build, mobiApp, device, lang)
if err != nil {
log.Error("%+v", err)
err = nil
}
return
})
g.Wait()
if tl := len(rec); tl < max {
if last := max - tl; len(atten) > last {
rec = append(rec, atten[:last]...)
} else {
rec = append(rec, atten...)
}
} else {
rec = rec[:max]
}
res = &channel.List{
RegionTop: top,
RegionBottom: bottom,
}
if isAudit := s.auditList(mobiApp, plat, build); !isAudit {
res.RecChannel = rec
res.AttenChannel = atten
}
res.Ver = s.hash(res)
return
}
// Recommend 推荐
func (s *Service) Recommend(c context.Context, mid int64, plat int8) (res []*channel.Channel, err error) {
list, err := s.tg.Discover(c, mid, s.isOverseas(plat))
if err != nil {
log.Error("%+v", err)
return
}
for _, chann := range list {
item := &channel.Channel{
ID: chann.ID,
Name: chann.Name,
Cover: chann.Cover,
IsAtten: chann.Attention,
Atten: chann.Sub,
}
res = append(res, item)
}
return
}
//Subscribe 我订阅的tag standard放前面用户自定义custom放后面
func (s *Service) Subscribe(c context.Context, mid int64, limit int) (res []*channel.Channel, err error) {
var (
tinfo []*tag.TagInfo
)
list, err := s.tg.Subscribe(c, mid)
if err != nil {
log.Error("%+v", err)
return
}
tinfo = list.Standard
tinfo = append(tinfo, list.Custom...)
for _, chann := range tinfo {
item := &channel.Channel{
ID: chann.ID,
Name: chann.Name,
Cover: chann.Cover,
Atten: chann.Sub,
IsAtten: chann.Attention,
Content: chann.Content,
}
res = append(res, item)
}
if len(res) > limit && limit > 0 {
res = res[:limit]
} else if len(res) == 0 {
res = []*channel.Channel{}
}
return
}
// Discover 发现频道页推荐走recommend接口有分类的揍list接口
func (s *Service) Discover(c context.Context, id, mid int64, plat int8) (res []*channel.Channel, err error) {
var (
list []*tag.Channel
)
if id > 0 {
list, err = s.tg.ListByCategory(c, id, mid, s.isOverseas(plat))
if err != nil {
log.Error("%+v", err)
return
}
} else {
list, err = s.tg.Recommend(c, mid, s.isOverseas(plat))
if err != nil {
log.Error("%+v", err)
return
}
}
if len(list) == 0 {
res = []*channel.Channel{}
return
}
for _, chann := range list {
item := &channel.Channel{
ID: chann.ID,
Name: chann.Name,
Cover: chann.Cover,
Atten: chann.Sub,
IsAtten: chann.Attention,
Content: chann.Content,
}
res = append(res, item)
}
return
}
// Category 频道分类
func (s *Service) Category(c context.Context, plat int8) (res []*channel.Category, err error) {
category, err := s.tg.Category(c, s.isOverseas(plat))
if err != nil {
log.Error("%+v", err)
return
}
res = append(res, &channel.Category{
ID: 0,
Name: "推荐",
})
for _, cat := range category {
item := &channel.Category{
ID: cat.ID,
Name: cat.Name,
}
res = append(res, item)
}
return
}
// RegionList 分区信息
func (s *Service) RegionList(c context.Context, plat int8, build int, mobiApp, device, lang string) (regionTop, regionBottom, regions []*channel.Region, err error) {
var (
hantlanguage = "hant"
)
if ok := model.IsOverseas(plat); ok && lang != _initlanguage && lang != hantlanguage {
lang = hantlanguage
} else if lang == "" {
lang = _initlanguage
}
var (
rs = s.cachelist[fmt.Sprintf(_initRegionKey, plat, lang)]
// maxTop = 8
ridtmp = map[string]struct{}{}
pids []string
auths map[string]*locmdl.Auth
ip = metadata.String(c, metadata.RemoteIP)
)
regionTop = []*channel.Region{}
regionBottom = []*channel.Region{}
regions = []*channel.Region{}
for _, rtmp := range rs {
if rtmp.ReID != 0 { //过滤二级分区
continue
}
if rtmp.Area != "" {
pids = append(pids, rtmp.Area)
}
}
if len(pids) > 0 {
auths, _ = s.loc.AuthPIDs(c, strings.Join(pids, ","), ip)
}
LOOP:
for _, rtmp := range rs {
r := &channel.Region{}
*r = *rtmp
if r.ReID != 0 { //过滤二级分区
continue
}
var tmpl, limitshow bool
if limit, ok := s.limitCache[r.ID]; ok {
for i, l := range s.limitCache[r.ID] {
if i+1 <= len(limit)-1 {
if ((l.Condition == "gt" && limit[i+1].Condition == "lt") && (l.Build < limit[i+1].Build)) ||
((l.Condition == "lt" && limit[i+1].Condition == "gt") && (l.Build > limit[i+1].Build)) {
if (l.Condition == "gt" && limit[i+1].Condition == "lt") &&
(build > l.Build && build < limit[i+1].Build) {
break
} else if (l.Condition == "lt" && limit[i+1].Condition == "gt") &&
(build < l.Build && build > limit[i+1].Build) {
break
} else {
tmpl = true
continue
}
}
}
if tmpl {
if i == len(limit)-1 {
limitshow = true
break
// continue LOOP
}
tmpl = false
continue
}
if model.InvalidBuild(build, l.Build, l.Condition) {
limitshow = true
continue
// continue LOOP
} else {
limitshow = false
break
}
}
}
if limitshow {
continue LOOP
}
if r.RID == 65539 {
if model.IsIOS(plat) {
r.URI = fmt.Sprintf("%s?from=category", r.URI)
} else {
r.URI = fmt.Sprintf("%s?sourceFrom=541", r.URI)
}
}
if auth, ok := auths[r.Area]; ok && auth.Play == locmdl.Forbidden {
log.Warn("s.invalid area(%v) ip(%v) error(%v)", r.Area, ip, err)
continue
}
if isAudit := s.auditRegion(mobiApp, plat, build, r.RID); isAudit {
continue
}
config, ok := s.configCache[r.ID]
if !ok {
continue
}
key := fmt.Sprintf(_regionRepeat, r.RID, r.ReID)
if _, ok := ridtmp[key]; !ok {
ridtmp[key] = struct{}{}
} else {
continue
}
for _, conf := range config {
if conf.ScenesID == 1 /*&& len(regionTop) < maxTop*/ {
regionTop = append(regionTop, r)
regions = append(regions, r)
} else if conf.ScenesID == 0 {
regionBottom = append(regionBottom, r)
regions = append(regions, r)
}
}
}
return
}
func (s *Service) hash(v *channel.List) string {
bs, err := json.Marshal(v)
if err != nil {
log.Error("json.Marshal error(%v)", err)
return _initVersion
}
return strconv.FormatUint(farm.Hash64(bs), 10)
}
func (s *Service) loadRegionlist() {
res, err := s.rg.AllList(context.TODO())
if err != nil {
log.Error("s.dao.All error(%v)", err)
return
}
tmp := map[string][]*channel.Region{}
for _, v := range res {
key := fmt.Sprintf(_initRegionKey, v.Plat, v.Language)
tmp[key] = append(tmp[key], v)
}
if len(tmp) > 0 {
s.cachelist = tmp
}
log.Info("region list cacheproc success")
limit, err := s.rg.Limit(context.TODO())
if err != nil {
log.Error("s.dao.limit error(%v)", err)
return
}
s.limitCache = limit
log.Info("region limit cacheproc success")
config, err := s.rg.Config(context.TODO())
if err != nil {
log.Error("s.dao.Config error(%v)", err)
return
}
s.configCache = config
log.Info("region config cacheproc success")
}
// Square 频道广场页
func (s *Service) Square(c context.Context, mid int64, plat int8, build int, loginEvent int32, mobiApp, device, lang, buvid string) (res *channel.Square, err error) {
res = new(channel.Square)
var (
squ *tag.ChannelSquare
regions []*channel.Region
oidNum = 2
)
isAudit := s.auditList(mobiApp, plat, build)
eg := errgroup.Group{}
//获取分区
eg.Go(func() (err error) {
_, _, regions, err = s.RegionList(c, plat, build, mobiApp, device, lang)
if err != nil {
log.Error("%+v", err)
err = nil
}
res.Region = regions
return
})
if !isAudit {
//获取推荐频道
eg.Go(func() (err error) {
var (
oids []int64
tagm = map[int64]*tag.Tag{}
chanOids = map[int64][]*channel.ChanOids{}
channelCards = map[int64][]*card.Card{}
initCardPlatKey = "card_platkey_%d_%d"
)
squ, err = s.tg.Square(c, mid, s.c.SquareCount, oidNum, build, loginEvent, plat, buvid, s.isOverseas(plat))
if err != nil {
log.Error("%+v", err)
err = nil
}
for _, rec := range squ.Channels {
cards, ok := s.cardCache[rec.ID]
if !ok {
continue
}
LOOP:
for _, c := range cards {
key := fmt.Sprintf(initCardPlatKey, plat, c.ID)
cardPlat, ok := s.cardPlatCache[key]
if !ok {
continue
}
if c.Type != model.GotoAv {
continue
}
for _, l := range cardPlat {
if model.InvalidBuild(build, l.Build, l.Condition) {
continue LOOP
}
}
channelCards[c.ChannelID] = append(channelCards[c.ChannelID], c)
}
}
for channelID, recOid := range squ.Oids {
oids = append(oids, recOid...)
if cards, ok := channelCards[channelID]; ok {
for _, c := range cards {
if c.Type == model.GotoAv {
chanOids[channelID] = append(chanOids[channelID], &channel.ChanOids{Oid: c.Value, FromType: _fTypeOperation})
oids = append(oids, c.Value)
}
}
}
for _, tmpOid := range recOid {
chanOids[channelID] = append(chanOids[channelID], &channel.ChanOids{Oid: tmpOid, FromType: _fTypeRecommend})
}
}
am, err := s.arc.Archives(c, oids)
if err != nil {
return
}
for _, rec := range squ.Channels {
var cardItem []*operate.Card
tagm[rec.ID] = &tag.Tag{
ID: rec.ID,
Name: rec.Name,
Cover: rec.Cover,
Content: rec.ShortContent,
Type: int8(rec.Type),
State: int8(rec.State),
IsAtten: int8(rec.Attention),
}
tagm[rec.ID].Count.Atten = int(rec.Sub)
for _, oidItem := range chanOids[rec.ID] {
if len(cardItem) >= 2 {
break
}
if _, ok := am[oidItem.Oid]; !ok {
continue
}
cardItem = append(cardItem, &operate.Card{ID: oidItem.Oid, FromType: oidItem.FromType})
}
if len(cardItem) < 2 {
continue
}
var (
h = cardm.Handle(plat, cdm.CardGt("channel_square"), "channel_square", cdm.ColumnSvrSingle, nil, tagm, nil, nil, nil)
)
if h == nil {
continue
}
op := &operate.Card{
ID: rec.ID,
Items: cardItem,
Plat: plat,
Param: strconv.FormatInt(rec.ID, 10),
}
h.From(am, op)
if h.Get() != nil && h.Get().Right {
res.Square = append(res.Square, h)
}
}
return
})
}
eg.Wait()
return
}
// Mysub 我订阅的tag standard放前面用户自定义custom放后面
func (s *Service) Mysub(c context.Context, mid int64, limit int) (res *channel.Mysub, err error) {
var (
tinfo []*tag.TagInfo
subChannel []*channel.Channel
)
res = new(channel.Mysub)
list, err := s.tg.Subscribe(c, mid)
if err != nil {
log.Error("%+v", err)
return
}
tinfo = list.Standard
tinfo = append(tinfo, list.Custom...)
if len(tinfo) > 0 {
for _, chann := range tinfo {
subChannel = append(subChannel, &channel.Channel{
ID: chann.ID,
Name: chann.Name,
Cover: chann.Cover,
Atten: chann.Sub,
IsAtten: chann.Attention,
Content: chann.Content,
})
}
if len(subChannel) > limit && limit > 0 {
subChannel = subChannel[:limit]
}
}
res.List = subChannel
res.DisplayCount = _maxAtten
return
}
func (s *Service) isOverseas(plat int8) (res int32) {
if ok := model.IsOverseas(plat); ok {
res = 1
} else {
res = 0
}
return
}
func (s *Service) tablist(t *tag.ChannelDetail) (res []*channel.TabList) {
res = s.defaultTab(t)
var (
mpos []int
tmpmenus = map[int]*tab.Menu{}
menus = s.menuCache[t.Tag.ID]
menusTabIDs = map[int64]struct{}{}
)
if len(menus) == 0 {
return
}
for _, m := range menus {
tmpmenus[m.Priority] = m
mpos = append(mpos, m.Priority)
}
for _, pos := range mpos {
var (
tmpm *tab.Menu
ok bool
)
if tmpm, ok = tmpmenus[pos]; !ok || pos == 0 {
continue
}
if _, ok := menusTabIDs[tmpm.TabID]; !ok {
menusTabIDs[tmpm.TabID] = struct{}{}
} else {
continue
}
tl := &channel.TabList{}
tl.TabListChange(tmpm)
if len(res) < pos {
res = append(res, tl)
continue
}
res = append(res[:pos-1], append([]*channel.TabList{tl}, res[pos-1:]...)...)
}
return
}
func (s *Service) defaultTab(t *tag.ChannelDetail) (res []*channel.TabList) {
for _, tmp := range _tabList {
r := &channel.TabList{}
*r = *tmp
switch tmp.TabID {
case "multiple":
r.URI = fmt.Sprintf(r.URI, t.Tag.ID)
case "topic":
r.URI = fmt.Sprintf(r.URI, t.Tag.ID, url.QueryEscape(t.Tag.Name))
}
res = append(res, r)
}
return
}