533 lines
15 KiB
Go
533 lines
15 KiB
Go
package v2
|
||
|
||
import (
|
||
"context"
|
||
"sync/atomic"
|
||
"time"
|
||
|
||
"go-common/app/interface/live/app-interface/dao/account"
|
||
|
||
"go-common/app/interface/live/app-interface/dao/av"
|
||
"go-common/app/interface/live/app-interface/dao/fans_medal"
|
||
"go-common/app/interface/live/app-interface/dao/live_data"
|
||
"go-common/app/interface/live/app-interface/dao/rankdb"
|
||
"go-common/app/interface/live/app-interface/dao/relation"
|
||
"go-common/app/interface/live/app-interface/dao/room_ex"
|
||
"go-common/app/interface/live/app-interface/dao/user_ext"
|
||
|
||
v2pb "go-common/app/interface/live/app-interface/api/http/v2"
|
||
"go-common/app/interface/live/app-interface/conf"
|
||
"go-common/app/interface/live/app-interface/dao"
|
||
liveuserDao "go-common/app/interface/live/app-interface/dao/live_user"
|
||
roomDao "go-common/app/interface/live/app-interface/dao/room"
|
||
roomexDao "go-common/app/interface/live/app-interface/dao/room_ex"
|
||
xuserDao "go-common/app/interface/live/app-interface/dao/xuser"
|
||
liveUserV1 "go-common/app/service/live/live_user/api/liverpc/v1"
|
||
recommendV1 "go-common/app/service/live/recommend/api/grpc/v1"
|
||
roomV2 "go-common/app/service/live/room/api/liverpc/v2"
|
||
xrf "go-common/app/service/live/xroom-feed/api"
|
||
"go-common/library/log"
|
||
"go-common/library/net/http/blademaster"
|
||
"go-common/library/net/metadata"
|
||
"go-common/library/sync/errgroup"
|
||
"go-common/library/xstr"
|
||
)
|
||
|
||
const (
|
||
_bannerType = 1
|
||
_entranceType = 2
|
||
_yunyingRecFormType = 3
|
||
_yunyingRecSquareType = 4
|
||
_rankType = 5
|
||
_recFormType = 6
|
||
_recSquareType = 7
|
||
_feedType = 8
|
||
_parentAreaFormType = 9
|
||
_parentAreaSquareType = 10
|
||
_activityType = 11
|
||
_myAreaTagType = 12
|
||
_myAreaTagListType = 13
|
||
_seaPatrolType = 14
|
||
)
|
||
|
||
// IndexService struct
|
||
type IndexService struct {
|
||
conf *conf.Config
|
||
commonType []int64
|
||
// dao
|
||
dao *dao.Dao
|
||
roomDao *roomDao.Dao
|
||
liveuserDao *liveuserDao.Dao
|
||
rankdbDao *rankdb.Dao
|
||
livedataDao *live_data.Dao
|
||
relationDao *relation.Dao
|
||
roomexDao *room_ex.Dao
|
||
userextDao *user_ext.Dao
|
||
avDao *av.Dao
|
||
fansMedalDao *fans_medal.Dao
|
||
accountDao *account.Dao
|
||
xuserDao *xuserDao.Dao
|
||
// cache
|
||
// all base module cache
|
||
//AllMInfoMap map[int64][]*v2pb.ModuleInfo
|
||
AllMInfoMap atomic.Value
|
||
//areaEntranceMap map[int64][]*v2pb.PicItem
|
||
areaEntranceListMap atomic.Value
|
||
commonRoomList atomic.Value
|
||
recommendConn recommendV1.RecommendClient
|
||
xrfClient *xrf.Client
|
||
}
|
||
|
||
// NewIndexService init
|
||
func NewIndexService(c *conf.Config) (s *IndexService) {
|
||
s = &IndexService{
|
||
conf: c,
|
||
dao: dao.New(c),
|
||
roomDao: roomDao.New(c),
|
||
liveuserDao: liveuserDao.New(c),
|
||
rankdbDao: rankdb.New(c),
|
||
roomexDao: roomexDao.New(c),
|
||
accountDao: account.New(c),
|
||
xuserDao: xuserDao.New(c),
|
||
commonType: []int64{
|
||
_yunyingRecFormType,
|
||
_yunyingRecSquareType,
|
||
_recFormType,
|
||
_recSquareType,
|
||
_parentAreaFormType,
|
||
_parentAreaSquareType,
|
||
},
|
||
}
|
||
// init cache data
|
||
s.loadAllModuleInfoMap()
|
||
s.loadAreaEntranceCache()
|
||
s.loadCommonListMap()
|
||
s.loadLastHourData(context.TODO())
|
||
go s.allModuleInfoProc()
|
||
go s.areaEntranceProc()
|
||
go s.allcommonListProc()
|
||
go s.loadLastHour()
|
||
conn, err := recommendV1.NewClient(conf.Conf.Warden)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
s.recommendConn = conn
|
||
xrfc, err := xrf.NewClient(conf.Conf.Warden)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
s.xrfClient = xrfc
|
||
return s
|
||
}
|
||
|
||
// Index 相关服务
|
||
|
||
// GetAllList implementation
|
||
// 首页大接口
|
||
// `midware:"guest,verify"`
|
||
func (s *IndexService) GetAllList(ctx context.Context, req *v2pb.GetAllListReq) (resp *v2pb.GetAllListResp, err error) {
|
||
resp = &v2pb.GetAllListResp{
|
||
Interval: 10,
|
||
IsSkyHorseGray: 0,
|
||
Banner: []*v2pb.MBanner{},
|
||
MyTag: []*v2pb.MMyTag{},
|
||
AreaEntrance: []*v2pb.MAreaEntrance{},
|
||
SeaPatrol: []*v2pb.MSeaPatrol{},
|
||
MyIdol: []*v2pb.MMyIdol{},
|
||
RoomList: []*v2pb.MRoomBlock{},
|
||
HourRank: []*v2pb.MHourRank{},
|
||
ActivityCard: []*v2pb.MActivityCard{},
|
||
}
|
||
moduleInfoMap := s.GetAllModuleInfoMapFromCache(ctx)
|
||
if moduleInfoMap == nil {
|
||
log.Error("[GetAllList]module info list is nil, moduleIds:%+v", moduleInfoMap)
|
||
return
|
||
}
|
||
|
||
// 初始化各模块返回信息
|
||
respCommonRoomList := make([]*v2pb.MRoomBlock, 0)
|
||
respMultiRoomList := make([]*v2pb.MRoomBlock, 0)
|
||
myAreaMap := make(map[int64]bool)
|
||
respMyIdol := &v2pb.MMyIdol{}
|
||
respSkyHorseRoomList := make([]*v2pb.CommonRoomItem, 0)
|
||
respLiveRecRoomList := make([]*v2pb.CommonRoomItem, 0)
|
||
|
||
// 大多使用header里的mid解析, 框架已封装请求的header
|
||
midInterface, isUIDSet := metadata.Value(ctx, metadata.Mid).(int64)
|
||
mid := int64(0)
|
||
if isUIDSet {
|
||
mid = midInterface
|
||
}
|
||
|
||
buvid := ""
|
||
// 主站封好的,可从device里获取到sid、buvid、buvid3、build、channel、device、mobi_app、platform
|
||
device, ok := metadata.Value(ctx, metadata.Device).(*blademaster.Device)
|
||
if ok {
|
||
buvid = device.Buvid
|
||
}
|
||
|
||
// 第一波并发获取数据,无依赖
|
||
func(device *blademaster.Device) {
|
||
wg1 := errgroup.Group{}
|
||
|
||
// banner
|
||
if s.isModuleExist(_bannerType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.Banner = s.getIndexBanner(ctx, req.Platform, req.Device, req.Build)
|
||
return
|
||
})
|
||
}
|
||
// 常用标签列表
|
||
if s.isModuleExist(_myAreaTagType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.MyTag, _ = s.GetIndexV2TagList(ctx, &liveUserV1.UserSettingGetTagReq{})
|
||
return
|
||
})
|
||
}
|
||
// 分区入口
|
||
if s.isModuleExist(_entranceType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.AreaEntrance = s.getAreaEntrance(ctx)
|
||
return
|
||
})
|
||
}
|
||
// 我的关注
|
||
if s.isModuleExist(_feedType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.MyIdol = s.LiveAnchorHomePage(ctx, req.RelationPage, req.Build, req.Platform, req.Quality)
|
||
if len(resp.MyIdol) > 0 {
|
||
respMyIdol = resp.MyIdol[0]
|
||
}
|
||
return
|
||
})
|
||
}
|
||
// 通用房间列表(肯定是有的,此处不做判断),推荐、运营推荐分区、常用分区(特殊:要在第二波拿)、一级分区
|
||
wg1.Go(func() (err error) {
|
||
respCommonRoomList = s.getCommonRoomListForIndex(ctx, req.Build, req.Platform, req.Quality)
|
||
return
|
||
})
|
||
// 活动卡片
|
||
if s.isModuleExist(_activityType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.ActivityCard = s.getActivityCard(ctx)
|
||
return
|
||
})
|
||
}
|
||
// 小时榜
|
||
if s.isModuleExist(_rankType) {
|
||
wg1.Go(func() (err error) {
|
||
resp.HourRank, _ = s.getLastHourTop3(ctx)
|
||
return
|
||
})
|
||
}
|
||
// 大航海
|
||
mobiApp := device.RawMobiApp
|
||
if s.isModuleExist(_seaPatrolType) && mobiApp != "iphone_b" && mobiApp != "android_b" {
|
||
wg1.Go(func() (err error) {
|
||
resp.SeaPatrol, _ = s.GetIndexV2SeaPatrol(ctx, &liveUserV1.NoteGetReq{})
|
||
return
|
||
})
|
||
}
|
||
|
||
err1 := wg1.Wait()
|
||
if err1 != nil {
|
||
log.Error("[GetAllList]wg1 wait error: %+v", err1)
|
||
}
|
||
}(device)
|
||
|
||
// 第二波获取数据 (依赖第一波)
|
||
func() {
|
||
wg2 := errgroup.Group{}
|
||
// 天马个性化推荐 无法缓存 依赖关注
|
||
if s.ifHitSkyHorse(mid, req.Device) {
|
||
wg2.Go(func() (err error) {
|
||
//目前只对第一个关注模块去重
|
||
respSkyHorseRoomList, err = s.getSkyHorseRoomListForIndex(ctx, respMyIdol, mid, buvid, req.Build, req.Platform, req.RecPage, req.Quality)
|
||
return
|
||
})
|
||
}
|
||
// 直播个性化推荐 无法缓存 依赖关注
|
||
if s.ifHitLiveRec(mid, req.Device) {
|
||
wg2.Go(func() (err error) {
|
||
respLiveRecRoomList, err = s.getLiveRecRoomList(ctx, respMyIdol, mid, req.Build, req.Platform, req.RecPage, req.Quality)
|
||
return
|
||
})
|
||
}
|
||
// 常用标签房间列表
|
||
wg2.Go(func() (err error) {
|
||
respMultiRoomList, myAreaMap = s.getMultiRoomList(ctx, resp.MyTag, req.Platform, req.Build, req.Quality)
|
||
resp.RoomList = append(resp.RoomList, respMultiRoomList...)
|
||
return
|
||
})
|
||
|
||
// 对保底逻辑的一些处理,对关注去重, 数量限制, 获取投放系统数据
|
||
wg2.Go(func() (err error) {
|
||
respCommonRoomList = s.handleCommonRoomList(ctx, respMyIdol, respCommonRoomList, req.Quality, req.Build, req.Platform, req.Device)
|
||
return
|
||
})
|
||
|
||
err2 := wg2.Wait()
|
||
if err2 != nil {
|
||
log.Error("[GetAllList]wg2 wait error: %+v", err2)
|
||
}
|
||
}()
|
||
|
||
// 推荐直播最终处理
|
||
handleRecResult(resp, respCommonRoomList, respSkyHorseRoomList, respLiveRecRoomList, myAreaMap)
|
||
if resp.IsSkyHorseGray == 0 && s.ifHitLiveRec(mid, req.Device) {
|
||
log.Info("live rec hit miss, mid:%d, liveRec:%+v", mid, respLiveRecRoomList)
|
||
}
|
||
return
|
||
}
|
||
|
||
// Change implementation
|
||
// 换一换接口
|
||
// `midware:"guest,verify"`
|
||
func (s *IndexService) Change(ctx context.Context, req *v2pb.ChangeReq) (resp *v2pb.ChangeResp, err error) {
|
||
resp = &v2pb.ChangeResp{}
|
||
mid, isUIDSet := metadata.Value(ctx, metadata.Mid).(int64)
|
||
var uid int64
|
||
if isUIDSet {
|
||
uid = mid
|
||
}
|
||
|
||
duplicates, _ := xstr.SplitInts(req.AttentionRoomId)
|
||
duplicatesMap := make(map[int64]bool)
|
||
|
||
for _, roomID := range duplicates {
|
||
duplicatesMap[roomID] = true
|
||
}
|
||
buvid := ""
|
||
// 主站封好的,可从device里获取到sid、buvid、buvid3、build、channel、device、mobi_app、platform
|
||
device, ok := metadata.Value(ctx, metadata.Device).(*blademaster.Device)
|
||
if ok {
|
||
buvid = device.Buvid
|
||
}
|
||
|
||
moduleInfo, err := s.roomDao.GetAllModuleInfo(ctx, req.ModuleId)
|
||
if err != nil || moduleInfo[0] == nil {
|
||
log.Error("[Change]GetModuleInfoById error:%+v", err)
|
||
return
|
||
}
|
||
|
||
// 给moduleInfo赋值
|
||
resp.ModuleInfo = &v2pb.ModuleInfo{
|
||
Id: moduleInfo[0].Id,
|
||
Link: moduleInfo[0].Link,
|
||
Pic: moduleInfo[0].Pic,
|
||
Title: moduleInfo[0].Title,
|
||
Type: moduleInfo[0].Type,
|
||
Sort: moduleInfo[0].Sort,
|
||
Count: moduleInfo[0].Count,
|
||
}
|
||
resp.List = make([]*v2pb.CommonRoomItem, 0)
|
||
|
||
isDefault := true
|
||
if s.ifHitSkyHorse(uid, req.Device) {
|
||
skyHorseList, err := s.getSkyHorseRoomList(ctx, mid, buvid, req.Build, req.Platform, duplicates, req.Page, req.Quality)
|
||
if err == nil && len(skyHorseList) > 0 {
|
||
isDefault = false
|
||
resp.IsSkyHorseGray = 1
|
||
resp.List = skyHorseList
|
||
}
|
||
}
|
||
|
||
if s.ifHitLiveRec(mid, req.Device) {
|
||
respLiveRoomList, err := s.getLiveRecRoomListForChange(ctx, mid, req.Build, req.Platform, duplicates, req.Page, req.Quality)
|
||
if err == nil && len(respLiveRoomList) > 0 {
|
||
isDefault = false
|
||
resp.IsSkyHorseGray = 1
|
||
resp.List = respLiveRoomList
|
||
} else {
|
||
log.Info("live rec hit miss, from:change, mid:%d, err:%+v, liveRec:%+v", mid, err, respLiveRoomList)
|
||
}
|
||
}
|
||
|
||
if isDefault {
|
||
resp.IsSkyHorseGray = 0
|
||
respCommonRoomList, errTemp := s.getCommonRoomListByID(ctx, req.ModuleId, req.Build, req.Platform, req.Quality, req.Device, duplicates)
|
||
if errTemp != nil {
|
||
log.Error("[Change]GetModuleInfoById error:%+v", errTemp)
|
||
err = errTemp
|
||
return
|
||
}
|
||
|
||
resp.List = respCommonRoomList
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// 指定模块是否存在
|
||
func (s *IndexService) isModuleExist(iType int64) (res bool) {
|
||
res = false
|
||
mInfoMap := s.GetAllModuleInfoMapFromCache(context.TODO())
|
||
if _, ok := mInfoMap[iType]; ok {
|
||
res = true
|
||
}
|
||
return
|
||
}
|
||
|
||
// 推荐模块最终处理:天马、对关注去重
|
||
func handleRecResult(resp *v2pb.GetAllListResp, respCommonRoomList []*v2pb.MRoomBlock, respSkyHorseRoomList []*v2pb.CommonRoomItem, respLiveRecRoomList []*v2pb.CommonRoomItem, myAreaMap map[int64]bool) {
|
||
afterHandleRoomList := make([]*v2pb.MRoomBlock, 0)
|
||
for _, roomBlock := range respCommonRoomList {
|
||
if roomBlock.ModuleInfo.Type == _recFormType || roomBlock.ModuleInfo.Type == _recSquareType {
|
||
if len(respSkyHorseRoomList) > 0 {
|
||
resp.IsSkyHorseGray = 1
|
||
roomBlock.List = respSkyHorseRoomList
|
||
} else if len(respLiveRecRoomList) > 0 {
|
||
resp.IsSkyHorseGray = 1
|
||
roomBlock.List = respLiveRecRoomList
|
||
}
|
||
|
||
afterHandleRoomList = append(afterHandleRoomList, roomBlock)
|
||
continue
|
||
}
|
||
// 常用分区对运营推荐分区去重
|
||
if roomBlock.ModuleInfo.Type == _yunyingRecFormType || roomBlock.ModuleInfo.Type == _yunyingRecSquareType {
|
||
for _, item := range roomBlock.List {
|
||
if _, ok := myAreaMap[item.AreaV2Id]; !ok {
|
||
afterHandleRoomList = append(afterHandleRoomList, roomBlock)
|
||
break
|
||
}
|
||
}
|
||
continue
|
||
}
|
||
afterHandleRoomList = append(afterHandleRoomList, roomBlock)
|
||
}
|
||
resp.RoomList = append(resp.RoomList, afterHandleRoomList...)
|
||
}
|
||
|
||
// get AllModuleInfoMap
|
||
func (s *IndexService) getAllModuleInfoMap(ctx context.Context) (allMInfoCacheMap map[int64][]*v2pb.ModuleInfo) {
|
||
var allMInfoData []*roomV2.AppIndexGetBaseMInfoListResp_ModuleInfo
|
||
var err error
|
||
var retry int64
|
||
for i := 1; i <= 3; i++ {
|
||
// 最多重试3次
|
||
allMInfoData, err = s.roomDao.GetAllModuleInfo(ctx, 0)
|
||
if err != nil || len(allMInfoData) <= 0 {
|
||
retry++
|
||
log.Error("[loadAllModuleInfoMap] GetAllModuleInfo error(%+v) retry_times(%d)", err, retry)
|
||
continue
|
||
}
|
||
break
|
||
}
|
||
|
||
if len(allMInfoData) > 0 && allMInfoData[1] != nil {
|
||
allMInfoCacheMap = make(map[int64][]*v2pb.ModuleInfo)
|
||
for _, m := range allMInfoData {
|
||
allMInfoCacheMap[m.Type] = append(allMInfoCacheMap[m.Type], &v2pb.ModuleInfo{
|
||
Id: m.Id,
|
||
Link: m.Link,
|
||
Pic: m.Pic,
|
||
Title: m.Title,
|
||
Type: m.Type,
|
||
Sort: m.Sort,
|
||
Count: m.Count,
|
||
})
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// cache load
|
||
func (s *IndexService) loadAllModuleInfoMap() {
|
||
allMInfoCacheMap := s.getAllModuleInfoMap(context.TODO())
|
||
if len(allMInfoCacheMap) > 0 {
|
||
s.AllMInfoMap.Store(allMInfoCacheMap)
|
||
log.Info("[loadAllModuleInfoMap]load data success!")
|
||
}
|
||
}
|
||
|
||
// ticker
|
||
func (s *IndexService) allModuleInfoProc() {
|
||
for {
|
||
time.Sleep(time.Second * 5)
|
||
s.loadAllModuleInfoMap()
|
||
}
|
||
}
|
||
|
||
// GetAllModuleInfoMapFromCache get all module info fromcache
|
||
func (s *IndexService) GetAllModuleInfoMapFromCache(ctx context.Context) (res map[int64][]*v2pb.ModuleInfo) {
|
||
// load
|
||
i := s.AllMInfoMap.Load()
|
||
// assert
|
||
res, ok := i.(map[int64][]*v2pb.ModuleInfo)
|
||
if ok {
|
||
return
|
||
}
|
||
// 回源&log
|
||
res = s.getAllModuleInfoMap(ctx)
|
||
log.Warn("[GetAllModuleInfoMap]memory cache miss!! i:%+v; res:%+v", i, res)
|
||
return
|
||
}
|
||
|
||
func (s *IndexService) getCommonListFromCache(sIds []int64) (commonList map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList) {
|
||
commonListCache, ok := s.commonRoomList.Load().(map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList)
|
||
if ok {
|
||
commonList = commonListCache
|
||
return
|
||
}
|
||
|
||
roomListMap, err := s.roomDao.GetListByIds(context.TODO(), sIds)
|
||
if err != nil {
|
||
log.Error("[getCommonListFromCache]GetListByIds error, error:%+v", err)
|
||
return
|
||
}
|
||
commonList = roomListMap
|
||
log.Warn("[getCommonListFromCache]memory cache miss!! res:%+v", commonList)
|
||
return
|
||
}
|
||
|
||
func (s *IndexService) loadCommonListMap() {
|
||
sIds := s.getIdsFromModuleMap(context.TODO(), s.commonType)
|
||
|
||
roomListMap, err := s.roomDao.GetListByIds(context.TODO(), sIds)
|
||
if err != nil {
|
||
log.Error("[loadCommonListMap]GetListByIds error, error:%+v", err)
|
||
return
|
||
}
|
||
|
||
copyRoomListMap := make(map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList)
|
||
|
||
for moduleId, roomList := range roomListMap {
|
||
for _, item := range roomList.List {
|
||
if _, ok := copyRoomListMap[moduleId]; !ok {
|
||
copyRoomListMap[moduleId] = &roomV2.AppIndexGetRoomListByIdsResp_RoomList{
|
||
List: make([]*roomV2.AppIndexGetRoomListByIdsResp_RoomInfo, 0),
|
||
}
|
||
}
|
||
copyRoomListMap[moduleId].List = append(copyRoomListMap[moduleId].List, item)
|
||
}
|
||
}
|
||
s.commonRoomList.Store(copyRoomListMap)
|
||
}
|
||
|
||
func (s *IndexService) allcommonListProc() {
|
||
for {
|
||
time.Sleep(time.Second * 2)
|
||
s.loadCommonListMap()
|
||
}
|
||
}
|
||
|
||
// 根据type从模块信息map拿到模块ids列表
|
||
func (s *IndexService) getIdsFromModuleMap(ctx context.Context, iTypes []int64) (sIds []int64) {
|
||
mMap := s.GetAllModuleInfoMapFromCache(ctx)
|
||
sIds = make([]int64, 0)
|
||
for _, iType := range iTypes {
|
||
typeList, ok := mMap[iType]
|
||
if !ok {
|
||
continue
|
||
}
|
||
for _, item := range typeList {
|
||
if item != nil {
|
||
sIds = append(sIds, item.Id)
|
||
}
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|