145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
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
|
|
}
|
|
}
|