@@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user