123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- package service
- import (
- "context"
- "math/rand"
- "sort"
- "sync"
- "time"
- "go-common/app/interface/openplatform/article/dao"
- "go-common/app/interface/openplatform/article/model"
- "go-common/library/ecode"
- "go-common/library/log"
- "go-common/library/sync/errgroup"
- )
- var (
- _recommendCategory = int64(0)
- )
- // Recommends list recommend arts by category id
- func (s *Service) Recommends(c context.Context, cid int64, pn, ps int, lastAids []int64, sort int) (res []*model.RecommendArt, err error) {
- var (
- start = (pn - 1) * ps
- // 多取一些用于去重
- end = start + ps - 1 + len(lastAids)
- rems = make(map[int64]*model.Recommend)
- allIDs, aids []int64
- metas map[int64]*model.Meta
- aidsm map[int64]bool
- withRecommend bool
- )
- if cid != _recommendCategory {
- if (s.categoriesMap == nil) || (s.categoriesMap[cid] == nil) {
- err = ecode.RequestErr
- return
- }
- }
- if sort == model.FieldDefault {
- withRecommend = true
- sort = model.FieldNew
- }
- allRec := s.recommendAids[cid]
- var recommends [][]*model.Recommend
- if cid == _recommendCategory {
- recommends = s.genRecommendArtFromPool(s.RecommendsMap[cid], s.c.Article.RecommendRegionLen)
- } else {
- recommends = s.RecommendsMap[cid]
- }
- recommendsLen := len(recommends)
- // 只是最新文章 无推荐
- if (start >= recommendsLen) || !withRecommend {
- if (cid == _recommendCategory) && !s.setting.ShowRecommendNewArticles {
- return
- }
- var (
- nids []int64
- newArtStart = start
- newArtEnd = end
- )
- if withRecommend {
- newArtStart = start - recommendsLen
- newArtEnd = end - recommendsLen
- }
- nids, _ = s.dao.SortCache(c, cid, sort, newArtStart, newArtEnd)
- if withRecommend {
- allIDs = uniqIDs(nids, allRec)
- } else {
- allIDs = nids
- }
- } else {
- aids, rems = s.dealRecommends(recommends)
- if end < recommendsLen {
- allIDs = aids[start : end+1]
- } else {
- if (cid == _recommendCategory) && !s.setting.ShowRecommendNewArticles {
- allIDs = aids[start:]
- } else {
- // 混合推荐和最新文章
- var (
- nids []int64
- rs = aids[start:]
- )
- newArtStart := 0
- newArtEnd := (end - start) - len(rs)
- nids, _ = s.dao.SortCache(c, cid, sort, newArtStart, newArtEnd)
- nids = uniqIDs(nids, allRec)
- allIDs = append(rs, nids...)
- }
- }
- }
- if len(allIDs) == 0 {
- return
- }
- if metas, err = s.ArticleMetas(c, allIDs); err != nil {
- return
- }
- //过滤禁止显示的稿件
- filterNoDistributeArtsMap(metas)
- filterNoRegionArts(metas)
- //填充数据
- aidsm = make(map[int64]bool, len(lastAids))
- for _, aid := range lastAids {
- aidsm[aid] = true
- }
- for _, id := range allIDs {
- if (metas == nil) || (metas[id] == nil) || aidsm[id] {
- continue
- }
- art := &model.RecommendArt{Meta: *metas[id]}
- if rems[id] != nil {
- art.Recommend = *rems[id]
- }
- res = append(res, art)
- }
- //截断分页数据
- if ps > len(res) {
- ps = len(res)
- }
- res = res[:ps]
- if cid == _recommendCategory {
- sortRecs(res)
- }
- return
- }
- func (s *Service) dealRecommends(recommends [][]*model.Recommend) (aids []int64, rems map[int64]*model.Recommend) {
- rems = make(map[int64]*model.Recommend)
- for _, recs := range recommends {
- rec := &model.Recommend{}
- *rec = *recs[s.randPosition(len(recs))]
- aids = append(aids, rec.ArticleID)
- // 不在推荐大图时间 去掉大图
- if rec.RecImageURL != "" {
- var now = time.Now().Unix()
- if (now < rec.RecImageStartTime) || (now > rec.RecImageEndTime) {
- rec.RecImageURL = ""
- rec.RecFlag = false
- }
- }
- if rec.RecFlag {
- rec.RecText = "编辑推荐"
- }
- rems[rec.ArticleID] = rec
- // 推荐文章id置空
- rec.ArticleID = 0
- }
- return
- }
- // 过滤禁止分区投稿
- func filterNoRegionArts(as map[int64]*model.Meta) {
- for id, a := range as {
- if (a != nil) && a.AttrVal(model.AttrBitNoRegion) {
- delete(as, id)
- }
- }
- }
- // 按照发布时间排序
- func sortRecs(res []*model.RecommendArt) {
- if len(res) == 0 {
- return
- }
- var len int
- for i, v := range res {
- if v.Rec {
- len = i
- }
- }
- sort.Slice(res[:len+1], func(i, j int) bool { return res[i].PublishTime > res[j].PublishTime })
- }
- // array a - array b
- func uniqIDs(a []int64, b []int64) (res []int64) {
- bm := make(map[int64]bool)
- for _, v := range b {
- bm[v] = true
- }
- for _, v := range a {
- if !bm[v] {
- res = append(res, v)
- }
- }
- return
- }
- // UpdateRecommends update recommends
- func (s *Service) UpdateRecommends(c context.Context) (err error) {
- var (
- recommendsMap = make(map[int64][][]*model.Recommend)
- recommendAids = make(map[int64][]int64)
- mutex = &sync.Mutex{}
- )
- group, ctx := errgroup.WithContext(c)
- group.Go(func() error {
- recommends, err1 := s.dao.RecommendByCategory(ctx, _recommendCategory)
- if err1 != nil {
- return err1
- }
- // 推荐分区无位置 为推荐池
- rs := [][]*model.Recommend{recommends}
- mutex.Lock()
- recommendsMap[_recommendCategory] = rs
- mutex.Unlock()
- return nil
- })
- for _, category := range s.categoriesMap {
- category := category
- group.Go(func() error {
- recommends, err1 := s.dao.RecommendByCategory(ctx, category.ID)
- if err1 != nil {
- return err1
- }
- rs := calculateRecommends(recommends)
- mutex.Lock()
- recommendsMap[category.ID] = rs
- mutex.Unlock()
- return nil
- })
- }
- if err = group.Wait(); err != nil {
- return
- }
- s.RecommendsMap = recommendsMap
- for cid, v := range recommendsMap {
- for _, vv := range v {
- for _, vvv := range vv {
- recommendAids[cid] = append(recommendAids[cid], vvv.ArticleID)
- }
- }
- }
- s.recommendAids = recommendAids
- log.Info("s.UpdateRecommends success! len:(%v)", len(recommendsMap))
- return
- }
- func calculateRecommends(rs []*model.Recommend) (res [][]*model.Recommend) {
- m := make(map[int][]*model.Recommend)
- // 位置去重+ 随机选择
- for _, r := range rs {
- if r == nil {
- continue
- }
- if len(m[r.Position]) == 0 {
- m[r.Position] = append(m[r.Position], r)
- } else {
- var endTime bool
- for _, x := range m[r.Position] {
- if x.EndTime != 0 {
- endTime = true
- break
- }
- }
- if endTime {
- if r.EndTime == 0 {
- continue
- } else {
- m[r.Position] = append(m[r.Position], r)
- }
- } else {
- if r.EndTime == 0 {
- m[r.Position] = append(m[r.Position], r)
- } else {
- m[r.Position] = []*model.Recommend{r}
- }
- }
- }
- }
- for _, recommends := range m {
- res = append(res, recommends)
- }
- sort.Sort(model.Recommends(res))
- return
- }
- func (s *Service) randPosition(max int) (res int) {
- res = rand.Intn(max)
- return
- }
- func (s *Service) genRecommendArtFromPool(rs [][]*model.Recommend, recLen int) (res [][]*model.Recommend) {
- var pool []*model.Recommend
- if len(rs) > 0 {
- pool = rs[0]
- }
- if len(pool) == 0 {
- return
- }
- recs := append([]*model.Recommend{}, pool...)
- for i := range recs {
- j := rand.Intn(i + 1)
- recs[i], recs[j] = recs[j], recs[i]
- }
- if len(recs) < recLen {
- recLen = len(recs)
- }
- for _, r := range recs[:recLen] {
- res = append(res, []*model.Recommend{r})
- }
- return
- }
- // DelRecommendArtCache delete recommend article cache
- func (s *Service) DelRecommendArtCache(c context.Context, aid, cid int64) (err error) {
- s.DelRecommendArt(_recommendCategory, aid)
- if cid, err = s.CategoryToRoot(cid); err != nil {
- dao.PromError("cache:删除文章推荐缓存")
- log.Error("s.DelRecommendArtCache.RootCategory(c, %v, %v) err: %+v", aid, cid, err)
- return
- }
- s.DelRecommendArt(cid, aid)
- return
- }
- // DelRecommendArt delete recommend article
- func (s *Service) DelRecommendArt(categoryID int64, aid int64) {
- select {
- case s.recommendChan <- [2]int64{categoryID, aid}:
- default:
- dao.PromError("recommends:删除推荐文章 chan已满")
- log.Error("s.DelRecommendArt(%v, %v) chan full!", categoryID, aid)
- }
- }
- func (s *Service) deleteRecommendproc() {
- for {
- info, ok := <-s.recommendChan
- if !ok {
- return
- }
- if s.RecommendsMap == nil {
- continue
- }
- categoryID, aid := info[0], info[1]
- newRecommendsMap := map[int64][][]*model.Recommend{}
- for cid, rss := range s.RecommendsMap {
- if cid != categoryID {
- newRecommendsMap[cid] = rss
- continue
- }
- var newRecommends [][]*model.Recommend
- for _, rs := range rss {
- var newRs []*model.Recommend
- for _, r := range rs {
- if r.ArticleID != aid {
- newRs = append(newRs, r)
- }
- }
- if len(newRs) > 0 {
- newRecommends = append(newRecommends, newRs)
- }
- }
- newRecommendsMap[cid] = newRecommends
- }
- s.RecommendsMap = newRecommendsMap
- }
- }
- // RecommendHome recommend home
- 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) {
- res = &model.RecommendHome{IP: ip, Categories: s.primaryCategories}
- plus, sky, err := s.RecommendPlus(c, _recommendCategory, plat, build, pn, ps, aids, mid, t, model.FieldDefault, buvid)
- if plus != nil {
- res.RecommendPlus = *plus
- }
- return
- }
- // RecommendPlus recommend plus
- 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) {
- res = &model.RecommendPlus{Banners: []*model.Banner{}, Articles: []*model.RecommendArtWithLike{}, Ranks: []*model.RankMeta{}, Hotspots: []*model.Hotspot{}}
- var group *errgroup.Group
- group, _ = errgroup.WithContext(c)
- group.Go(func() error {
- var arts []*model.RecommendArtWithLike
- if arts, sky, err = s.SkyHorse(c, cid, pn, ps, aids, sort, mid, build, buvid, plat); err == nil {
- res.Articles = arts
- }
- return nil
- })
- group.Go(func() error {
- if bs, e := s.Banners(c, plat, build, t); (e == nil) && (len(bs) > 0) {
- res.Banners = bs
- }
- return nil
- })
- group.Go(func() error {
- if s.setting.ShowHotspot {
- if hs, _ := s.dao.CacheHotspots(c); len(hs) > 0 {
- res.Hotspots = hs
- }
- }
- return nil
- })
- group.Go(func() error {
- if !s.setting.ShowAppHomeRank {
- return nil
- }
- if ranks, _, err := s.Ranks(c, model.RankWeek, mid, ""); (err == nil) && (len(ranks) > 0) {
- if len(ranks) > 3 {
- ranks = ranks[:3]
- }
- res.Ranks = ranks
- }
- return nil
- })
- group.Wait()
- return
- }
- // AllRecommends all recommends articles
- func (s *Service) AllRecommends(c context.Context, pn, ps int) (count int64, res []*model.Meta, err error) {
- if pn < 1 {
- pn = 1
- }
- t := time.Now()
- count, _ = s.dao.AllRecommendCount(c, t)
- res = []*model.Meta{}
- ids, err := s.dao.AllRecommends(c, t, pn, ps)
- if err != nil {
- return
- }
- if len(ids) == 0 {
- return
- }
- metas, err := s.ArticleMetas(c, ids)
- if err != nil {
- return
- }
- for _, id := range ids {
- if metas[id] != nil {
- res = append(res, metas[id])
- }
- }
- return
- }
- // SkyHorse .
- 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) {
- if (cid != _recommendCategory) || !s.skyHorseGray(buvid, mid) {
- res, err = s.RecommendsWithLike(c, cid, pn, ps, lastAids, sort, mid)
- return
- }
- var aids []int64
- var metas map[int64]*model.Meta
- var rems map[int64]*model.Recommend
- if pn == 1 {
- size := ps
- if size > s.c.Article.SkyHorseRecommendRegionLen {
- size = s.c.Article.SkyHorseRecommendRegionLen
- }
- recommends := s.genRecommendArtFromPool(s.RecommendsMap[_recommendCategory], size)
- aids, rems = s.dealRecommends(recommends)
- }
- if len(aids) < ps {
- sky, err = s.dao.SkyHorse(c, mid, build, buvid, plat, ps-len(aids))
- if (err != nil) || (len(sky.Data) == 0) {
- res, err = s.RecommendsWithLike(c, cid, pn, ps, lastAids, sort, mid)
- sky = nil
- return
- }
- for _, item := range sky.Data {
- if rems[item.ID] == nil {
- aids = append(aids, item.ID)
- }
- }
- }
- if metas, err = s.ArticleMetas(c, aids); err != nil {
- return
- }
- //过滤禁止显示的稿件
- filterNoDistributeArtsMap(metas)
- filterNoRegionArts(metas)
- states, _ := s.HadLikesByMid(c, mid, aids)
- for _, aid := range aids {
- if metas[aid] == nil {
- continue
- }
- art := model.RecommendArt{Meta: *metas[aid]}
- r := &model.RecommendArtWithLike{RecommendArt: art}
- if states != nil {
- r.LikeState = int(states[aid])
- }
- if rems[aid] != nil {
- r.Recommend = *rems[aid]
- }
- res = append(res, r)
- }
- return
- }
- func (s *Service) skyHorseGray(buvid string, mid int64) bool {
- if (mid == 0) && (buvid == "") {
- return false
- }
- for _, id := range s.c.Article.SkyHorseGrayUsers {
- if mid == id {
- return true
- }
- }
- for _, id := range s.c.Article.SkyHorseGray {
- if mid%10 == id {
- return true
- }
- }
- return false
- }
- func (s *Service) groupRecommend(c context.Context) (err error) {
- var (
- m = make(map[int64]map[int64]bool)
- mutex = &sync.Mutex{}
- )
- for _, recommends := range s.RecommendsMap {
- var (
- rs []*model.Recommend
- arts map[int64]*model.Meta
- aids = []int64{}
- )
- if len(recommends) > 0 {
- rs = recommends[0]
- }
- for _, r := range rs {
- aids = append(aids, r.ArticleID)
- }
- if arts, err = s.ArticleMetas(c, aids); err != nil || arts == nil {
- return
- }
- for _, art := range arts {
- if _, ok := m[art.Category.ID]; !ok {
- m[art.Category.ID] = make(map[int64]bool)
- }
- m[art.Category.ID][art.ID] = true
- }
- }
- mutex.Lock()
- s.RecommendsGroups = m
- mutex.Unlock()
- return
- }
- func (s *Service) getRecommentsGroups(c context.Context, cid int64, aid int64) (res []int64) {
- for i := range s.RecommendsGroups[cid] {
- if i != aid {
- res = append(res, i)
- }
- }
- return
- }
|