go-common/app/interface/live/app-interface/service/v2/index.go
2019-04-22 18:49:16 +08:00

533 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 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
}