recommends.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. package service
  2. import (
  3. "context"
  4. "math/rand"
  5. "sort"
  6. "sync"
  7. "time"
  8. "go-common/app/interface/openplatform/article/dao"
  9. "go-common/app/interface/openplatform/article/model"
  10. "go-common/library/ecode"
  11. "go-common/library/log"
  12. "go-common/library/sync/errgroup"
  13. )
  14. var (
  15. _recommendCategory = int64(0)
  16. )
  17. // Recommends list recommend arts by category id
  18. func (s *Service) Recommends(c context.Context, cid int64, pn, ps int, lastAids []int64, sort int) (res []*model.RecommendArt, err error) {
  19. var (
  20. start = (pn - 1) * ps
  21. // 多取一些用于去重
  22. end = start + ps - 1 + len(lastAids)
  23. rems = make(map[int64]*model.Recommend)
  24. allIDs, aids []int64
  25. metas map[int64]*model.Meta
  26. aidsm map[int64]bool
  27. withRecommend bool
  28. )
  29. if cid != _recommendCategory {
  30. if (s.categoriesMap == nil) || (s.categoriesMap[cid] == nil) {
  31. err = ecode.RequestErr
  32. return
  33. }
  34. }
  35. if sort == model.FieldDefault {
  36. withRecommend = true
  37. sort = model.FieldNew
  38. }
  39. allRec := s.recommendAids[cid]
  40. var recommends [][]*model.Recommend
  41. if cid == _recommendCategory {
  42. recommends = s.genRecommendArtFromPool(s.RecommendsMap[cid], s.c.Article.RecommendRegionLen)
  43. } else {
  44. recommends = s.RecommendsMap[cid]
  45. }
  46. recommendsLen := len(recommends)
  47. // 只是最新文章 无推荐
  48. if (start >= recommendsLen) || !withRecommend {
  49. if (cid == _recommendCategory) && !s.setting.ShowRecommendNewArticles {
  50. return
  51. }
  52. var (
  53. nids []int64
  54. newArtStart = start
  55. newArtEnd = end
  56. )
  57. if withRecommend {
  58. newArtStart = start - recommendsLen
  59. newArtEnd = end - recommendsLen
  60. }
  61. nids, _ = s.dao.SortCache(c, cid, sort, newArtStart, newArtEnd)
  62. if withRecommend {
  63. allIDs = uniqIDs(nids, allRec)
  64. } else {
  65. allIDs = nids
  66. }
  67. } else {
  68. aids, rems = s.dealRecommends(recommends)
  69. if end < recommendsLen {
  70. allIDs = aids[start : end+1]
  71. } else {
  72. if (cid == _recommendCategory) && !s.setting.ShowRecommendNewArticles {
  73. allIDs = aids[start:]
  74. } else {
  75. // 混合推荐和最新文章
  76. var (
  77. nids []int64
  78. rs = aids[start:]
  79. )
  80. newArtStart := 0
  81. newArtEnd := (end - start) - len(rs)
  82. nids, _ = s.dao.SortCache(c, cid, sort, newArtStart, newArtEnd)
  83. nids = uniqIDs(nids, allRec)
  84. allIDs = append(rs, nids...)
  85. }
  86. }
  87. }
  88. if len(allIDs) == 0 {
  89. return
  90. }
  91. if metas, err = s.ArticleMetas(c, allIDs); err != nil {
  92. return
  93. }
  94. //过滤禁止显示的稿件
  95. filterNoDistributeArtsMap(metas)
  96. filterNoRegionArts(metas)
  97. //填充数据
  98. aidsm = make(map[int64]bool, len(lastAids))
  99. for _, aid := range lastAids {
  100. aidsm[aid] = true
  101. }
  102. for _, id := range allIDs {
  103. if (metas == nil) || (metas[id] == nil) || aidsm[id] {
  104. continue
  105. }
  106. art := &model.RecommendArt{Meta: *metas[id]}
  107. if rems[id] != nil {
  108. art.Recommend = *rems[id]
  109. }
  110. res = append(res, art)
  111. }
  112. //截断分页数据
  113. if ps > len(res) {
  114. ps = len(res)
  115. }
  116. res = res[:ps]
  117. if cid == _recommendCategory {
  118. sortRecs(res)
  119. }
  120. return
  121. }
  122. func (s *Service) dealRecommends(recommends [][]*model.Recommend) (aids []int64, rems map[int64]*model.Recommend) {
  123. rems = make(map[int64]*model.Recommend)
  124. for _, recs := range recommends {
  125. rec := &model.Recommend{}
  126. *rec = *recs[s.randPosition(len(recs))]
  127. aids = append(aids, rec.ArticleID)
  128. // 不在推荐大图时间 去掉大图
  129. if rec.RecImageURL != "" {
  130. var now = time.Now().Unix()
  131. if (now < rec.RecImageStartTime) || (now > rec.RecImageEndTime) {
  132. rec.RecImageURL = ""
  133. rec.RecFlag = false
  134. }
  135. }
  136. if rec.RecFlag {
  137. rec.RecText = "编辑推荐"
  138. }
  139. rems[rec.ArticleID] = rec
  140. // 推荐文章id置空
  141. rec.ArticleID = 0
  142. }
  143. return
  144. }
  145. // 过滤禁止分区投稿
  146. func filterNoRegionArts(as map[int64]*model.Meta) {
  147. for id, a := range as {
  148. if (a != nil) && a.AttrVal(model.AttrBitNoRegion) {
  149. delete(as, id)
  150. }
  151. }
  152. }
  153. // 按照发布时间排序
  154. func sortRecs(res []*model.RecommendArt) {
  155. if len(res) == 0 {
  156. return
  157. }
  158. var len int
  159. for i, v := range res {
  160. if v.Rec {
  161. len = i
  162. }
  163. }
  164. sort.Slice(res[:len+1], func(i, j int) bool { return res[i].PublishTime > res[j].PublishTime })
  165. }
  166. // array a - array b
  167. func uniqIDs(a []int64, b []int64) (res []int64) {
  168. bm := make(map[int64]bool)
  169. for _, v := range b {
  170. bm[v] = true
  171. }
  172. for _, v := range a {
  173. if !bm[v] {
  174. res = append(res, v)
  175. }
  176. }
  177. return
  178. }
  179. // UpdateRecommends update recommends
  180. func (s *Service) UpdateRecommends(c context.Context) (err error) {
  181. var (
  182. recommendsMap = make(map[int64][][]*model.Recommend)
  183. recommendAids = make(map[int64][]int64)
  184. mutex = &sync.Mutex{}
  185. )
  186. group, ctx := errgroup.WithContext(c)
  187. group.Go(func() error {
  188. recommends, err1 := s.dao.RecommendByCategory(ctx, _recommendCategory)
  189. if err1 != nil {
  190. return err1
  191. }
  192. // 推荐分区无位置 为推荐池
  193. rs := [][]*model.Recommend{recommends}
  194. mutex.Lock()
  195. recommendsMap[_recommendCategory] = rs
  196. mutex.Unlock()
  197. return nil
  198. })
  199. for _, category := range s.categoriesMap {
  200. category := category
  201. group.Go(func() error {
  202. recommends, err1 := s.dao.RecommendByCategory(ctx, category.ID)
  203. if err1 != nil {
  204. return err1
  205. }
  206. rs := calculateRecommends(recommends)
  207. mutex.Lock()
  208. recommendsMap[category.ID] = rs
  209. mutex.Unlock()
  210. return nil
  211. })
  212. }
  213. if err = group.Wait(); err != nil {
  214. return
  215. }
  216. s.RecommendsMap = recommendsMap
  217. for cid, v := range recommendsMap {
  218. for _, vv := range v {
  219. for _, vvv := range vv {
  220. recommendAids[cid] = append(recommendAids[cid], vvv.ArticleID)
  221. }
  222. }
  223. }
  224. s.recommendAids = recommendAids
  225. log.Info("s.UpdateRecommends success! len:(%v)", len(recommendsMap))
  226. return
  227. }
  228. func calculateRecommends(rs []*model.Recommend) (res [][]*model.Recommend) {
  229. m := make(map[int][]*model.Recommend)
  230. // 位置去重+ 随机选择
  231. for _, r := range rs {
  232. if r == nil {
  233. continue
  234. }
  235. if len(m[r.Position]) == 0 {
  236. m[r.Position] = append(m[r.Position], r)
  237. } else {
  238. var endTime bool
  239. for _, x := range m[r.Position] {
  240. if x.EndTime != 0 {
  241. endTime = true
  242. break
  243. }
  244. }
  245. if endTime {
  246. if r.EndTime == 0 {
  247. continue
  248. } else {
  249. m[r.Position] = append(m[r.Position], r)
  250. }
  251. } else {
  252. if r.EndTime == 0 {
  253. m[r.Position] = append(m[r.Position], r)
  254. } else {
  255. m[r.Position] = []*model.Recommend{r}
  256. }
  257. }
  258. }
  259. }
  260. for _, recommends := range m {
  261. res = append(res, recommends)
  262. }
  263. sort.Sort(model.Recommends(res))
  264. return
  265. }
  266. func (s *Service) randPosition(max int) (res int) {
  267. res = rand.Intn(max)
  268. return
  269. }
  270. func (s *Service) genRecommendArtFromPool(rs [][]*model.Recommend, recLen int) (res [][]*model.Recommend) {
  271. var pool []*model.Recommend
  272. if len(rs) > 0 {
  273. pool = rs[0]
  274. }
  275. if len(pool) == 0 {
  276. return
  277. }
  278. recs := append([]*model.Recommend{}, pool...)
  279. for i := range recs {
  280. j := rand.Intn(i + 1)
  281. recs[i], recs[j] = recs[j], recs[i]
  282. }
  283. if len(recs) < recLen {
  284. recLen = len(recs)
  285. }
  286. for _, r := range recs[:recLen] {
  287. res = append(res, []*model.Recommend{r})
  288. }
  289. return
  290. }
  291. // DelRecommendArtCache delete recommend article cache
  292. func (s *Service) DelRecommendArtCache(c context.Context, aid, cid int64) (err error) {
  293. s.DelRecommendArt(_recommendCategory, aid)
  294. if cid, err = s.CategoryToRoot(cid); err != nil {
  295. dao.PromError("cache:删除文章推荐缓存")
  296. log.Error("s.DelRecommendArtCache.RootCategory(c, %v, %v) err: %+v", aid, cid, err)
  297. return
  298. }
  299. s.DelRecommendArt(cid, aid)
  300. return
  301. }
  302. // DelRecommendArt delete recommend article
  303. func (s *Service) DelRecommendArt(categoryID int64, aid int64) {
  304. select {
  305. case s.recommendChan <- [2]int64{categoryID, aid}:
  306. default:
  307. dao.PromError("recommends:删除推荐文章 chan已满")
  308. log.Error("s.DelRecommendArt(%v, %v) chan full!", categoryID, aid)
  309. }
  310. }
  311. func (s *Service) deleteRecommendproc() {
  312. for {
  313. info, ok := <-s.recommendChan
  314. if !ok {
  315. return
  316. }
  317. if s.RecommendsMap == nil {
  318. continue
  319. }
  320. categoryID, aid := info[0], info[1]
  321. newRecommendsMap := map[int64][][]*model.Recommend{}
  322. for cid, rss := range s.RecommendsMap {
  323. if cid != categoryID {
  324. newRecommendsMap[cid] = rss
  325. continue
  326. }
  327. var newRecommends [][]*model.Recommend
  328. for _, rs := range rss {
  329. var newRs []*model.Recommend
  330. for _, r := range rs {
  331. if r.ArticleID != aid {
  332. newRs = append(newRs, r)
  333. }
  334. }
  335. if len(newRs) > 0 {
  336. newRecommends = append(newRecommends, newRs)
  337. }
  338. }
  339. newRecommendsMap[cid] = newRecommends
  340. }
  341. s.RecommendsMap = newRecommendsMap
  342. }
  343. }
  344. // RecommendHome recommend home
  345. func (s *Service) RecommendHome(c context.Context, plat int8, build int, pn, ps int, aids []int64, mid int64, ip string, t time.Time, buvid string) (res *model.RecommendHome, sky *model.SkyHorseResp, err error) {
  346. res = &model.RecommendHome{IP: ip, Categories: s.primaryCategories}
  347. plus, sky, err := s.RecommendPlus(c, _recommendCategory, plat, build, pn, ps, aids, mid, t, model.FieldDefault, buvid)
  348. if plus != nil {
  349. res.RecommendPlus = *plus
  350. }
  351. return
  352. }
  353. // RecommendPlus recommend plus
  354. func (s *Service) RecommendPlus(c context.Context, cid int64, plat int8, build int, pn, ps int, aids []int64, mid int64, t time.Time, sort int, buvid string) (res *model.RecommendPlus, sky *model.SkyHorseResp, err error) {
  355. res = &model.RecommendPlus{Banners: []*model.Banner{}, Articles: []*model.RecommendArtWithLike{}, Ranks: []*model.RankMeta{}, Hotspots: []*model.Hotspot{}}
  356. var group *errgroup.Group
  357. group, _ = errgroup.WithContext(c)
  358. group.Go(func() error {
  359. var arts []*model.RecommendArtWithLike
  360. if arts, sky, err = s.SkyHorse(c, cid, pn, ps, aids, sort, mid, build, buvid, plat); err == nil {
  361. res.Articles = arts
  362. }
  363. return nil
  364. })
  365. group.Go(func() error {
  366. if bs, e := s.Banners(c, plat, build, t); (e == nil) && (len(bs) > 0) {
  367. res.Banners = bs
  368. }
  369. return nil
  370. })
  371. group.Go(func() error {
  372. if s.setting.ShowHotspot {
  373. if hs, _ := s.dao.CacheHotspots(c); len(hs) > 0 {
  374. res.Hotspots = hs
  375. }
  376. }
  377. return nil
  378. })
  379. group.Go(func() error {
  380. if !s.setting.ShowAppHomeRank {
  381. return nil
  382. }
  383. if ranks, _, err := s.Ranks(c, model.RankWeek, mid, ""); (err == nil) && (len(ranks) > 0) {
  384. if len(ranks) > 3 {
  385. ranks = ranks[:3]
  386. }
  387. res.Ranks = ranks
  388. }
  389. return nil
  390. })
  391. group.Wait()
  392. return
  393. }
  394. // AllRecommends all recommends articles
  395. func (s *Service) AllRecommends(c context.Context, pn, ps int) (count int64, res []*model.Meta, err error) {
  396. if pn < 1 {
  397. pn = 1
  398. }
  399. t := time.Now()
  400. count, _ = s.dao.AllRecommendCount(c, t)
  401. res = []*model.Meta{}
  402. ids, err := s.dao.AllRecommends(c, t, pn, ps)
  403. if err != nil {
  404. return
  405. }
  406. if len(ids) == 0 {
  407. return
  408. }
  409. metas, err := s.ArticleMetas(c, ids)
  410. if err != nil {
  411. return
  412. }
  413. for _, id := range ids {
  414. if metas[id] != nil {
  415. res = append(res, metas[id])
  416. }
  417. }
  418. return
  419. }
  420. // SkyHorse .
  421. func (s *Service) SkyHorse(c context.Context, cid int64, pn, ps int, lastAids []int64, sort int, mid int64, build int, buvid string, plat int8) (res []*model.RecommendArtWithLike, sky *model.SkyHorseResp, err error) {
  422. if (cid != _recommendCategory) || !s.skyHorseGray(buvid, mid) {
  423. res, err = s.RecommendsWithLike(c, cid, pn, ps, lastAids, sort, mid)
  424. return
  425. }
  426. var aids []int64
  427. var metas map[int64]*model.Meta
  428. var rems map[int64]*model.Recommend
  429. if pn == 1 {
  430. size := ps
  431. if size > s.c.Article.SkyHorseRecommendRegionLen {
  432. size = s.c.Article.SkyHorseRecommendRegionLen
  433. }
  434. recommends := s.genRecommendArtFromPool(s.RecommendsMap[_recommendCategory], size)
  435. aids, rems = s.dealRecommends(recommends)
  436. }
  437. if len(aids) < ps {
  438. sky, err = s.dao.SkyHorse(c, mid, build, buvid, plat, ps-len(aids))
  439. if (err != nil) || (len(sky.Data) == 0) {
  440. res, err = s.RecommendsWithLike(c, cid, pn, ps, lastAids, sort, mid)
  441. sky = nil
  442. return
  443. }
  444. for _, item := range sky.Data {
  445. if rems[item.ID] == nil {
  446. aids = append(aids, item.ID)
  447. }
  448. }
  449. }
  450. if metas, err = s.ArticleMetas(c, aids); err != nil {
  451. return
  452. }
  453. //过滤禁止显示的稿件
  454. filterNoDistributeArtsMap(metas)
  455. filterNoRegionArts(metas)
  456. states, _ := s.HadLikesByMid(c, mid, aids)
  457. for _, aid := range aids {
  458. if metas[aid] == nil {
  459. continue
  460. }
  461. art := model.RecommendArt{Meta: *metas[aid]}
  462. r := &model.RecommendArtWithLike{RecommendArt: art}
  463. if states != nil {
  464. r.LikeState = int(states[aid])
  465. }
  466. if rems[aid] != nil {
  467. r.Recommend = *rems[aid]
  468. }
  469. res = append(res, r)
  470. }
  471. return
  472. }
  473. func (s *Service) skyHorseGray(buvid string, mid int64) bool {
  474. if (mid == 0) && (buvid == "") {
  475. return false
  476. }
  477. for _, id := range s.c.Article.SkyHorseGrayUsers {
  478. if mid == id {
  479. return true
  480. }
  481. }
  482. for _, id := range s.c.Article.SkyHorseGray {
  483. if mid%10 == id {
  484. return true
  485. }
  486. }
  487. return false
  488. }
  489. func (s *Service) groupRecommend(c context.Context) (err error) {
  490. var (
  491. m = make(map[int64]map[int64]bool)
  492. mutex = &sync.Mutex{}
  493. )
  494. for _, recommends := range s.RecommendsMap {
  495. var (
  496. rs []*model.Recommend
  497. arts map[int64]*model.Meta
  498. aids = []int64{}
  499. )
  500. if len(recommends) > 0 {
  501. rs = recommends[0]
  502. }
  503. for _, r := range rs {
  504. aids = append(aids, r.ArticleID)
  505. }
  506. if arts, err = s.ArticleMetas(c, aids); err != nil || arts == nil {
  507. return
  508. }
  509. for _, art := range arts {
  510. if _, ok := m[art.Category.ID]; !ok {
  511. m[art.Category.ID] = make(map[int64]bool)
  512. }
  513. m[art.Category.ID][art.ID] = true
  514. }
  515. }
  516. mutex.Lock()
  517. s.RecommendsGroups = m
  518. mutex.Unlock()
  519. return
  520. }
  521. func (s *Service) getRecommentsGroups(c context.Context, cid int64, aid int64) (res []int64) {
  522. for i := range s.RecommendsGroups[cid] {
  523. if i != aid {
  524. res = append(res, i)
  525. }
  526. }
  527. return
  528. }