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

261 lines
8.6 KiB
Go

package translationser
import (
"context"
"fmt"
"net/url"
"strings"
"sync"
"videoweb/app/api/proto"
"videoweb/pub/common/log"
"videoweb/pub/common/netutil"
"videoweb/pub/models/cfg"
"videoweb/pub/models/translationcategorymod"
"videoweb/pub/models/translationmod"
"videoweb/pub/models/videocategorymod"
"videoweb/pub/pubdef"
"github.com/robfig/cron/v3"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
CacheMgr CacheInfo
skdCron *cron.Cron
categoryCacheFill sync.Mutex
)
type CacheInfo struct {
Lock sync.RWMutex // 读写锁
TranslationData proto.AppTranslationInfo // 顶部导航缓存
BottomTranslationData proto.AppTranslationInfo // 底部导航缓存
TranslationMap map[int64]proto.TranslationInfo
// 视频分类缓存(按 ID 索引)
VideoCategoryMap map[int64]*videocategorymod.VideoCategory
}
func init() {
skdCron = cron.New(cron.WithSeconds())
CacheMgr.TranslationData = proto.AppTranslationInfo{}
CacheMgr.BottomTranslationData = proto.AppTranslationInfo{}
CacheMgr.TranslationMap = make(map[int64]proto.TranslationInfo)
CacheMgr.VideoCategoryMap = make(map[int64]*videocategorymod.VideoCategory)
}
func Start() {
ctx := context.Background()
UpdateTranslationInfo(ctx)
_, _ = skdCron.AddFunc("0 */5 * * * ?", func() {
UpdateTranslationInfo(ctx)
})
skdCron.Start()
}
func UpdateTranslationInfo(ctx context.Context) {
filter := bson.M{cfg.FN.SysCfg.Id: 1}
sysCfg, _ := cfg.FindOneSysCfg(context.Background(), filter)
if sysCfg == nil {
return
}
translation := proto.AppTranslationInfo{}
bottomTranslation := proto.AppTranslationInfo{}
wg := &sync.WaitGroup{}
wg.Add(3)
var translationCategoryList translationcategorymod.TranslationCategoryList
var translationList translationmod.TranslationInfoList
var videoCategoryList videocategorymod.VideoCategoryList
go func() {
defer wg.Done()
translationCategoryList, _ = translationcategorymod.FindManyTranslationCategorys(ctx,
bson.M{translationcategorymod.FN.TranslationCategory.Status: true},
options.Find().SetLimit(1000).SetSort(bson.M{translationcategorymod.FN.TranslationCategory.Rank: -1}),
)
}()
go func() {
defer wg.Done()
translationList, _ = translationmod.FindManyTranslationInfos(ctx,
bson.M{translationmod.FN.TranslationInfo.Status: true},
options.Find().SetLimit(1000).SetSort(bson.M{translationmod.FN.TranslationInfo.Rank: -1}),
)
}()
go func() {
defer wg.Done()
videoCategoryList, _ = videocategorymod.FindManyVideoCategorys(ctx,
bson.M{videocategorymod.FN.VideoCategory.Status: true},
options.Find().SetLimit(500).SetSort(bson.M{videocategorymod.FN.VideoCategory.Rank: -1}),
)
}()
wg.Wait()
// ---- 构建各分类 ID Map(用于缓存 & 导航分组) ----
newVideoCategoryMap := make(map[int64]*videocategorymod.VideoCategory, len(videoCategoryList))
videoCategoryByNavId := make(map[int64][]*videocategorymod.VideoCategory)
for _, c := range videoCategoryList {
newVideoCategoryMap[c.Id] = c
if c.TranslationCategoryId > 0 {
videoCategoryByNavId[c.TranslationCategoryId] = append(videoCategoryByNavId[c.TranslationCategoryId], c)
}
}
// ---- 构建导航树 ----
tempTranslationMap := make(map[int64]proto.TranslationInfo)
for _, categoryInfo := range translationCategoryList {
categoryTemp := proto.TranslationCategory{
ID: categoryInfo.Id,
Name: categoryInfo.Name,
Icon: netutil.MergeURL(sysCfg.ImgAppCdn, categoryInfo.Icon),
TranslationList: make([]proto.TranslationInfo, 0),
CategoryType: categoryInfo.CategoryType,
UrlValue: categoryInfo.UrlValue,
}
if categoryInfo.CategoryType == pubdef.TranslationTypeSub {
// 固定路径条目优先放在最前面(根据导航分类的 UrlValue 决定属于哪个内容域)
categoryTemp.TranslationList = append(categoryTemp.TranslationList,
buildFixedNavItems(categoryInfo.UrlValue)...,
)
// 手动配置的导航条目
for _, translationInfo := range translationList {
if categoryInfo.Id != translationInfo.TransCategoryId {
continue
}
translationTmp := proto.TranslationInfo{
ID: translationInfo.Id,
Name: translationInfo.Name,
TransCategoryId: translationInfo.TransCategoryId,
}
if translationInfo.IsFriendAds {
// 友链广告,顶部暂不支持
continue
} else if translationInfo.HtmlId > 0 {
translationTmp.UrlValue = "/richtext?title=" + url.QueryEscape(strings.TrimSpace(translationInfo.Name))
} else if len(translationInfo.Link) > 0 {
translationTmp.UrlValue = translationInfo.Link
}
if translationTmp.UrlValue == "" {
continue
}
tempTranslationMap[translationInfo.Id] = translationTmp
categoryTemp.TranslationList = append(categoryTemp.TranslationList, translationTmp)
}
// 挂载到该导航的视频分类
for _, c := range videoCategoryByNavId[categoryInfo.Id] {
if c.DisplayPosition == pubdef.CategoryDisplayPositionHomeOnly {
continue
}
categoryTemp.TranslationList = append(categoryTemp.TranslationList, proto.TranslationInfo{
ID: c.Id,
Name: c.Name,
DisplayMode: c.DisplayMode,
UrlValue: fmt.Sprintf(pubdef.TranslationPath_VideoMore, c.Id),
})
}
} else if categoryInfo.CategoryType == pubdef.TranslationTypeBottom {
for _, translationInfo := range translationList {
if categoryInfo.Id != translationInfo.TransCategoryId {
continue
}
translationTmp := proto.TranslationInfo{
ID: translationInfo.Id,
Name: translationInfo.Name,
TransCategoryId: translationInfo.TransCategoryId,
}
if translationInfo.IsFriendAds {
translationTmp.IsFriendAds = true
} else if translationInfo.HtmlId > 0 {
translationTmp.UrlValue = "/richtext?title=" + url.QueryEscape(strings.TrimSpace(translationInfo.Name))
} else if len(translationInfo.Link) > 0 {
translationTmp.UrlValue = translationInfo.Link
}
tempTranslationMap[translationInfo.Id] = translationTmp
categoryTemp.TranslationList = append(categoryTemp.TranslationList, translationTmp)
}
bottomTranslation.CategoryList = append(bottomTranslation.CategoryList, categoryTemp)
continue
} else {
continue
}
translation.CategoryList = append(translation.CategoryList, categoryTemp)
}
log.Info("UpdateTranslationInfo", log.Any("categoryList", translation.CategoryList))
CacheMgr.Lock.Lock()
defer CacheMgr.Lock.Unlock()
CacheMgr.TranslationData = translation
CacheMgr.BottomTranslationData = bottomTranslation
CacheMgr.TranslationMap = tempTranslationMap
CacheMgr.VideoCategoryMap = newVideoCategoryMap
}
// ---- 导航读取 ----
func GetTranslationData() proto.AppTranslationInfo {
CacheMgr.Lock.RLock()
defer CacheMgr.Lock.RUnlock()
return CacheMgr.TranslationData
}
func GetTranslationBottomList() proto.AppTranslationInfo {
CacheMgr.Lock.RLock()
defer CacheMgr.Lock.RUnlock()
return CacheMgr.BottomTranslationData
}
// ---- 分类读取 ----
func categoryCacheEmpty() bool {
CacheMgr.Lock.RLock()
defer CacheMgr.Lock.RUnlock()
return len(CacheMgr.VideoCategoryMap) == 0
}
// EnsureCategoryCaches 若任一分内容分类缓存为空则立即补全;常规定时 5 分钟刷新。
func EnsureCategoryCaches(ctx context.Context) {
if !categoryCacheEmpty() {
return
}
categoryCacheFill.Lock()
defer categoryCacheFill.Unlock()
if !categoryCacheEmpty() {
return
}
UpdateTranslationInfo(ctx)
}
// EnsureVideoCategoryCache 兼容旧调用,等同 EnsureCategoryCaches。
func EnsureVideoCategoryCache(ctx context.Context) {
EnsureCategoryCaches(ctx)
}
// GetVideoCategory 通过视频分类 ID 获取分类信息,不存在返回 nil
func GetVideoCategory(id int64) *videocategorymod.VideoCategory {
EnsureCategoryCaches(context.Background())
CacheMgr.Lock.RLock()
defer CacheMgr.Lock.RUnlock()
return CacheMgr.VideoCategoryMap[id]
}
// buildFixedNavItems 根据导航分类的 UrlValue 返回对应内容域的固定路径条目,
// 这些条目始终排在动态分类和手动配置条目之前。
func buildFixedNavItems(urlValue string) []proto.TranslationInfo {
switch urlValue {
case pubdef.TranslationPath_Video:
return []proto.TranslationInfo{
//{Name: pubdef.TranslationName_Video, UrlValue: pubdef.TranslationPath_Video},
//{Name: pubdef.TranslationName_VideoAll, UrlValue: pubdef.TranslationPath_VideoAll},
//{Name: pubdef.TranslationName_VideoRank, UrlValue: pubdef.TranslationPath_VideoRank},
//{Name: pubdef.TranslationName_ActressRank, UrlValue: pubdef.TranslationPath_ActressRank},
}
}
return nil
}