227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
package displaycount
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"hash/fnv"
|
|
"math"
|
|
"strings"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/models/videofavoritemod"
|
|
"videoweb/pub/models/videoinformationmod"
|
|
"videoweb/pub/models/videolikemod"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
type countRow struct {
|
|
Id int64 `bson:"_id"`
|
|
Count int64 `bson:"count"`
|
|
}
|
|
|
|
type videoCounts struct {
|
|
Watch int64
|
|
Like int64
|
|
Favorite int64
|
|
}
|
|
|
|
// Value 真实数大于零时放大十倍,零值按内容和指标生成稳定展示数
|
|
func Value(real, id int64, kind string) int64 {
|
|
if real > 0 {
|
|
if real > math.MaxInt64/10 {
|
|
return math.MaxInt64
|
|
}
|
|
return real * 10
|
|
}
|
|
h := fnv.New64a()
|
|
_, _ = h.Write([]byte(kind))
|
|
var key [8]byte
|
|
binary.LittleEndian.PutUint64(key[:], uint64(id))
|
|
_, _ = h.Write(key[:])
|
|
return 100 + int64(h.Sum64()%901)
|
|
}
|
|
|
|
// Transform 将 App 内容接口中的现有计数字段转换为展示值
|
|
func Transform(c *gin.Context, data interface{}) interface{} {
|
|
path := c.Request.URL.Path
|
|
if !contentPath(path) || data == nil {
|
|
return data
|
|
}
|
|
raw, err := json.Marshal(data)
|
|
if err != nil {
|
|
return data
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
|
decoder.UseNumber()
|
|
var body interface{}
|
|
if err := decoder.Decode(&body); err != nil {
|
|
return data
|
|
}
|
|
ids := make(map[int64]struct{})
|
|
collectVideoIDs(body, path, ids)
|
|
counts, err := loadVideoCounts(c, ids)
|
|
if err != nil {
|
|
log.Warn("displaycount: load video counts failed", log.E(err))
|
|
return data
|
|
}
|
|
apply(body, path, c.Query("rankPeriod"), counts)
|
|
return body
|
|
}
|
|
|
|
func contentPath(path string) bool {
|
|
return strings.HasPrefix(path, "/api/app/playlet/") ||
|
|
strings.HasPrefix(path, "/api/app/video/") ||
|
|
strings.HasPrefix(path, "/api/app/home") ||
|
|
strings.HasPrefix(path, "/api/app/actress/work/") ||
|
|
strings.HasPrefix(path, "/search")
|
|
}
|
|
|
|
func collectVideoIDs(node interface{}, path string, ids map[int64]struct{}) {
|
|
switch item := node.(type) {
|
|
case map[string]interface{}:
|
|
if isVideo(item, path) {
|
|
if id := number(item["id"]); id > 0 {
|
|
ids[id] = struct{}{}
|
|
}
|
|
}
|
|
for _, child := range item {
|
|
collectVideoIDs(child, path, ids)
|
|
}
|
|
case []interface{}:
|
|
for _, child := range item {
|
|
collectVideoIDs(child, path, ids)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isVideo(item map[string]interface{}, path string) bool {
|
|
if item["type"] != nil && number(item["type"]) == 2 {
|
|
return false
|
|
}
|
|
if _, ok := item["watchNum"]; ok {
|
|
_, hasLike := item["likeNum"]
|
|
return hasLike
|
|
}
|
|
if _, ok := item["watchTimes"]; ok {
|
|
_, hasVideo := item["video"]
|
|
return hasVideo && number(item["type"]) != 2
|
|
}
|
|
return path == "/api/app/video/like" && item["likeNum"] != nil && item["id"] != nil
|
|
}
|
|
|
|
func loadVideoCounts(c *gin.Context, idSet map[int64]struct{}) (map[int64]videoCounts, error) {
|
|
result := make(map[int64]videoCounts, len(idSet))
|
|
if len(idSet) == 0 {
|
|
return result, nil
|
|
}
|
|
ids := make([]int64, 0, len(idSet))
|
|
for id := range idSet {
|
|
ids = append(ids, id)
|
|
}
|
|
videos, err := videoinformationmod.FindManyVideoInformations(c, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": ids},
|
|
}, options.Find().SetProjection(bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: 1,
|
|
videoinformationmod.FN.VideoInformation.Real.WatchTimes: 1,
|
|
}))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, video := range videos {
|
|
result[video.Id] = videoCounts{Watch: video.Real.WatchTimes}
|
|
}
|
|
likes := make([]countRow, 0)
|
|
if err := videolikemod.AggregateVideoLike(c, &likes, countPipeline(videolikemod.FN.VideoLike.VideoID, ids)); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, row := range likes {
|
|
value := result[row.Id]
|
|
value.Like = row.Count
|
|
result[row.Id] = value
|
|
}
|
|
favorites := make([]countRow, 0)
|
|
if err := videofavoritemod.AggregateVideoFavorite(c, &favorites, countPipeline(videofavoritemod.FN.VideoFavorite.VideoID, ids)); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, row := range favorites {
|
|
value := result[row.Id]
|
|
value.Favorite = row.Count
|
|
result[row.Id] = value
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func countPipeline(field string, ids []int64) []bson.M {
|
|
return []bson.M{
|
|
{"$match": bson.M{field: bson.M{"$in": ids}}},
|
|
{"$group": bson.M{"_id": "$" + field, "count": bson.M{"$sum": 1}}},
|
|
}
|
|
}
|
|
|
|
func apply(node interface{}, path, rankPeriod string, counts map[int64]videoCounts) {
|
|
switch item := node.(type) {
|
|
case map[string]interface{}:
|
|
id := number(item["id"])
|
|
if id > 0 && isVideo(item, path) {
|
|
count := counts[id]
|
|
if _, ok := item["watchNum"]; ok {
|
|
item["watchNum"] = Value(count.Watch, id, "video-watch")
|
|
}
|
|
if _, ok := item["watchTimes"]; ok {
|
|
item["watchTimes"] = Value(count.Watch, id, "video-watch")
|
|
}
|
|
item["likeNum"] = Value(count.Like, id, "video-like")
|
|
item["countCollect"] = Value(count.Favorite, id, "video-favorite")
|
|
} else if id > 0 && item["countBrowse"] != nil && item["countCollect"] != nil {
|
|
kind := "playlet"
|
|
if item["currentEpisode"] != nil {
|
|
kind = "playlet-chapter"
|
|
}
|
|
if item["rankScore"] != nil && path == "/api/app/playlet/rank/list" {
|
|
if rankPeriod == "" || rankPeriod == "0" {
|
|
item["rankScore"] = Value(number(item["countLike"]), id, kind+"-like")
|
|
} else {
|
|
item["rankScore"] = Value(number(item["rankScore"]), id, kind+"-period-watch-"+rankPeriod)
|
|
}
|
|
}
|
|
item["countBrowse"] = Value(number(item["countBrowse"]), id, kind+"-watch")
|
|
item["countCollect"] = Value(number(item["countCollect"]), id, kind+"-favorite")
|
|
if item["displayCountLike"] != nil {
|
|
like := Value(number(item["countLike"]), id, kind+"-like")
|
|
item["displayCountLike"] = like
|
|
item["countLike"] = like
|
|
} else if item["countLike"] != nil {
|
|
item["countLike"] = Value(number(item["countLike"]), id, kind+"-like")
|
|
}
|
|
} else if id > 0 && path == "/api/app/playlet/favorite" && item["countCollect"] != nil {
|
|
item["countCollect"] = Value(number(item["countCollect"]), id, "playlet-favorite")
|
|
} else if id > 0 && path == "/api/app/playlet/like" && item["likeNum"] != nil {
|
|
item["likeNum"] = Value(number(item["likeNum"]), id, "playlet-like")
|
|
}
|
|
for _, child := range item {
|
|
apply(child, path, rankPeriod, counts)
|
|
}
|
|
case []interface{}:
|
|
for _, child := range item {
|
|
apply(child, path, rankPeriod, counts)
|
|
}
|
|
}
|
|
}
|
|
|
|
func number(v interface{}) int64 {
|
|
switch n := v.(type) {
|
|
case json.Number:
|
|
value, _ := n.Int64()
|
|
return value
|
|
case int64:
|
|
return n
|
|
case float64:
|
|
return int64(n)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|