Initial commit: web_hghl_server
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user