Files
2026-10-26 16:28:14 +08:00

281 lines
8.5 KiB
Go

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
}