package configser import ( "sync" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) const rankLimit = 48 // NormalizeRankPage 榜单分页:页码从 1 起,pageSize 不是 rankLimit 时强制改为 rankLimit。 func NormalizeRankPage(pageNum, pageSize int64) (int64, int64) { if pageNum <= 0 { pageNum = 1 } if pageSize != int64(rankLimit) { pageSize = int64(rankLimit) } return pageNum, pageSize } type periodRankCache[T any] struct { Lock sync.RWMutex Total []T Week []T Month []T Year []T } func (c *periodRankCache[T]) hasAny() bool { c.Lock.RLock() defer c.Lock.RUnlock() return len(c.Total) > 0 || len(c.Week) > 0 || len(c.Month) > 0 || len(c.Year) > 0 } func (c *periodRankCache[T]) pick(p RankPeriod) []T { switch p { case RankPeriodWeek: return c.Week case RankPeriodMonth: return c.Month case RankPeriodYear: return c.Year default: return c.Total } } func (c *periodRankCache[T]) replace(total, week, month, year []T) { c.Lock.Lock() c.Total, c.Week, c.Month, c.Year = total, week, month, year c.Lock.Unlock() } func (c *periodRankCache[T]) get(p RankPeriod, limit int, refreshIfEmpty func()) []T { c.Lock.RLock() list := c.pick(p) empty := len(list) == 0 c.Lock.RUnlock() if empty { refreshIfEmpty() c.Lock.RLock() list = c.pick(p) c.Lock.RUnlock() } return sliceLimit(list, limit) } func sliceLimit[T any](s []T, limit int) []T { if limit <= 0 || limit >= len(s) { return s } return s[:limit] } // SliceRankPage 对榜单缓存做分页切片 func SliceRankPage[T any](all []T, pageNum, pageSize int64) []T { if pageNum <= 0 { pageNum = 1 } if pageSize <= 0 { return []T{} } start := (pageNum - 1) * pageSize if start >= int64(len(all)) { return []T{} } end := start + pageSize if end > int64(len(all)) { end = int64(len(all)) } return append([]T{}, all[start:end]...) } func contentLikeRankPipeline(statusField string, statusVal interface{}, realLikes, fakeLikes, updatedAt, idField string, limit int64) []bson.M { return []bson.M{ {"$match": bson.M{statusField: statusVal}}, {"$addFields": bson.M{"rankScore": bson.M{"$add": []interface{}{ bson.M{"$ifNull": []interface{}{"$" + realLikes, 0}}, bson.M{"$ifNull": []interface{}{"$" + fakeLikes, 0}}, }}}}, {"$match": bson.M{"rankScore": bson.M{"$gt": 0}}}, {"$sort": bson.D{ {Key: "rankScore", Value: -1}, {Key: updatedAt, Value: -1}, {Key: idField, Value: -1}, }}, {"$limit": limit}, } } func contentWatchRankOpts(watchField, updatedAt, idField string, limit int64) *options.FindOptions { return options.Find(). SetSort(bson.D{ {Key: watchField, Value: -1}, {Key: updatedAt, Value: -1}, {Key: idField, Value: -1}, }). SetLimit(limit) } func contentRankFillPipeline(idField, statusField string, statusVal interface{}, excludeIds []int64, needFill int64) []bson.M { return []bson.M{ {"$match": bson.M{ idField: bson.M{"$nin": excludeIds}, statusField: statusVal, }}, {"$sample": bson.M{"size": needFill}}, } } func periodWatchNum(p RankPeriod, week, month, year, total int64) int64 { switch p { case RankPeriodWeek: return week case RankPeriodMonth: return month case RankPeriodYear: return year default: return total } }