553 lines
13 KiB
Go
553 lines
13 KiB
Go
|
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
|
||
|
}
|