package contentmarkerser import ( "encoding/json" "time" "91porn-server/common/constant/redisconst" "91porn-server/common/log" "91porn-server/models/v/mediamod" "91porn-server/models/v/moduleconfmod" "91porn-server/models/v/tagmod" "91porn-server/models/v/vidmod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) type ModuleMarker struct { ModuleID string `json:"moduleId"` LatestAt *time.Time `json:"latestAt"` } type Response struct { HomeLatestAt *time.Time `json:"homeLatestAt"` Modules []ModuleMarker `json:"modules"` } // MarkerCacheReader is the Redis JSON subset used to share marker snapshots. type MarkerCacheReader interface { GetWithJson2Any(key string, val any) error } // MarkerCache adds atomic cache population to the marker snapshot reader. type MarkerCache interface { MarkerCacheReader SetNX(key string, value interface{}, expiration time.Duration) (bool, error) } // GetCached returns the shared marker snapshot, loading and caching it on a miss. func GetCached(now time.Time, cache MarkerCache) (Response, error) { data, err := getCached(now, cache, Get) if err != nil { return Response{}, err } if latestAt, cacheHit := homeLatestAtFromCachedList(cache); cacheHit { data.HomeLatestAt = latestAt } return data, nil } func getCached(now time.Time, cache MarkerCache, load func(time.Time) (Response, error)) (Response, error) { if data, ok := ReadCached(cache); ok { return data, nil } data, err := load(now) if err != nil { return Response{}, err } if cache != nil { payload, marshalErr := json.Marshal(data) if marshalErr != nil { log.Warn("marshal content update markers failed", log.E(marshalErr)) return data, nil } stored, cacheErr := cache.SetNX( redisconst.ContentUpdateMarkersCache, string(payload), redisconst.ContentUpdateMarkersCacheExpire, ) if cacheErr != nil { log.Warn("cache content update markers failed", log.E(cacheErr)) } else if !stored { if current, ok := ReadCached(cache); ok { return current, nil } } } return data, nil } // ReadCached returns only an existing marker snapshot and never queries MongoDB. func ReadCached(cache MarkerCacheReader) (Response, bool) { if cache == nil { return Response{}, false } var data Response if err := cache.GetWithJson2Any(redisconst.ContentUpdateMarkersCache, &data); err != nil { return Response{}, false } return data, true } type cachedHomeLatestList struct { Videos []struct { ReviewAt time.Time `json:"reviewAt"` } `json:"videos"` } const ( homeLatestListSortType = 1 homeLatestListPageNumber = 1 homeLatestListPageSize = 10 ) // homeLatestAtFromCachedList makes the "latest" red dot describe the same // first-page snapshot that the client can currently read from the list cache. func homeLatestAtFromCachedList(cache MarkerCacheReader) (*time.Time, bool) { if cache == nil { return nil, false } var data cachedHomeLatestList key := redisconst.GetMostNewModuleVideoListKey( homeLatestListSortType, homeLatestListPageNumber, homeLatestListPageSize, ) if err := cache.GetWithJson2Any(key, &data); err != nil { return nil, false } if len(data.Videos) == 0 || data.Videos[0].ReviewAt.IsZero() { return nil, true } latestAt := data.Videos[0].ReviewAt return &latestAt, true } func Get(now time.Time) (Response, error) { modules, err := moduleconfmod.GetAllActiveModule(now) if err != nil { return Response{}, err } excluded, err := moduleconfmod.ExcludedVideoModuleIDs(now, false) if err != nil { return Response{}, err } baseFilter := bson.M{"status": vidmod.CheckPass, "newsType": vidmod.SP} if len(excluded) > 0 { baseFilter["mId"] = bson.M{"$nin": excluded} } if darkTag, findErr := tagmod.FindOneTagByName("暗网"); findErr == nil && !darkTag.ID.IsZero() { baseFilter["tags"] = bson.M{"$ne": darkTag.ID} } homeLatestAt, err := vidmod.LatestReviewAt(baseFilter) if err != nil { return Response{}, err } moduleIDs := make([]string, 0, len(modules)) moduleObjectIDs := make([]primitive.ObjectID, 0, len(modules)) for _, module := range modules { moduleIDs = append(moduleIDs, module.ID.Hex()) moduleObjectIDs = append(moduleObjectIDs, module.ID) } moduleTimes, err := vidmod.LatestReviewAtByModules(moduleIDs) if err != nil { return Response{}, err } mediaModuleTimes, err := mediamod.LatestContentAtByModules(moduleObjectIDs) if err != nil { return Response{}, err } markers := make([]ModuleMarker, 0, len(modules)) for _, module := range modules { var latest *time.Time if value, ok := moduleTimes[module.ID.Hex()]; ok { valueCopy := value latest = &valueCopy } if value, ok := mediaModuleTimes[module.ID.Hex()]; ok && (latest == nil || value.After(*latest)) { valueCopy := value latest = &valueCopy } markers = append(markers, ModuleMarker{ModuleID: module.ID.Hex(), LatestAt: latest}) } return Response{ HomeLatestAt: homeLatestAt, Modules: markers, }, nil }