500 lines
12 KiB
Go
500 lines
12 KiB
Go
package playletcache
|
||
|
||
import (
|
||
"context"
|
||
"sort"
|
||
"strings"
|
||
"sync"
|
||
"videoweb/pub/common/log"
|
||
"videoweb/pub/models/hotsearchmod"
|
||
"videoweb/pub/models/playletmod"
|
||
"videoweb/pub/models/playlettagmod"
|
||
"videoweb/pub/models/playletthememod"
|
||
"videoweb/pub/pubdef"
|
||
|
||
"github.com/robfig/cron/v3"
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/mongo/options"
|
||
)
|
||
|
||
var (
|
||
skdCron *cron.Cron
|
||
cache struct {
|
||
lock sync.RWMutex
|
||
themeByID map[int64]*ThemeItem
|
||
themeBySlug map[string]*ThemeItem
|
||
tagByID map[int64]*TagItem
|
||
tagBySlug map[string]*TagItem
|
||
hotSearch []HotSearchItem
|
||
guessLike []*playletmod.Playlet
|
||
}
|
||
)
|
||
|
||
// HotSearchItem 热搜词缓存项
|
||
type HotSearchItem struct {
|
||
Keyword string
|
||
Count int64
|
||
}
|
||
|
||
// ThemeItem 题材缓存项(含 SEO)
|
||
type ThemeItem struct {
|
||
Id int64
|
||
Name string
|
||
Slug string
|
||
Icon string
|
||
Rank int64
|
||
Status bool
|
||
IsRecommend bool
|
||
PlayletCount int64
|
||
SeoInfo pubdef.SeoValue
|
||
}
|
||
|
||
// TagItem 标签缓存项(含 SEO)
|
||
type TagItem struct {
|
||
Id int64
|
||
Name string
|
||
Slug string
|
||
Rank int64
|
||
Status bool
|
||
SeoInfo pubdef.SeoValue
|
||
}
|
||
|
||
// Start 启动题材/标签/热搜词/猜你喜欢缓存,每 10 分钟刷新一次。
|
||
func Start() {
|
||
skdCron = cron.New(cron.WithSeconds())
|
||
UpdateCache(context.Background())
|
||
_, _ = skdCron.AddFunc("0 */10 * * * ?", func() {
|
||
UpdateCache(context.Background())
|
||
})
|
||
skdCron.Start()
|
||
}
|
||
|
||
// UpdateCache 从 Mongo 全量刷新题材、标签、热搜词和猜你喜欢缓存。
|
||
func UpdateCache(ctx context.Context) {
|
||
updateThemeCache(ctx)
|
||
updateTagCache(ctx)
|
||
updateHotSearchCache(ctx)
|
||
updateGuessLikeCache(ctx)
|
||
}
|
||
|
||
func updateThemeCache(ctx context.Context) {
|
||
list, err := playletthememod.FindManyPlayletTheme(ctx, bson.M{}, options.Find().
|
||
SetProjection(bson.D{
|
||
{Key: playletthememod.FN.PlayletTheme.Id, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.Name, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.Slug, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.Icon, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.Rank, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.Status, Value: 1},
|
||
{Key: playletthememod.FN.PlayletTheme.IsRecommend, Value: 1},
|
||
{Key: "seoInfo", Value: 1},
|
||
}).
|
||
SetSort(bson.D{
|
||
{Key: playletthememod.FN.PlayletTheme.Rank, Value: -1},
|
||
{Key: playletthememod.FN.PlayletTheme.Id, Value: -1},
|
||
}))
|
||
if err != nil {
|
||
log.Warn("playletcache.updateThemeCache failed", log.E(err))
|
||
return
|
||
}
|
||
countMap := countUpPlayletsByTheme(ctx)
|
||
temp := make(map[int64]*ThemeItem, len(list))
|
||
tempBySlug := make(map[string]*ThemeItem, len(list))
|
||
for _, item := range list {
|
||
if item == nil || item.Id <= 0 {
|
||
continue
|
||
}
|
||
cached := &ThemeItem{
|
||
Id: item.Id,
|
||
Name: item.Name,
|
||
Slug: item.Slug,
|
||
Icon: item.Icon,
|
||
Rank: item.Rank,
|
||
Status: item.Status,
|
||
IsRecommend: item.IsRecommend,
|
||
PlayletCount: countMap[item.Id],
|
||
SeoInfo: item.SeoInfo,
|
||
}
|
||
temp[item.Id] = cached
|
||
if slug := strings.TrimSpace(item.Slug); slug != "" {
|
||
tempBySlug[slug] = cached
|
||
}
|
||
}
|
||
cache.lock.Lock()
|
||
cache.themeByID = temp
|
||
cache.themeBySlug = tempBySlug
|
||
cache.lock.Unlock()
|
||
}
|
||
|
||
type themePlayletCount struct {
|
||
Id int64 `bson:"_id"`
|
||
Count int64 `bson:"count"`
|
||
}
|
||
|
||
// countUpPlayletsByTheme 按题材统计已上架短剧数量。
|
||
func countUpPlayletsByTheme(ctx context.Context) map[int64]int64 {
|
||
result := make(map[int64]int64)
|
||
list := make([]themePlayletCount, 0)
|
||
err := playletmod.AggregatePlaylet(ctx, &list, []bson.M{
|
||
{
|
||
"$match": bson.M{
|
||
playletmod.FN.Playlet.Status: playletmod.StatusUp,
|
||
},
|
||
},
|
||
{
|
||
"$unwind": "$" + playletmod.FN.Playlet.ThemeIds,
|
||
},
|
||
{
|
||
"$group": bson.M{
|
||
"_id": "$" + playletmod.FN.Playlet.ThemeIds,
|
||
"count": bson.M{"$sum": 1},
|
||
},
|
||
},
|
||
})
|
||
if err != nil {
|
||
log.Warn("playletcache.countUpPlayletsByTheme failed", log.E(err))
|
||
return result
|
||
}
|
||
for _, item := range list {
|
||
if item.Id <= 0 || item.Count <= 0 {
|
||
continue
|
||
}
|
||
result[item.Id] = item.Count
|
||
}
|
||
return result
|
||
}
|
||
|
||
func updateTagCache(ctx context.Context) {
|
||
list, err := playlettagmod.FindManyPlayletTag(ctx, bson.M{}, options.Find().
|
||
SetProjection(bson.D{
|
||
{Key: playlettagmod.FN.PlayletTag.Id, Value: 1},
|
||
{Key: playlettagmod.FN.PlayletTag.Name, Value: 1},
|
||
{Key: playlettagmod.FN.PlayletTag.Slug, Value: 1},
|
||
{Key: playlettagmod.FN.PlayletTag.Rank, Value: 1},
|
||
{Key: playlettagmod.FN.PlayletTag.Status, Value: 1},
|
||
{Key: "seoInfo", Value: 1},
|
||
}).
|
||
SetSort(bson.D{
|
||
{Key: playlettagmod.FN.PlayletTag.Rank, Value: -1},
|
||
{Key: playlettagmod.FN.PlayletTag.Id, Value: -1},
|
||
}))
|
||
if err != nil {
|
||
log.Warn("playletcache.updateTagCache failed", log.E(err))
|
||
return
|
||
}
|
||
temp := make(map[int64]*TagItem, len(list))
|
||
tempBySlug := make(map[string]*TagItem, len(list))
|
||
for _, item := range list {
|
||
if item == nil || item.Id <= 0 {
|
||
continue
|
||
}
|
||
cached := &TagItem{
|
||
Id: item.Id,
|
||
Name: item.Name,
|
||
Slug: item.Slug,
|
||
Rank: item.Rank,
|
||
Status: item.Status,
|
||
SeoInfo: item.SeoInfo,
|
||
}
|
||
temp[item.Id] = cached
|
||
if slug := strings.TrimSpace(item.Slug); slug != "" {
|
||
tempBySlug[slug] = cached
|
||
}
|
||
}
|
||
cache.lock.Lock()
|
||
cache.tagByID = temp
|
||
cache.tagBySlug = tempBySlug
|
||
cache.lock.Unlock()
|
||
}
|
||
|
||
func updateHotSearchCache(ctx context.Context) {
|
||
list, err := hotsearchmod.FindTopHotSearch(ctx, hotsearchmod.TopLimit)
|
||
if err != nil {
|
||
log.Warn("playletcache.updateHotSearchCache failed", log.E(err))
|
||
return
|
||
}
|
||
temp := make([]HotSearchItem, 0, len(list))
|
||
for _, item := range list {
|
||
if item == nil || strings.TrimSpace(item.Keyword) == "" {
|
||
continue
|
||
}
|
||
temp = append(temp, HotSearchItem{
|
||
Keyword: item.Keyword,
|
||
Count: item.Count,
|
||
})
|
||
}
|
||
cache.lock.Lock()
|
||
cache.hotSearch = temp
|
||
cache.lock.Unlock()
|
||
}
|
||
|
||
// ListHotSearch 返回缓存的前10个热搜词。
|
||
func ListHotSearch() []HotSearchItem {
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
if len(cache.hotSearch) == 0 {
|
||
return make([]HotSearchItem, 0)
|
||
}
|
||
result := make([]HotSearchItem, len(cache.hotSearch))
|
||
copy(result, cache.hotSearch)
|
||
return result
|
||
}
|
||
|
||
// GetTheme 按 ID 取题材缓存;不存在返回 nil。
|
||
func GetTheme(id int64) *ThemeItem {
|
||
if id <= 0 {
|
||
return nil
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
item := cache.themeByID[id]
|
||
if item == nil {
|
||
return nil
|
||
}
|
||
cp := *item
|
||
return &cp
|
||
}
|
||
|
||
// GetEnabledTheme 按 ID 取上架题材;未上架或不存在返回 nil。
|
||
func GetEnabledTheme(id int64) *ThemeItem {
|
||
item := GetTheme(id)
|
||
if item == nil || !item.Status {
|
||
return nil
|
||
}
|
||
return item
|
||
}
|
||
|
||
// GetThemeBySlug 按 slug 取题材缓存;不存在返回 nil。
|
||
func GetThemeBySlug(slug string) *ThemeItem {
|
||
slug = strings.TrimSpace(slug)
|
||
if slug == "" {
|
||
return nil
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
item := cache.themeBySlug[slug]
|
||
if item == nil {
|
||
return nil
|
||
}
|
||
cp := *item
|
||
return &cp
|
||
}
|
||
|
||
// GetTag 按 ID 取标签缓存;不存在返回 nil。
|
||
func GetTag(id int64) *TagItem {
|
||
if id <= 0 {
|
||
return nil
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
item := cache.tagByID[id]
|
||
if item == nil {
|
||
return nil
|
||
}
|
||
cp := *item
|
||
return &cp
|
||
}
|
||
|
||
// GetEnabledTag 按 ID 取上架标签;未上架或不存在返回 nil。
|
||
func GetEnabledTag(id int64) *TagItem {
|
||
item := GetTag(id)
|
||
if item == nil || !item.Status {
|
||
return nil
|
||
}
|
||
return item
|
||
}
|
||
|
||
// ListEnabledTags 返回已上架标签列表,按缓存排序。
|
||
func ListEnabledTags() []*TagItem {
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
result := make([]*TagItem, 0, len(cache.tagByID))
|
||
seen := make(map[int64]struct{}, len(cache.tagByID))
|
||
for _, item := range cache.tagBySlug {
|
||
if item == nil || !item.Status || item.Id <= 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[item.Id]; ok {
|
||
continue
|
||
}
|
||
cp := *item
|
||
result = append(result, &cp)
|
||
seen[item.Id] = struct{}{}
|
||
}
|
||
for _, item := range cache.tagByID {
|
||
if item == nil || !item.Status || item.Id <= 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[item.Id]; ok {
|
||
continue
|
||
}
|
||
cp := *item
|
||
result = append(result, &cp)
|
||
seen[item.Id] = struct{}{}
|
||
}
|
||
sort.SliceStable(result, func(i, j int) bool {
|
||
if result[i].Rank == result[j].Rank {
|
||
return result[i].Id > result[j].Id
|
||
}
|
||
return result[i].Rank > result[j].Rank
|
||
})
|
||
return result
|
||
}
|
||
|
||
// GetTagBySlug 按 slug 取标签缓存;不存在返回 nil。
|
||
func GetTagBySlug(slug string) *TagItem {
|
||
slug = strings.TrimSpace(slug)
|
||
if slug == "" {
|
||
return nil
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
item := cache.tagBySlug[slug]
|
||
if item == nil {
|
||
return nil
|
||
}
|
||
cp := *item
|
||
return &cp
|
||
}
|
||
|
||
// ListEnabledThemes 返回全部上架题材,按 Rank、Id 倒序。
|
||
func ListEnabledThemes() []*ThemeItem {
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
result := make([]*ThemeItem, 0, len(cache.themeByID))
|
||
for _, item := range cache.themeByID {
|
||
if item == nil || !item.Status {
|
||
continue
|
||
}
|
||
cp := *item
|
||
result = append(result, &cp)
|
||
}
|
||
sort.SliceStable(result, func(i, j int) bool {
|
||
if result[i].Rank != result[j].Rank {
|
||
return result[i].Rank > result[j].Rank
|
||
}
|
||
return result[i].Id > result[j].Id
|
||
})
|
||
return result
|
||
}
|
||
|
||
// ListEnabledThemesByPlayletCount 返回有上架短剧的启用题材,按绑定短剧数量倒序
|
||
func ListEnabledThemesByPlayletCount(limit int) []*ThemeItem {
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
result := make([]*ThemeItem, 0, len(cache.themeByID))
|
||
for _, item := range cache.themeByID {
|
||
if item == nil || !item.Status || item.PlayletCount <= 0 {
|
||
continue
|
||
}
|
||
cp := *item
|
||
result = append(result, &cp)
|
||
}
|
||
sort.SliceStable(result, func(i, j int) bool {
|
||
if result[i].PlayletCount != result[j].PlayletCount {
|
||
return result[i].PlayletCount > result[j].PlayletCount
|
||
}
|
||
if result[i].Rank != result[j].Rank {
|
||
return result[i].Rank > result[j].Rank
|
||
}
|
||
return result[i].Id > result[j].Id
|
||
})
|
||
if limit > 0 && len(result) > limit {
|
||
return result[:limit]
|
||
}
|
||
return result
|
||
}
|
||
|
||
const homeRecommendThemeLimit = 4
|
||
|
||
// ListRecommendedThemes 返回已上架且推荐的题材,按 Rank、Id 倒序,最多 limit 条。
|
||
func ListRecommendedThemes(limit int) []*ThemeItem {
|
||
if limit <= 0 {
|
||
limit = homeRecommendThemeLimit
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
result := make([]*ThemeItem, 0, limit)
|
||
for _, item := range cache.themeByID {
|
||
if item == nil || !item.Status || !item.IsRecommend {
|
||
continue
|
||
}
|
||
cp := *item
|
||
result = append(result, &cp)
|
||
}
|
||
sort.SliceStable(result, func(i, j int) bool {
|
||
if result[i].Rank != result[j].Rank {
|
||
return result[i].Rank > result[j].Rank
|
||
}
|
||
return result[i].Id > result[j].Id
|
||
})
|
||
if len(result) > limit {
|
||
result = result[:limit]
|
||
}
|
||
return result
|
||
}
|
||
|
||
// ThemeNameMap 批量取题材名称(含未上架,便于列表展示历史题材名)。
|
||
func ThemeNameMap(ids []int64) map[int64]string {
|
||
result := make(map[int64]string, len(ids))
|
||
if len(ids) == 0 {
|
||
return result
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
for _, id := range ids {
|
||
if item := cache.themeByID[id]; item != nil {
|
||
result[id] = item.Name
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// TagNameMap 批量取标签名称(含未上架)。
|
||
func TagNameMap(ids []int64) map[int64]string {
|
||
result := make(map[int64]string, len(ids))
|
||
if len(ids) == 0 {
|
||
return result
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
for _, id := range ids {
|
||
if item := cache.tagByID[id]; item != nil {
|
||
result[id] = item.Name
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// ThemeSlugMap 批量取题材 slug(含未上架)。
|
||
func ThemeSlugMap(ids []int64) map[int64]string {
|
||
result := make(map[int64]string, len(ids))
|
||
if len(ids) == 0 {
|
||
return result
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
for _, id := range ids {
|
||
if item := cache.themeByID[id]; item != nil {
|
||
result[id] = item.Slug
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// TagSlugMap 批量取标签 slug(含未上架)。
|
||
func TagSlugMap(ids []int64) map[int64]string {
|
||
result := make(map[int64]string, len(ids))
|
||
if len(ids) == 0 {
|
||
return result
|
||
}
|
||
cache.lock.RLock()
|
||
defer cache.lock.RUnlock()
|
||
for _, id := range ids {
|
||
if item := cache.tagByID[id]; item != nil {
|
||
result[id] = item.Slug
|
||
}
|
||
}
|
||
return result
|
||
}
|