index.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package v2
  2. import (
  3. "context"
  4. "sync/atomic"
  5. "time"
  6. "go-common/app/interface/live/app-interface/dao/account"
  7. "go-common/app/interface/live/app-interface/dao/av"
  8. "go-common/app/interface/live/app-interface/dao/fans_medal"
  9. "go-common/app/interface/live/app-interface/dao/live_data"
  10. "go-common/app/interface/live/app-interface/dao/rankdb"
  11. "go-common/app/interface/live/app-interface/dao/relation"
  12. "go-common/app/interface/live/app-interface/dao/room_ex"
  13. "go-common/app/interface/live/app-interface/dao/user_ext"
  14. v2pb "go-common/app/interface/live/app-interface/api/http/v2"
  15. "go-common/app/interface/live/app-interface/conf"
  16. "go-common/app/interface/live/app-interface/dao"
  17. liveuserDao "go-common/app/interface/live/app-interface/dao/live_user"
  18. roomDao "go-common/app/interface/live/app-interface/dao/room"
  19. roomexDao "go-common/app/interface/live/app-interface/dao/room_ex"
  20. xuserDao "go-common/app/interface/live/app-interface/dao/xuser"
  21. liveUserV1 "go-common/app/service/live/live_user/api/liverpc/v1"
  22. recommendV1 "go-common/app/service/live/recommend/api/grpc/v1"
  23. roomV2 "go-common/app/service/live/room/api/liverpc/v2"
  24. xrf "go-common/app/service/live/xroom-feed/api"
  25. "go-common/library/log"
  26. "go-common/library/net/http/blademaster"
  27. "go-common/library/net/metadata"
  28. "go-common/library/sync/errgroup"
  29. "go-common/library/xstr"
  30. )
  31. const (
  32. _bannerType = 1
  33. _entranceType = 2
  34. _yunyingRecFormType = 3
  35. _yunyingRecSquareType = 4
  36. _rankType = 5
  37. _recFormType = 6
  38. _recSquareType = 7
  39. _feedType = 8
  40. _parentAreaFormType = 9
  41. _parentAreaSquareType = 10
  42. _activityType = 11
  43. _myAreaTagType = 12
  44. _myAreaTagListType = 13
  45. _seaPatrolType = 14
  46. )
  47. // IndexService struct
  48. type IndexService struct {
  49. conf *conf.Config
  50. commonType []int64
  51. // dao
  52. dao *dao.Dao
  53. roomDao *roomDao.Dao
  54. liveuserDao *liveuserDao.Dao
  55. rankdbDao *rankdb.Dao
  56. livedataDao *live_data.Dao
  57. relationDao *relation.Dao
  58. roomexDao *room_ex.Dao
  59. userextDao *user_ext.Dao
  60. avDao *av.Dao
  61. fansMedalDao *fans_medal.Dao
  62. accountDao *account.Dao
  63. xuserDao *xuserDao.Dao
  64. // cache
  65. // all base module cache
  66. //AllMInfoMap map[int64][]*v2pb.ModuleInfo
  67. AllMInfoMap atomic.Value
  68. //areaEntranceMap map[int64][]*v2pb.PicItem
  69. areaEntranceListMap atomic.Value
  70. commonRoomList atomic.Value
  71. recommendConn recommendV1.RecommendClient
  72. xrfClient *xrf.Client
  73. }
  74. // NewIndexService init
  75. func NewIndexService(c *conf.Config) (s *IndexService) {
  76. s = &IndexService{
  77. conf: c,
  78. dao: dao.New(c),
  79. roomDao: roomDao.New(c),
  80. liveuserDao: liveuserDao.New(c),
  81. rankdbDao: rankdb.New(c),
  82. roomexDao: roomexDao.New(c),
  83. accountDao: account.New(c),
  84. xuserDao: xuserDao.New(c),
  85. commonType: []int64{
  86. _yunyingRecFormType,
  87. _yunyingRecSquareType,
  88. _recFormType,
  89. _recSquareType,
  90. _parentAreaFormType,
  91. _parentAreaSquareType,
  92. },
  93. }
  94. // init cache data
  95. s.loadAllModuleInfoMap()
  96. s.loadAreaEntranceCache()
  97. s.loadCommonListMap()
  98. s.loadLastHourData(context.TODO())
  99. go s.allModuleInfoProc()
  100. go s.areaEntranceProc()
  101. go s.allcommonListProc()
  102. go s.loadLastHour()
  103. conn, err := recommendV1.NewClient(conf.Conf.Warden)
  104. if err != nil {
  105. panic(err)
  106. }
  107. s.recommendConn = conn
  108. xrfc, err := xrf.NewClient(conf.Conf.Warden)
  109. if err != nil {
  110. panic(err)
  111. }
  112. s.xrfClient = xrfc
  113. return s
  114. }
  115. // Index 相关服务
  116. // GetAllList implementation
  117. // 首页大接口
  118. // `midware:"guest,verify"`
  119. func (s *IndexService) GetAllList(ctx context.Context, req *v2pb.GetAllListReq) (resp *v2pb.GetAllListResp, err error) {
  120. resp = &v2pb.GetAllListResp{
  121. Interval: 10,
  122. IsSkyHorseGray: 0,
  123. Banner: []*v2pb.MBanner{},
  124. MyTag: []*v2pb.MMyTag{},
  125. AreaEntrance: []*v2pb.MAreaEntrance{},
  126. SeaPatrol: []*v2pb.MSeaPatrol{},
  127. MyIdol: []*v2pb.MMyIdol{},
  128. RoomList: []*v2pb.MRoomBlock{},
  129. HourRank: []*v2pb.MHourRank{},
  130. ActivityCard: []*v2pb.MActivityCard{},
  131. }
  132. moduleInfoMap := s.GetAllModuleInfoMapFromCache(ctx)
  133. if moduleInfoMap == nil {
  134. log.Error("[GetAllList]module info list is nil, moduleIds:%+v", moduleInfoMap)
  135. return
  136. }
  137. // 初始化各模块返回信息
  138. respCommonRoomList := make([]*v2pb.MRoomBlock, 0)
  139. respMultiRoomList := make([]*v2pb.MRoomBlock, 0)
  140. myAreaMap := make(map[int64]bool)
  141. respMyIdol := &v2pb.MMyIdol{}
  142. respSkyHorseRoomList := make([]*v2pb.CommonRoomItem, 0)
  143. respLiveRecRoomList := make([]*v2pb.CommonRoomItem, 0)
  144. // 大多使用header里的mid解析, 框架已封装请求的header
  145. midInterface, isUIDSet := metadata.Value(ctx, metadata.Mid).(int64)
  146. mid := int64(0)
  147. if isUIDSet {
  148. mid = midInterface
  149. }
  150. buvid := ""
  151. // 主站封好的,可从device里获取到sid、buvid、buvid3、build、channel、device、mobi_app、platform
  152. device, ok := metadata.Value(ctx, metadata.Device).(*blademaster.Device)
  153. if ok {
  154. buvid = device.Buvid
  155. }
  156. // 第一波并发获取数据,无依赖
  157. func(device *blademaster.Device) {
  158. wg1 := errgroup.Group{}
  159. // banner
  160. if s.isModuleExist(_bannerType) {
  161. wg1.Go(func() (err error) {
  162. resp.Banner = s.getIndexBanner(ctx, req.Platform, req.Device, req.Build)
  163. return
  164. })
  165. }
  166. // 常用标签列表
  167. if s.isModuleExist(_myAreaTagType) {
  168. wg1.Go(func() (err error) {
  169. resp.MyTag, _ = s.GetIndexV2TagList(ctx, &liveUserV1.UserSettingGetTagReq{})
  170. return
  171. })
  172. }
  173. // 分区入口
  174. if s.isModuleExist(_entranceType) {
  175. wg1.Go(func() (err error) {
  176. resp.AreaEntrance = s.getAreaEntrance(ctx)
  177. return
  178. })
  179. }
  180. // 我的关注
  181. if s.isModuleExist(_feedType) {
  182. wg1.Go(func() (err error) {
  183. resp.MyIdol = s.LiveAnchorHomePage(ctx, req.RelationPage, req.Build, req.Platform, req.Quality)
  184. if len(resp.MyIdol) > 0 {
  185. respMyIdol = resp.MyIdol[0]
  186. }
  187. return
  188. })
  189. }
  190. // 通用房间列表(肯定是有的,此处不做判断),推荐、运营推荐分区、常用分区(特殊:要在第二波拿)、一级分区
  191. wg1.Go(func() (err error) {
  192. respCommonRoomList = s.getCommonRoomListForIndex(ctx, req.Build, req.Platform, req.Quality)
  193. return
  194. })
  195. // 活动卡片
  196. if s.isModuleExist(_activityType) {
  197. wg1.Go(func() (err error) {
  198. resp.ActivityCard = s.getActivityCard(ctx)
  199. return
  200. })
  201. }
  202. // 小时榜
  203. if s.isModuleExist(_rankType) {
  204. wg1.Go(func() (err error) {
  205. resp.HourRank, _ = s.getLastHourTop3(ctx)
  206. return
  207. })
  208. }
  209. // 大航海
  210. mobiApp := device.RawMobiApp
  211. if s.isModuleExist(_seaPatrolType) && mobiApp != "iphone_b" && mobiApp != "android_b" {
  212. wg1.Go(func() (err error) {
  213. resp.SeaPatrol, _ = s.GetIndexV2SeaPatrol(ctx, &liveUserV1.NoteGetReq{})
  214. return
  215. })
  216. }
  217. err1 := wg1.Wait()
  218. if err1 != nil {
  219. log.Error("[GetAllList]wg1 wait error: %+v", err1)
  220. }
  221. }(device)
  222. // 第二波获取数据 (依赖第一波)
  223. func() {
  224. wg2 := errgroup.Group{}
  225. // 天马个性化推荐 无法缓存 依赖关注
  226. if s.ifHitSkyHorse(mid, req.Device) {
  227. wg2.Go(func() (err error) {
  228. //目前只对第一个关注模块去重
  229. respSkyHorseRoomList, err = s.getSkyHorseRoomListForIndex(ctx, respMyIdol, mid, buvid, req.Build, req.Platform, req.RecPage, req.Quality)
  230. return
  231. })
  232. }
  233. // 直播个性化推荐 无法缓存 依赖关注
  234. if s.ifHitLiveRec(mid, req.Device) {
  235. wg2.Go(func() (err error) {
  236. respLiveRecRoomList, err = s.getLiveRecRoomList(ctx, respMyIdol, mid, req.Build, req.Platform, req.RecPage, req.Quality)
  237. return
  238. })
  239. }
  240. // 常用标签房间列表
  241. wg2.Go(func() (err error) {
  242. respMultiRoomList, myAreaMap = s.getMultiRoomList(ctx, resp.MyTag, req.Platform, req.Build, req.Quality)
  243. resp.RoomList = append(resp.RoomList, respMultiRoomList...)
  244. return
  245. })
  246. // 对保底逻辑的一些处理,对关注去重, 数量限制, 获取投放系统数据
  247. wg2.Go(func() (err error) {
  248. respCommonRoomList = s.handleCommonRoomList(ctx, respMyIdol, respCommonRoomList, req.Quality, req.Build, req.Platform, req.Device)
  249. return
  250. })
  251. err2 := wg2.Wait()
  252. if err2 != nil {
  253. log.Error("[GetAllList]wg2 wait error: %+v", err2)
  254. }
  255. }()
  256. // 推荐直播最终处理
  257. handleRecResult(resp, respCommonRoomList, respSkyHorseRoomList, respLiveRecRoomList, myAreaMap)
  258. if resp.IsSkyHorseGray == 0 && s.ifHitLiveRec(mid, req.Device) {
  259. log.Info("live rec hit miss, mid:%d, liveRec:%+v", mid, respLiveRecRoomList)
  260. }
  261. return
  262. }
  263. // Change implementation
  264. // 换一换接口
  265. // `midware:"guest,verify"`
  266. func (s *IndexService) Change(ctx context.Context, req *v2pb.ChangeReq) (resp *v2pb.ChangeResp, err error) {
  267. resp = &v2pb.ChangeResp{}
  268. mid, isUIDSet := metadata.Value(ctx, metadata.Mid).(int64)
  269. var uid int64
  270. if isUIDSet {
  271. uid = mid
  272. }
  273. duplicates, _ := xstr.SplitInts(req.AttentionRoomId)
  274. duplicatesMap := make(map[int64]bool)
  275. for _, roomID := range duplicates {
  276. duplicatesMap[roomID] = true
  277. }
  278. buvid := ""
  279. // 主站封好的,可从device里获取到sid、buvid、buvid3、build、channel、device、mobi_app、platform
  280. device, ok := metadata.Value(ctx, metadata.Device).(*blademaster.Device)
  281. if ok {
  282. buvid = device.Buvid
  283. }
  284. moduleInfo, err := s.roomDao.GetAllModuleInfo(ctx, req.ModuleId)
  285. if err != nil || moduleInfo[0] == nil {
  286. log.Error("[Change]GetModuleInfoById error:%+v", err)
  287. return
  288. }
  289. // 给moduleInfo赋值
  290. resp.ModuleInfo = &v2pb.ModuleInfo{
  291. Id: moduleInfo[0].Id,
  292. Link: moduleInfo[0].Link,
  293. Pic: moduleInfo[0].Pic,
  294. Title: moduleInfo[0].Title,
  295. Type: moduleInfo[0].Type,
  296. Sort: moduleInfo[0].Sort,
  297. Count: moduleInfo[0].Count,
  298. }
  299. resp.List = make([]*v2pb.CommonRoomItem, 0)
  300. isDefault := true
  301. if s.ifHitSkyHorse(uid, req.Device) {
  302. skyHorseList, err := s.getSkyHorseRoomList(ctx, mid, buvid, req.Build, req.Platform, duplicates, req.Page, req.Quality)
  303. if err == nil && len(skyHorseList) > 0 {
  304. isDefault = false
  305. resp.IsSkyHorseGray = 1
  306. resp.List = skyHorseList
  307. }
  308. }
  309. if s.ifHitLiveRec(mid, req.Device) {
  310. respLiveRoomList, err := s.getLiveRecRoomListForChange(ctx, mid, req.Build, req.Platform, duplicates, req.Page, req.Quality)
  311. if err == nil && len(respLiveRoomList) > 0 {
  312. isDefault = false
  313. resp.IsSkyHorseGray = 1
  314. resp.List = respLiveRoomList
  315. } else {
  316. log.Info("live rec hit miss, from:change, mid:%d, err:%+v, liveRec:%+v", mid, err, respLiveRoomList)
  317. }
  318. }
  319. if isDefault {
  320. resp.IsSkyHorseGray = 0
  321. respCommonRoomList, errTemp := s.getCommonRoomListByID(ctx, req.ModuleId, req.Build, req.Platform, req.Quality, req.Device, duplicates)
  322. if errTemp != nil {
  323. log.Error("[Change]GetModuleInfoById error:%+v", errTemp)
  324. err = errTemp
  325. return
  326. }
  327. resp.List = respCommonRoomList
  328. }
  329. return
  330. }
  331. // 指定模块是否存在
  332. func (s *IndexService) isModuleExist(iType int64) (res bool) {
  333. res = false
  334. mInfoMap := s.GetAllModuleInfoMapFromCache(context.TODO())
  335. if _, ok := mInfoMap[iType]; ok {
  336. res = true
  337. }
  338. return
  339. }
  340. // 推荐模块最终处理:天马、对关注去重
  341. func handleRecResult(resp *v2pb.GetAllListResp, respCommonRoomList []*v2pb.MRoomBlock, respSkyHorseRoomList []*v2pb.CommonRoomItem, respLiveRecRoomList []*v2pb.CommonRoomItem, myAreaMap map[int64]bool) {
  342. afterHandleRoomList := make([]*v2pb.MRoomBlock, 0)
  343. for _, roomBlock := range respCommonRoomList {
  344. if roomBlock.ModuleInfo.Type == _recFormType || roomBlock.ModuleInfo.Type == _recSquareType {
  345. if len(respSkyHorseRoomList) > 0 {
  346. resp.IsSkyHorseGray = 1
  347. roomBlock.List = respSkyHorseRoomList
  348. } else if len(respLiveRecRoomList) > 0 {
  349. resp.IsSkyHorseGray = 1
  350. roomBlock.List = respLiveRecRoomList
  351. }
  352. afterHandleRoomList = append(afterHandleRoomList, roomBlock)
  353. continue
  354. }
  355. // 常用分区对运营推荐分区去重
  356. if roomBlock.ModuleInfo.Type == _yunyingRecFormType || roomBlock.ModuleInfo.Type == _yunyingRecSquareType {
  357. for _, item := range roomBlock.List {
  358. if _, ok := myAreaMap[item.AreaV2Id]; !ok {
  359. afterHandleRoomList = append(afterHandleRoomList, roomBlock)
  360. break
  361. }
  362. }
  363. continue
  364. }
  365. afterHandleRoomList = append(afterHandleRoomList, roomBlock)
  366. }
  367. resp.RoomList = append(resp.RoomList, afterHandleRoomList...)
  368. }
  369. // get AllModuleInfoMap
  370. func (s *IndexService) getAllModuleInfoMap(ctx context.Context) (allMInfoCacheMap map[int64][]*v2pb.ModuleInfo) {
  371. var allMInfoData []*roomV2.AppIndexGetBaseMInfoListResp_ModuleInfo
  372. var err error
  373. var retry int64
  374. for i := 1; i <= 3; i++ {
  375. // 最多重试3次
  376. allMInfoData, err = s.roomDao.GetAllModuleInfo(ctx, 0)
  377. if err != nil || len(allMInfoData) <= 0 {
  378. retry++
  379. log.Error("[loadAllModuleInfoMap] GetAllModuleInfo error(%+v) retry_times(%d)", err, retry)
  380. continue
  381. }
  382. break
  383. }
  384. if len(allMInfoData) > 0 && allMInfoData[1] != nil {
  385. allMInfoCacheMap = make(map[int64][]*v2pb.ModuleInfo)
  386. for _, m := range allMInfoData {
  387. allMInfoCacheMap[m.Type] = append(allMInfoCacheMap[m.Type], &v2pb.ModuleInfo{
  388. Id: m.Id,
  389. Link: m.Link,
  390. Pic: m.Pic,
  391. Title: m.Title,
  392. Type: m.Type,
  393. Sort: m.Sort,
  394. Count: m.Count,
  395. })
  396. }
  397. }
  398. return
  399. }
  400. // cache load
  401. func (s *IndexService) loadAllModuleInfoMap() {
  402. allMInfoCacheMap := s.getAllModuleInfoMap(context.TODO())
  403. if len(allMInfoCacheMap) > 0 {
  404. s.AllMInfoMap.Store(allMInfoCacheMap)
  405. log.Info("[loadAllModuleInfoMap]load data success!")
  406. }
  407. }
  408. // ticker
  409. func (s *IndexService) allModuleInfoProc() {
  410. for {
  411. time.Sleep(time.Second * 5)
  412. s.loadAllModuleInfoMap()
  413. }
  414. }
  415. // GetAllModuleInfoMapFromCache get all module info fromcache
  416. func (s *IndexService) GetAllModuleInfoMapFromCache(ctx context.Context) (res map[int64][]*v2pb.ModuleInfo) {
  417. // load
  418. i := s.AllMInfoMap.Load()
  419. // assert
  420. res, ok := i.(map[int64][]*v2pb.ModuleInfo)
  421. if ok {
  422. return
  423. }
  424. // 回源&log
  425. res = s.getAllModuleInfoMap(ctx)
  426. log.Warn("[GetAllModuleInfoMap]memory cache miss!! i:%+v; res:%+v", i, res)
  427. return
  428. }
  429. func (s *IndexService) getCommonListFromCache(sIds []int64) (commonList map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList) {
  430. commonListCache, ok := s.commonRoomList.Load().(map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList)
  431. if ok {
  432. commonList = commonListCache
  433. return
  434. }
  435. roomListMap, err := s.roomDao.GetListByIds(context.TODO(), sIds)
  436. if err != nil {
  437. log.Error("[getCommonListFromCache]GetListByIds error, error:%+v", err)
  438. return
  439. }
  440. commonList = roomListMap
  441. log.Warn("[getCommonListFromCache]memory cache miss!! res:%+v", commonList)
  442. return
  443. }
  444. func (s *IndexService) loadCommonListMap() {
  445. sIds := s.getIdsFromModuleMap(context.TODO(), s.commonType)
  446. roomListMap, err := s.roomDao.GetListByIds(context.TODO(), sIds)
  447. if err != nil {
  448. log.Error("[loadCommonListMap]GetListByIds error, error:%+v", err)
  449. return
  450. }
  451. copyRoomListMap := make(map[int64]*roomV2.AppIndexGetRoomListByIdsResp_RoomList)
  452. for moduleId, roomList := range roomListMap {
  453. for _, item := range roomList.List {
  454. if _, ok := copyRoomListMap[moduleId]; !ok {
  455. copyRoomListMap[moduleId] = &roomV2.AppIndexGetRoomListByIdsResp_RoomList{
  456. List: make([]*roomV2.AppIndexGetRoomListByIdsResp_RoomInfo, 0),
  457. }
  458. }
  459. copyRoomListMap[moduleId].List = append(copyRoomListMap[moduleId].List, item)
  460. }
  461. }
  462. s.commonRoomList.Store(copyRoomListMap)
  463. }
  464. func (s *IndexService) allcommonListProc() {
  465. for {
  466. time.Sleep(time.Second * 2)
  467. s.loadCommonListMap()
  468. }
  469. }
  470. // 根据type从模块信息map拿到模块ids列表
  471. func (s *IndexService) getIdsFromModuleMap(ctx context.Context, iTypes []int64) (sIds []int64) {
  472. mMap := s.GetAllModuleInfoMapFromCache(ctx)
  473. sIds = make([]int64, 0)
  474. for _, iType := range iTypes {
  475. typeList, ok := mMap[iType]
  476. if !ok {
  477. continue
  478. }
  479. for _, item := range typeList {
  480. if item != nil {
  481. sIds = append(sIds, item.Id)
  482. }
  483. }
  484. }
  485. return
  486. }