dynamic.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. package service
  2. import (
  3. "context"
  4. "sort"
  5. "time"
  6. "go-common/app/interface/main/space/model"
  7. arcmdl "go-common/app/service/main/archive/api"
  8. coinmdl "go-common/app/service/main/coin/api"
  9. thumbup "go-common/app/service/main/thumbup/model"
  10. "go-common/library/ecode"
  11. "go-common/library/log"
  12. "go-common/library/net/metadata"
  13. "go-common/library/sync/errgroup"
  14. )
  15. const (
  16. _dyTypeCoin = -1
  17. _dyTypeLike = -2
  18. _dyTypeMerge = -3
  19. _businessLike = "archive"
  20. _likeVideoCnt = 100
  21. _dyListCnt = 20
  22. _dyDefaultQn = 16
  23. _dyFoldNum = 3
  24. )
  25. var dyTypeFoldMap = map[int]struct{}{_dyTypeCoin: {}, _dyTypeLike: {}, _dyTypeMerge: {}}
  26. // DynamicList get dynamic list.
  27. func (s Service) DynamicList(c context.Context, arg *model.DyListArg) (dyTotal *model.DyTotal, err error) {
  28. var (
  29. list, actList []*model.DyItem
  30. mergeList []*model.DyActItem
  31. dyList *model.DyList
  32. topDy *model.DyCard
  33. dyListTs, lastCoinTs, lastLikeTs int64
  34. topErr, dyErr error
  35. hasCoin, hasDy, hasLike, top bool
  36. )
  37. fp := arg.Pn == 1
  38. repeatDyIDs := make(map[int64]int64, 1)
  39. group, errCtx := errgroup.WithContext(c)
  40. group.Go(func() error {
  41. if topDy, topErr = s.topDynamic(errCtx, arg.Vmid, arg.Qn); topErr == nil && topDy != nil {
  42. top = fp
  43. repeatDyIDs[topDy.Desc.DynamicID] = topDy.Desc.DynamicID
  44. }
  45. return nil
  46. })
  47. group.Go(func() error {
  48. if dyList, dyErr = s.dao.DynamicList(errCtx, arg.Mid, arg.Vmid, arg.DyID, arg.Qn, arg.Pn); dyErr != nil {
  49. log.Error("s.dao.DynamicList(mid:%d,vmid:%d,dyID:%d,qn:%d,pn:%d) error(%+v)", arg.Mid, arg.Vmid, arg.DyID, arg.Qn, arg.Pn, dyErr)
  50. }
  51. return nil
  52. })
  53. group.Go(func() error {
  54. lastCoinTs, lastLikeTs, mergeList = s.actList(errCtx, arg.Mid, arg.Vmid)
  55. return nil
  56. })
  57. if e := group.Wait(); e != nil {
  58. log.Error("DynamicList group.Wait mid(%d) error(%v)", arg.Vmid, e)
  59. }
  60. // rm repeat data
  61. if dyErr == nil && dyList != nil && len(dyList.Cards) > 0 {
  62. for _, v := range dyList.Cards {
  63. if _, ok := repeatDyIDs[v.Desc.DynamicID]; ok {
  64. continue
  65. }
  66. item := new(model.DyResult)
  67. item.FromCard(v)
  68. list = append(list, &model.DyItem{Type: v.Desc.Type, Card: item, Ctime: v.Desc.Timestamp})
  69. }
  70. hasDy = dyList.HasMore == 1
  71. dyListTs = dyList.Cards[len(dyList.Cards)-1].Desc.Timestamp
  72. }
  73. if len(mergeList) > 0 {
  74. hasCoin, hasLike, actList = s.filterActList(c, lastCoinTs, lastLikeTs, arg.LastTime, dyListTs, mergeList, fp)
  75. list = append(list, actList...)
  76. }
  77. sort.Slice(list, func(i, j int) bool { return list[i].Ctime > list[j].Ctime })
  78. dyTotal = new(model.DyTotal)
  79. if top {
  80. topItem := new(model.DyResult)
  81. topItem.FromCard(topDy)
  82. dyTotal.List = append(dyTotal.List, &model.DyItem{Type: topDy.Desc.Type, Top: true, Card: topItem, Ctime: topDy.Desc.Timestamp})
  83. }
  84. dyTotal.HasMore = hasDy || hasCoin || hasLike
  85. dyTotal.List = append(dyTotal.List, list...)
  86. if s.c.Rule.ActFold {
  87. dyTotal.List = foldDyActItem(dyTotal.List)
  88. }
  89. return
  90. }
  91. func (s *Service) actList(c context.Context, mid, vmid int64) (lastCoinTs, lastLikeTs int64, mergeList []*model.DyActItem) {
  92. var (
  93. coinList, likeList, preList []*model.DyActItem
  94. coinErr, likeErr error
  95. coinPcy, likePcy bool
  96. )
  97. group, errCtx := errgroup.WithContext(c)
  98. privacy := s.privacy(c, vmid)
  99. if value, ok := privacy[model.PcyCoinVideo]; ok && value != _defaultPrivacy {
  100. coinPcy = true
  101. }
  102. if value, ok := privacy[model.PcyLikeVideo]; ok && value != _defaultPrivacy {
  103. likePcy = true
  104. }
  105. // coin video
  106. if mid == vmid || !coinPcy {
  107. group.Go(func() error {
  108. coinList, coinErr = s.coinVideos(errCtx, vmid, coinPcy)
  109. return nil
  110. })
  111. }
  112. // like video
  113. if mid == vmid || !likePcy {
  114. group.Go(func() error {
  115. likeList, likeErr = s.likeVideos(errCtx, vmid, likePcy)
  116. return nil
  117. })
  118. }
  119. group.Wait()
  120. if coinErr == nil {
  121. if l := len(coinList); l > 0 {
  122. preList = append(preList, coinList...)
  123. lastCoinTs = coinList[l-1].ActionTime
  124. }
  125. }
  126. if likeErr == nil {
  127. if l := len(likeList); l > 0 {
  128. preList = append(preList, likeList...)
  129. lastLikeTs = likeList[l-1].ActionTime
  130. }
  131. }
  132. if len(preList) == 0 {
  133. return
  134. }
  135. sort.Slice(preList, func(i, j int) bool { return preList[i].ActionTime > preList[j].ActionTime })
  136. if s.c.Rule.Merge {
  137. mergeList = mergeDyActItem(preList)
  138. } else {
  139. mergeList = preList
  140. }
  141. return
  142. }
  143. func (s *Service) filterActList(c context.Context, lastCoinTs, lastLikeTs, lastTime, dyListTs int64, mergeList []*model.DyActItem, fp bool) (hasCoin, hasLike bool, list []*model.DyItem) {
  144. var (
  145. actList []*model.DyActItem
  146. actAids []int64
  147. coinTs, likeTs int64
  148. )
  149. for _, v := range mergeList {
  150. if dyListTs == 0 && len(actList) >= _dyListCnt {
  151. lastActTs := actList[len(actList)-1].ActionTime
  152. penultActTs := actList[len(actList)-2].ActionTime
  153. y1, m1, d1 := time.Unix(lastActTs, 0).Date()
  154. y2, m2, d2 := time.Unix(penultActTs, 0).Date()
  155. if d1 != d2 || m1 != m2 || y1 != y2 {
  156. actList = actList[:len(actList)-1]
  157. break
  158. }
  159. }
  160. if fp {
  161. if dyListTs > 0 {
  162. if v.ActionTime >= dyListTs {
  163. actList = append(actList, v)
  164. actAids = append(actAids, v.Aid)
  165. }
  166. } else {
  167. actList = append(actList, v)
  168. actAids = append(actAids, v.Aid)
  169. }
  170. } else {
  171. if dyListTs > 0 {
  172. if v.ActionTime >= dyListTs && v.ActionTime < lastTime {
  173. actList = append(actList, v)
  174. actAids = append(actAids, v.Aid)
  175. }
  176. } else {
  177. if v.ActionTime < lastTime {
  178. actList = append(actList, v)
  179. actAids = append(actAids, v.Aid)
  180. }
  181. }
  182. }
  183. switch v.Type {
  184. case _dyTypeCoin:
  185. coinTs = v.ActionTime
  186. case _dyTypeLike:
  187. likeTs = v.ActionTime
  188. }
  189. }
  190. if coinTs > lastCoinTs {
  191. hasCoin = true
  192. }
  193. if likeTs > lastLikeTs {
  194. hasLike = true
  195. }
  196. if arcsReply, err := s.arcClient.Arcs(c, &arcmdl.ArcsRequest{Aids: actAids}); err != nil {
  197. log.Error("DynamicList s.arcClient.Arcs(%v) error(%v)", actAids, err)
  198. } else {
  199. for _, v := range actList {
  200. if arc, ok := arcsReply.Arcs[v.Aid]; ok && arc != nil && arc.IsNormal() {
  201. video := new(model.VideoItem)
  202. video.FromArchive(arc)
  203. video.ActionTime = v.ActionTime
  204. list = append(list, &model.DyItem{Type: v.Type, Archive: video, Ctime: v.ActionTime, Privacy: v.Privacy})
  205. }
  206. }
  207. }
  208. return
  209. }
  210. func mergeDyActItem(preList []*model.DyActItem) (mergeList []*model.DyActItem) {
  211. type privacy struct {
  212. Num int
  213. Coin bool
  214. Like bool
  215. }
  216. aidNumMap := make(map[int64]*privacy, len(preList))
  217. aidExist := make(map[int64]struct{}, len(preList))
  218. for _, v := range preList {
  219. if _, exist := aidNumMap[v.Aid]; !exist {
  220. aidNumMap[v.Aid] = new(privacy)
  221. }
  222. aidNumMap[v.Aid].Num++
  223. switch v.Type {
  224. case _dyTypeCoin:
  225. aidNumMap[v.Aid].Coin = v.Privacy
  226. case _dyTypeLike:
  227. aidNumMap[v.Aid].Like = v.Privacy
  228. }
  229. }
  230. for _, v := range preList {
  231. num := aidNumMap[v.Aid].Num
  232. if num > 1 {
  233. if _, ok := aidExist[v.Aid]; !ok {
  234. v.Type = _dyTypeMerge
  235. v.Privacy = aidNumMap[v.Aid].Coin && aidNumMap[v.Aid].Like
  236. mergeList = append(mergeList, v)
  237. }
  238. aidExist[v.Aid] = struct{}{}
  239. } else {
  240. mergeList = append(mergeList, v)
  241. }
  242. }
  243. return
  244. }
  245. func foldDyActItem(list []*model.DyItem) (foldList []*model.DyItem) {
  246. l := len(list)
  247. if l == 0 {
  248. foldList = make([]*model.DyItem, 0)
  249. return
  250. }
  251. if l < _dyFoldNum {
  252. foldList = list
  253. return
  254. }
  255. var preCk bool
  256. for index, v := range list {
  257. if index == 0 {
  258. foldList = append(foldList, v)
  259. continue
  260. }
  261. last := index == l-1
  262. if index >= _dyFoldNum-1 {
  263. _, tpCheck := dyTypeFoldMap[v.Type]
  264. y1, m1, d1 := time.Unix(v.Ctime, 0).Date()
  265. _, preTpCheck := dyTypeFoldMap[list[index-1].Type]
  266. y2, m2, d2 := time.Unix(list[index-1].Ctime, 0).Date()
  267. _, check := dyTypeFoldMap[list[index-2].Type]
  268. y3, m3, d3 := time.Unix(list[index-2].Ctime, 0).Date()
  269. ck := tpCheck && preTpCheck && check && (y1 == y2 && m1 == m2 && d1 == d2) && (y1 == y3 && m1 == m3 && d1 == d3)
  270. // append pre item to fold if ck or preCk
  271. if ck || preCk {
  272. foldList[len(foldList)-1].Fold = append(foldList[len(foldList)-1].Fold, list[index-1])
  273. if last {
  274. foldList[len(foldList)-1].Fold = append(foldList[len(foldList)-1].Fold, v)
  275. }
  276. } else {
  277. foldList = append(foldList, list[index-1])
  278. if last {
  279. foldList = append(foldList, v)
  280. }
  281. }
  282. preCk = ck
  283. }
  284. }
  285. return foldList
  286. }
  287. func (s *Service) coinVideos(c context.Context, vmid int64, pcy bool) (list []*model.DyActItem, err error) {
  288. var (
  289. coinReply *coinmdl.ListReply
  290. aids []int64
  291. )
  292. if coinReply, err = s.coinClient.List(c, &coinmdl.ListReq{Mid: vmid, Business: _businessCoin, Ts: time.Now().Unix()}); err != nil {
  293. log.Error("s.coinClient.List(%d) error(%v)", vmid, err)
  294. return
  295. }
  296. existArcs := make(map[int64]*coinmdl.ModelList, len(coinReply.List))
  297. for _, v := range coinReply.List {
  298. if len(aids) > _coinVideoLimit {
  299. break
  300. }
  301. if _, ok := existArcs[v.Aid]; ok {
  302. continue
  303. }
  304. if v.Aid > 0 {
  305. list = append(list, &model.DyActItem{Aid: v.Aid, Type: _dyTypeCoin, ActionTime: v.Ts, Privacy: pcy})
  306. existArcs[v.Aid] = v
  307. }
  308. }
  309. return
  310. }
  311. func (s *Service) likeVideos(c context.Context, mid int64, pcy bool) (list []*model.DyActItem, err error) {
  312. var (
  313. likes *thumbup.UserTotalLike
  314. ip = metadata.String(c, metadata.RemoteIP)
  315. )
  316. arg := &thumbup.ArgUserLikes{Mid: mid, Business: _businessLike, Pn: 1, Ps: _likeVideoCnt, RealIP: ip}
  317. if likes, err = s.thumbup.UserTotalLike(c, arg); err != nil {
  318. log.Error("s.thumbup.UserTotalLike(%d) error(%v)", mid, err)
  319. return
  320. }
  321. if likes != nil {
  322. for _, v := range likes.List {
  323. if v.MessageID > 0 {
  324. list = append(list, &model.DyActItem{Aid: v.MessageID, Type: _dyTypeLike, ActionTime: int64(v.Time), Privacy: pcy})
  325. }
  326. }
  327. }
  328. return
  329. }
  330. // topDynamic get top dynamic.
  331. func (s *Service) topDynamic(c context.Context, mid int64, qn int) (res *model.DyCard, err error) {
  332. var (
  333. dyID int64
  334. )
  335. if dyID, err = s.dao.TopDynamic(c, mid); err != nil {
  336. return
  337. }
  338. if dyID == 0 {
  339. err = ecode.NothingFound
  340. return
  341. }
  342. if res, err = s.dao.Dynamic(c, mid, dyID, qn); err != nil || res == nil {
  343. log.Error("Dynamic s.dao.Dynamic mid(%d) dyID(%d) error(%v)", mid, dyID, err)
  344. err = ecode.NothingFound
  345. }
  346. return
  347. }
  348. // SetTopDynamic set top dynamic.
  349. func (s *Service) SetTopDynamic(c context.Context, mid, dynamicID int64) (err error) {
  350. var (
  351. dynamic *model.DyCard
  352. preDyID int64
  353. )
  354. if dynamic, err = s.dao.Dynamic(c, mid, dynamicID, _dyDefaultQn); err != nil || dynamic == nil {
  355. log.Error("SetTopDynamic s.dao.Dynamic(%d) error(%v)", dynamicID, err)
  356. return
  357. }
  358. if dynamic.Desc.UID != mid {
  359. err = ecode.RequestErr
  360. return
  361. }
  362. if preDyID, err = s.dao.TopDynamic(c, mid); err != nil {
  363. return
  364. }
  365. if preDyID == dynamicID {
  366. err = ecode.NotModified
  367. return
  368. }
  369. if err = s.dao.AddTopDynamic(c, mid, dynamicID); err == nil {
  370. s.dao.AddCacheTopDynamic(c, mid, dynamicID)
  371. }
  372. return
  373. }
  374. // CancelTopDynamic cancel top dynamic.
  375. func (s *Service) CancelTopDynamic(c context.Context, mid int64, now time.Time) (err error) {
  376. var dyID int64
  377. if dyID, err = s.dao.TopDynamic(c, mid); err != nil {
  378. return
  379. }
  380. if dyID == 0 {
  381. err = ecode.RequestErr
  382. return
  383. }
  384. if err = s.dao.DelTopDynamic(c, mid, now); err == nil {
  385. s.dao.AddCacheTopDynamic(c, mid, -1)
  386. }
  387. return
  388. }