package configser import ( "context" "strings" "sync" "sync/atomic" "videoweb/app/api/proto" "videoweb/pub/common/log" "videoweb/pub/common/netutil" "videoweb/pub/common/playletcache" "videoweb/pub/common/utils" "videoweb/pub/models/contenttagmod" "videoweb/pub/models/playletmod" "videoweb/pub/models/playletthememod" "videoweb/pub/pubdef" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) var ( PlayletRankMgr periodRankCache[proto.PlayletRankData] playletRankRefreshLock sync.Mutex playletRankCacheVersion atomic.Int64 ) func UpdatePlayletRankCache() { version := playletcache.RankCacheVersion() ctx := context.Background() imgCdn := GetConfigInfo().ImgCdn PlayletRankMgr.replace( buildPlayletRankList(ctx, imgCdn, RankPeriodTotal), buildPlayletRankList(ctx, imgCdn, RankPeriodWeek), buildPlayletRankList(ctx, imgCdn, RankPeriodMonth), buildPlayletRankList(ctx, imgCdn, RankPeriodYear), ) playletRankCacheVersion.Store(version) } func refreshPlayletRankCacheIfEmpty() { if PlayletRankMgr.hasAny() { return } playletRankRefreshLock.Lock() defer playletRankRefreshLock.Unlock() if PlayletRankMgr.hasAny() { return } UpdatePlayletRankCache() } func GetPlayletRankList(p RankPeriod, limit int) []proto.PlayletRankData { version := playletcache.RankCacheVersion() if version != playletRankCacheVersion.Load() { playletRankRefreshLock.Lock() if version != playletRankCacheVersion.Load() { UpdatePlayletRankCache() } playletRankRefreshLock.Unlock() } return PlayletRankMgr.get(p, limit, refreshPlayletRankCacheIfEmpty) } func playletPeriodWatchField(p RankPeriod) string { switch p { case RankPeriodWeek: return playletmod.FN.Playlet.WeekWatchs case RankPeriodMonth: return playletmod.FN.Playlet.MonthWatchs case RankPeriodYear: return playletmod.FN.Playlet.YearWatchs default: return "" } } func buildPlayletRankList(ctx context.Context, imgCdn string, p RankPeriod) []proto.PlayletRankData { var ranked playletmod.PlayletList if p == RankPeriodTotal { if err := playletmod.AggregatePlaylet(ctx, &ranked, contentLikeRankPipeline( playletmod.FN.Playlet.Status, playletmod.StatusUp, playletmod.FN.Playlet.CountLike, playletmod.FN.Playlet.VirtualCountLike, playletmod.FN.Playlet.UpdatedAt, playletmod.FN.Playlet.Id, rankLimit, )); err != nil { log.Warn("buildPlayletRankList like rank query failed", log.E(err)) ranked = playletmod.PlayletList{} } } else { watchField := playletPeriodWatchField(p) list, err := playletmod.FindManyPlaylet(ctx, bson.M{ playletmod.FN.Playlet.Status: playletmod.StatusUp, watchField: bson.M{"$gt": 0}, }, contentWatchRankOpts(watchField, playletmod.FN.Playlet.UpdatedAt, playletmod.FN.Playlet.Id, rankLimit)) if err != nil { log.Warn("buildPlayletRankList watch rank query failed", log.Any("period", p), log.E(err)) ranked = playletmod.PlayletList{} } else { ranked = list } } if int64(len(ranked)) < rankLimit { needFill := rankLimit - int64(len(ranked)) excludeIds := make([]int64, 0, len(ranked)) for _, v := range ranked { if v != nil { excludeIds = append(excludeIds, v.Id) } } var filled playletmod.PlayletList if err := playletmod.AggregatePlaylet(ctx, &filled, contentRankFillPipeline( playletmod.FN.Playlet.Id, playletmod.FN.Playlet.Status, playletmod.StatusUp, excludeIds, needFill, )); err != nil { log.Warn("buildPlayletRankList fill sample failed", log.Any("period", p), log.E(err)) } else { ranked = append(ranked, filled...) } } tagNameMap, themeNameMap := loadPlayletRankNameMaps(ctx, ranked) result := make([]proto.PlayletRankData, 0, len(ranked)) for _, item := range ranked { if item == nil { continue } result = append(result, playletToRankData(item, imgCdn, tagNameMap, themeNameMap, p)) } return result } func playletToRankData(item *playletmod.Playlet, imgCdn string, tagNameMap contenttagmod.ContentTagMap, themeNameMap map[int64]string, p RankPeriod) proto.PlayletRankData { return proto.PlayletRankData{ PlayletData: proto.PlayletData{ Id: item.Id, Status: item.Status, IsRecommend: item.IsRecommend, IsNew: !item.OnlineAt.IsZero() && item.OnlineAt.After(utils.BeginOfWeekByTime(utils.Now())), IsFeatured: item.IsFeatured, IsOriginal: item.IsOriginal || item.MediaCenterId == 0, ShowOriginal: item.ShowOriginal, OnlineAt: item.OnlineAt, MediaCreatedAt: item.MediaCreatedAt, MediaUpdatedAt: item.MediaUpdatedAt, Title: item.Title, Slug: item.Slug, Desc: item.Desc, Anchor: item.Anchor, Cover: netutil.MergeURL(imgCdn, item.DisplayCover()), Preview: netutil.MergeURL(imgCdn, strings.TrimSpace(item.Preview)), TagIds: item.TagIds, TagNames: playletRankTagNamesByIDs(item.TagIds, tagNameMap), TagSlugs: playletRankTagSlugsByIDs(item.TagIds, tagNameMap), ThemeIds: item.ThemeIds, ThemeNames: playletNamesByIDs(item.ThemeIds, themeNameMap), ContentType: item.ContentType, PayType: item.PayType, SingleCoins: item.SingleCoins, TotalEpisode: item.TotalEpisode, UpdateStatus: item.UpdateStatus, CountBrowse: item.DisplayCountBrowse(), HeatValue: item.DisplayHeatValue(), CountCollect: item.CountCollect, CountLike: item.CountLike, VirtualCountLike: item.VirtualCountLike, DisplayCountLike: item.CountLike + item.VirtualCountLike, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, SeoInfo: playletRankSeo(item.SeoInfo), }, RankScore: periodWatchNum(p, item.WeekWatchs, item.MonthWatchs, item.YearWatchs, item.CountLike+item.VirtualCountLike), } } func playletRankSeo(seo pubdef.SeoValue) pubdef.SeoValue { seo.Title = strings.TrimSpace(seo.Title) seo.Description = strings.TrimSpace(seo.Description) if seo.Keyworld == nil { seo.Keyworld = make([]string, 0) return seo } words := make([]string, 0, len(seo.Keyworld)) for _, word := range seo.Keyworld { word = strings.TrimSpace(word) if word != "" { words = append(words, word) } } seo.Keyworld = words return seo } func playletNamesByIDs(ids []int64, nameMap map[int64]string) []string { result := make([]string, 0, len(ids)) for _, id := range ids { if name := nameMap[id]; name != "" { result = append(result, name) } } return result } func loadPlayletRankNameMaps(ctx context.Context, list playletmod.PlayletList) (contenttagmod.ContentTagMap, map[int64]string) { tagIDs := make([]int64, 0) themeIDs := make([]int64, 0) seenTag := make(map[int64]struct{}) seenTheme := make(map[int64]struct{}) for _, item := range list { if item == nil { continue } for _, id := range item.TagIds { if id <= 0 { continue } if _, ok := seenTag[id]; ok { continue } seenTag[id] = struct{}{} tagIDs = append(tagIDs, id) } for _, id := range item.ThemeIds { if id <= 0 { continue } if _, ok := seenTheme[id]; ok { continue } seenTheme[id] = struct{}{} themeIDs = append(themeIDs, id) } } tagNameMap := make(contenttagmod.ContentTagMap, len(tagIDs)) if len(tagIDs) > 0 { if tags, err := contenttagmod.GetMapTags(ctx, tagIDs); err != nil { log.Warn("loadPlayletRankNameMaps tags failed", log.E(err)) } else { tagNameMap = tags } } themeNameMap := make(map[int64]string, len(themeIDs)) if len(themeIDs) > 0 { themes, err := playletthememod.FindManyPlayletTheme(ctx, bson.M{ playletthememod.FN.PlayletTheme.Id: bson.M{"$in": themeIDs}, }, options.Find().SetProjection(bson.M{ playletthememod.FN.PlayletTheme.Id: 1, playletthememod.FN.PlayletTheme.Name: 1, })) if err != nil { log.Warn("loadPlayletRankNameMaps themes failed", log.E(err)) } else { for _, theme := range themes { if theme != nil { themeNameMap[theme.Id] = theme.Name } } } } return tagNameMap, themeNameMap } func playletRankTagNamesByIDs(ids []int64, tags contenttagmod.ContentTagMap) []string { result := make([]string, 0, len(ids)) for _, id := range ids { if tag := tags[id]; tag != nil && tag.Name != "" { result = append(result, tag.Name) } } return result } func playletRankTagSlugsByIDs(ids []int64, tags contenttagmod.ContentTagMap) []string { result := make([]string, 0, len(ids)) for _, id := range ids { if tag := tags[id]; tag != nil && tag.Name != "" { result = append(result, tag.Slug) } } return result }