Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

183 lines
4.9 KiB
Go

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
}