feed.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "go-common/app/service/bbq/common"
  7. "time"
  8. v1 "go-common/app/interface/bbq/app-bbq/api/http/v1"
  9. "go-common/app/interface/bbq/app-bbq/model"
  10. video "go-common/app/service/bbq/video/api/grpc/v1"
  11. "go-common/library/ecode"
  12. "go-common/library/log"
  13. bm "go-common/library/net/http/blademaster"
  14. "go-common/library/net/trace"
  15. xtime "go-common/library/time"
  16. )
  17. // FeedUpdateNum 关注页红点
  18. func (s *Service) FeedUpdateNum(c context.Context, mid int64) (res v1.FeedUpdateNumResponse, err error) {
  19. // 0. 获取关注链
  20. followedMid, err := s.dao.FetchFollowList(c, mid)
  21. if err != nil {
  22. log.Errorv(c, log.KV("log", "fetch follow fail"), log.KV("mid", mid), log.KV("err", err))
  23. return
  24. }
  25. if len(followedMid) == 0 {
  26. res.Num = 0
  27. log.V(1).Infov(c, log.KV("log", "no_follow"), log.KV("mid", mid))
  28. return
  29. }
  30. // 1. 获取mid上次浏览点
  31. pubTime, _ := s.dao.GetMIDLastPubtime(c, mid)
  32. // 2. 获取新的视频
  33. newlySvID, err := s.dao.FetchAvailableOutboxList(c, common.FeedStates, followedMid, false, 0, xtime.Time(pubTime), 1)
  34. if err != nil {
  35. log.Errorv(c, log.KV("log", "fetch available outbox list fail"), log.KV("mid", mid), log.KV("pubTime", pubTime))
  36. return
  37. }
  38. // 3. form rsp
  39. if len(newlySvID) == 0 {
  40. res.Num = 0
  41. } else {
  42. res.Num = 1
  43. }
  44. return
  45. }
  46. // FeedList 关注页短视屏列表
  47. func (s *Service) FeedList(c context.Context, req *v1.FeedListRequest) (res *v1.FeedListResponse, err error) {
  48. var (
  49. mid = req.MID
  50. markStr = req.Mark
  51. )
  52. res = new(v1.FeedListResponse)
  53. res.List = make([]*v1.SvDetail, 0)
  54. res.RecList = make([]*v1.SvDetail, 0)
  55. // 0.前期校验
  56. // 解析mark,获取last_svid
  57. var mark model.FeedMark
  58. isFirstPage := false
  59. // 关注up的总视频列表是否空
  60. needRecList := false
  61. if len(markStr) != 0 {
  62. var markData = []byte(markStr)
  63. err = json.Unmarshal(markData, &mark)
  64. if err != nil {
  65. err = ecode.ReqParamErr
  66. log.Errorv(c, log.KV("log", "mark_unmarshal"), log.KV("mark", markStr))
  67. return
  68. }
  69. log.V(1).Infov(c, log.KV("mark", markData))
  70. }
  71. if mark.IsRec {
  72. needRecList = true
  73. } else if mark.LastSvID == 0 {
  74. isFirstPage = true
  75. mark.LastSvID = model.MaxInt64
  76. mark.LastPubtime = xtime.Time(time.Now().Unix())
  77. // 更新最新浏览svid
  78. s.dao.SetMIDLastPubtime(c, mid, int64(mark.LastPubtime))
  79. }
  80. // 1. 需要获取详情的svid列表
  81. var svids []int64
  82. if !needRecList {
  83. // 1.获取关注链
  84. var followedMid []int64
  85. followedMid, err = s.dao.FetchFollowList(c, mid)
  86. if err != nil {
  87. log.Errorv(c, log.KV("log", "fetch_follow"), log.KV("mid", mid))
  88. return
  89. }
  90. // 无关注人,直接返回
  91. if len(followedMid) == 0 {
  92. res.HasMore = false
  93. needRecList = true
  94. log.V(1).Infov(c, log.KV("log", "no_follow"), log.KV("mid", mid))
  95. } else {
  96. // 2.获取svid列表
  97. svids, err = s.dao.FetchAvailableOutboxList(c, common.FeedStates, followedMid, true, mark.LastSvID, mark.LastPubtime, model.FeedListLen)
  98. if err != nil {
  99. log.Warnw(c, "log", "fetch available outbox list fail")
  100. return
  101. }
  102. // 为了保护列表,所以/2,后面切换获取逻辑就可以去掉了
  103. if len(svids) < model.FeedListLen/2 {
  104. res.HasMore = false
  105. // 关注人了,但是这些人没有发布过视频
  106. if len(svids) == 0 && isFirstPage {
  107. needRecList = true
  108. }
  109. } else {
  110. res.HasMore = true
  111. }
  112. }
  113. }
  114. // 如果第一页就是empty或者mark携带了is_rec,需要为用户推荐一些视频
  115. if needRecList {
  116. svids, err = s.dao.AttentionRecList(c, model.FeedListLen, mid, req.BUVID)
  117. if err != nil {
  118. log.Warnw(c, "log", "get attention feed rec fail")
  119. return
  120. }
  121. }
  122. // 2.获取sv信息列表
  123. var list []*v1.SvDetail
  124. // 2.0 获取sv详情
  125. detailMap, err := s.getVideoDetail(c, req.MID, req.Qn, req.Device, svids, true)
  126. if err != nil {
  127. log.Warnv(c, log.KV("log", "get video detail fail"))
  128. return
  129. } else if len(detailMap) == 0 {
  130. log.Warnv(c, log.KV("log", "feed list empty"), log.KV("svid_num", len(svids)))
  131. return
  132. }
  133. // 2.1 获取热评
  134. hots, hotErr := s.dao.ReplyHot(c, mid, svids)
  135. if hotErr != nil {
  136. log.Warnv(c, log.KV("log", "get hot reply fail"))
  137. }
  138. log.V(1).Infov(c, log.KV("log", "get_video_detail"), log.KV("req_size", len(svids)),
  139. log.KV("rsp_size", len(detailMap)))
  140. for _, svID := range svids {
  141. v, exists := detailMap[svID]
  142. if exists {
  143. if hots, ok := hots[svID]; ok {
  144. v.HotReply.Hots = hots
  145. }
  146. list = append(list, v)
  147. } else {
  148. log.Warnv(c, log.KV("log", "sv_not_found"), log.KV("mid", mid), log.KV("svid", svID))
  149. }
  150. }
  151. // 3. 组装回包,判断往哪个list塞数据
  152. var nextMark model.FeedMark
  153. if needRecList {
  154. res.RecList = list
  155. nextMark.IsRec = true
  156. } else {
  157. res.List = list
  158. if len(list) > 0 && res.HasMore {
  159. nextMark.LastSvID = list[len(list)-1].SVID
  160. nextMark.LastPubtime = list[len(list)-1].Pubtime
  161. }
  162. }
  163. jsonStr, _ := json.Marshal(nextMark) // marshal的时候相信库函数,不做err判断
  164. res.Mark = string(jsonStr)
  165. return
  166. }
  167. // SpaceSvList 个人空间视频列表
  168. func (s *Service) SpaceSvList(c context.Context, req *v1.SpaceSvListRequest) (res *v1.SpaceSvListResponse, err error) {
  169. // 0.前期校验
  170. // 这里就不校验up主是否存在
  171. res = new(v1.SpaceSvListResponse)
  172. res.List = make([]*v1.SvDetail, 0)
  173. upMid := req.UpMid
  174. if upMid == 0 {
  175. err = ecode.ReqParamErr
  176. log.Errorv(c, log.KV("log", "up mid is 0"), log.KV("up_mid", 0))
  177. return
  178. }
  179. // parseCursor
  180. cursor, cursorNext, err := parseCursor(req.CursorPrev, req.CursorNext)
  181. if err != nil {
  182. return
  183. }
  184. // 1. 如果是主人态,其第一页,则进行额外prepare_list
  185. if req.MID == req.UpMid && len(req.CursorNext) == 0 && len(req.CursorPrev) == 0 {
  186. prepareRes, tmpErr := s.videoClient.ListPrepareVideo(c, &video.PrepareVideoRequest{Mid: req.MID})
  187. if tmpErr != nil {
  188. log.Warnw(c, "log", "get prepare video fail", "mid", req.MID)
  189. } else {
  190. res.PrepareList = prepareRes.List
  191. }
  192. }
  193. // 2. 获取svid列表
  194. states := common.SpaceFanStates
  195. if req.MID == req.UpMid {
  196. states = common.SpaceOwnerStates
  197. }
  198. svids, err := s.dao.FetchAvailableOutboxList(c, states, []int64{upMid}, cursorNext, cursor.CursorID, cursor.CursorTime, req.Size)
  199. if err != nil {
  200. log.Infov(c, log.KV("log", "fetch_outbox_list"), log.KV("error", err))
  201. return
  202. }
  203. if len(svids) < req.Size/2 {
  204. res.HasMore = false
  205. if len(svids) == 0 {
  206. return
  207. }
  208. } else {
  209. res.HasMore = true
  210. }
  211. // 3.获取sv详情
  212. detailMap, err := s.svInfos(c, svids, req.MID, true)
  213. if err != nil {
  214. log.Errorv(c, log.KV("log", "get video detail fail"))
  215. return
  216. } else if len(detailMap) == 0 {
  217. log.Warnv(c, log.KV("log", "feed list empty"), log.KV("svid_num", len(svids)))
  218. return
  219. }
  220. for _, svID := range svids {
  221. item, exists := detailMap[svID]
  222. if exists {
  223. sv := new(v1.SvDetail)
  224. sv.VideoResponse = *item
  225. res.List = append(res.List, sv)
  226. } else {
  227. log.Warnv(c, log.KV("log", "sv_not_found"), log.KV("svid", svID))
  228. }
  229. }
  230. // query id
  231. tracer, _ := trace.FromContext(c)
  232. queryID := fmt.Sprintf("%s", tracer)
  233. // 4. 后处理,为每个item添加cursor值
  234. var itemCursor model.CursorValue
  235. for _, item := range res.List {
  236. itemCursor.CursorID = item.SVID
  237. itemCursor.CursorTime = item.Pubtime
  238. jsonStr, _ := json.Marshal(itemCursor) // marshal的时候相信库函数,不做err判断
  239. item.CursorValue = string(jsonStr)
  240. item.QueryID = queryID
  241. }
  242. return
  243. }
  244. // getVideoDetail 返回SvDetail的列表,返回的list顺序和svids顺序一致,但不保证svid都能出现在list中
  245. func (s *Service) getVideoDetail(c context.Context, mid int64, qn int64, device *bm.Device, svids []int64, fullVersion bool) (res map[int64]*v1.SvDetail, err error) {
  246. res = make(map[int64]*v1.SvDetail)
  247. if len(svids) == 0 {
  248. return
  249. }
  250. // 拉取视频详情
  251. svRes, err := s.svInfos(c, svids, mid, true)
  252. if err != nil {
  253. log.Errorv(c, log.KV("log", "get video detail from dao fail"))
  254. return
  255. }
  256. log.V(1).Infov(c, log.KV("log", "get_video_detail"), log.KV("req_size", len(svids)), log.KV("rsp_size", len(svRes)))
  257. if len(svRes) == 0 {
  258. log.Warnv(c, log.KV("log", "get_video_detail_empty"), log.KV("req_size", len(svids)), log.KV("rsp_size", len(svRes)))
  259. return
  260. }
  261. // 开始组装回包
  262. currentTs := time.Now().Unix()
  263. for svid, svInfo := range svRes {
  264. sv := new(v1.SvDetail)
  265. // 组装video基础信息
  266. sv.VideoResponse = *svInfo
  267. if currentTs > int64(sv.Pubtime) {
  268. sv.ElapsedTime = currentTs - int64(sv.Pubtime)
  269. }
  270. res[svid] = sv
  271. }
  272. return
  273. }
  274. // parseCursor从cursor_prev和cursor_next,判断请求的方向,以及生成cursor
  275. func parseCursor(cursorPrev string, cursorNext string) (cursor model.CursorValue, directionNext bool, err error) {
  276. // 判断是向前还是向后查询
  277. directionNext = true
  278. cursorStr := cursorNext
  279. if len(cursorNext) == 0 && len(cursorPrev) > 0 {
  280. directionNext = false
  281. cursorStr = cursorPrev
  282. }
  283. // 解析cursor中的cursor_id
  284. if len(cursorStr) != 0 {
  285. var cursorData = []byte(cursorStr)
  286. err = json.Unmarshal(cursorData, &cursor)
  287. if err != nil {
  288. err = ecode.ReqParamErr
  289. return
  290. }
  291. }
  292. // 第一次请求的时候,携带的svid=0,需要转成max传给dao层
  293. if directionNext && cursor.CursorID == 0 {
  294. cursor.CursorID = model.MaxInt64
  295. cursor.CursorTime = xtime.Time(time.Now().Unix())
  296. }
  297. return
  298. }