Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+409
View File
@@ -0,0 +1,409 @@
package vidser
import (
"91porn-server/app/appg"
"91porn-server/app/service/vidhelpser"
"91porn-server/common"
"91porn-server/common/constant"
"91porn-server/common/constant/redisconst"
"91porn-server/common/elastic"
"91porn-server/common/log"
"91porn-server/models/commod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/mediatagmod"
"91porn-server/models/v/tagmod"
"91porn-server/models/v/vidmod"
"fmt"
"math/rand"
"sync"
"time"
"github.com/vmihailenco/msgpack/v5"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewsTypeToSting(in string) string {
var libraryTypeName string
switch in {
case constant.SP:
libraryTypeName = "影片"
case constant.SHORT:
libraryTypeName = "抖音"
case constant.Cartoon:
libraryTypeName = "动漫"
case constant.Comics:
libraryTypeName = "漫画"
case constant.PIC:
libraryTypeName = "图集"
case constant.COVER:
libraryTypeName = "帖子"
}
return libraryTypeName
}
// GetLibrary 获取片库详情
func GetLibrary(uid uint64) (res vidmod.LibraryData, err error) {
var data vidmod.LibraryData
// 获取缓存
str, err := appg.Redis.Get(redisconst.VideoLibraryCache)
if err != nil {
log.Warn(fmt.Sprintf("uid:%v;缓存获取片库信息异常:%v", uid, err))
}
if str != nil {
if err = msgpack.Unmarshal([]byte(*str), &data); err == nil {
return data, nil
}
log.Warn(fmt.Sprintf("uid:%v;解析片库缓存数据异常:%v", uid, err))
}
data.OrderBy = append(data.OrderBy, vidmod.SortKey{
Key: "new",
Name: "最新上架",
}, vidmod.SortKey{
Key: "playNum",
Name: "最多观看",
}, vidmod.SortKey{
Key: "love",
Name: "最多收藏",
})
data.Canvas = append(data.Canvas, vidmod.SortKey{
Key: constant.SP,
Name: NewsTypeToSting(constant.SP),
}, vidmod.SortKey{
Key: constant.SHORT,
Name: NewsTypeToSting(constant.SHORT),
}, vidmod.SortKey{
Key: constant.Cartoon,
Name: NewsTypeToSting(constant.Cartoon),
}, vidmod.SortKey{
Key: constant.Comics,
Name: NewsTypeToSting(constant.Comics),
}, vidmod.SortKey{
Key: constant.PIC,
Name: NewsTypeToSting(constant.PIC),
}, vidmod.SortKey{
Key: constant.COVER,
Name: NewsTypeToSting(constant.COVER),
})
data.PaymentType = append(data.PaymentType, vidmod.SortKey{
Key: "",
Name: "全部",
}, vidmod.SortKey{
Key: "vip",
Name: "VIP",
}, vidmod.SortKey{
Key: "point",
Name: "金币",
})
data.TimeType = append(
data.TimeType, vidmod.SortKey{
Key: "1",
Name: "本月",
}, vidmod.SortKey{
Key: "2",
Name: "三个月内",
}, vidmod.SortKey{
Key: "3",
Name: "半年内",
}, vidmod.SortKey{
Key: "4",
Name: "更久",
})
data.VidTags = append(data.VidTags, vidmod.Tag{
ID: "",
Name: "全部类型",
})
data.ACGTags = append(data.ACGTags, vidmod.Tag{
ID: "",
Name: "全部类型",
})
wg := sync.WaitGroup{}
wg.Add(2)
common.Go(func() {
defer wg.Done()
// 获取最热标签
tags, _, err := tagmod.GetLibraryTagList(commod.Page{PageNumber: 1, PageSize: 20})
if err != nil {
return
}
if len(tags) > 0 {
for _, t := range tags {
data.VidTags = append(data.VidTags, vidmod.Tag{
ID: t.ID.Hex(),
Name: t.TagName,
})
}
}
})
common.Go(func() {
defer wg.Done()
// 获取ACG所有推荐标签
tagFilter := bson.M{"active": true, "isDelete": false, "isDiscovery": true}
tagOp := options.Find().SetLimit(20).SetSort(bson.D{{Key: "sort", Value: -1}})
mediaTags, err := mediatagmod.QueryAllList(tagFilter, tagOp)
if err != nil {
return
}
if len(mediaTags) > 0 {
for _, t := range mediaTags {
data.ACGTags = append(data.ACGTags, vidmod.Tag{
ID: t.ID.Hex(),
Name: t.Name,
})
}
}
})
wg.Wait()
common.Go(func() {
// 加入缓存 随机1-3分钟缓存
random := rand.Intn(120) + 60
d, err := msgpack.Marshal(data)
if err != nil {
return
}
if err = appg.Redis.Set(redisconst.VideoLibraryCache, d, time.Duration(random)*time.Second); err != nil {
log.Warn(fmt.Sprintf("uid:%v, 保存缓存数据异常:%v", uid, err))
}
})
return data, nil
}
type LibraryElasticSearchRequest struct {
Keyword LibraryReq `json:"keyword" bson:"keyword" binding:"required"` // 关键词
UID uint64 `json:"-"` // 用户ID
commod.Page
}
type LibraryElasticSearchResponse struct {
List []vidmod.ESVideo `json:"list"` // list
HasNext bool `json:"hasNext"` // hasNext
Total int `json:"total"` // total
}
type LibraryReq struct {
Canvas SortKey `json:"canvas" bson:"canvas"` // 视频分类
OrderBy SortKey `json:"orderBy" bson:"orderBy"` // 视频排序
Tags SearchTag `json:"tags" bson:"tags"` // 全部标签
PaymentType SortKey `json:"paymentType" bson:"paymentType"` // 付费分类
TimeType SortKey `json:"timeType" bson:"timeType"` // 时间排序
}
type LibraryData struct {
Canvas []SortKey `json:"canvas" bson:"canvas"` // 视频分类
OrderBy []SortKey `json:"orderBy" bson:"orderBy"` // 视频排序
VidTags []SearchTag `json:"vidTags" bson:"vidTags"` // 全部视频标签
ACGTags []SearchTag `json:"acgTags" bson:"acgTags"` // 全部ACG标签
PaymentType []SortKey `json:"paymentType" bson:"paymentType"` // 付费分类
TimeType []SortKey `json:"timeType" bson:"timeType"` // 时间排序
}
type SortKey struct {
Key string `json:"key" bson:"key"` // 健值
Name string `json:"name" bson:"name"` // 健名称
}
type SearchTag struct {
ID string `json:"id" bson:"id"` // 标签ID
Name string `json:"name" bson:"name"` // 标签名称
}
type AppElasticSearchLibraryResponse struct {
List []*vidmod.VideoInfoResp `json:"list" bson:"list"` // 视频列表
AllMediaList []*mediamod.AppMediaBase `json:"allMediaList" bson:"allMediaList"` // ACG列表
HasNext bool `json:"hasNext" bson:"hasNext"` // 是否下一页
Total int `json:"total" bson:"total"` // 总数
}
func (in *LibraryElasticSearchRequest) CheckSearchType() (AppElasticSearchLibraryResponse, error) {
var (
data AppElasticSearchLibraryResponse
err error
)
switch in.Keyword.Canvas.Key {
case constant.SP, constant.SHORT, constant.PIC, constant.COVER:
data, err = in.VidLibrarySearch()
case constant.Cartoon, constant.Comics:
data, err = in.MediaLibrarySearch()
}
if err != nil {
return data, err
}
return data, nil
}
func (in *LibraryElasticSearchRequest) VidLibrarySearch() (AppElasticSearchLibraryResponse, error) {
resp := AppElasticSearchLibraryResponse{}
from := in.Skip64()
size := in.Limit64()
must := elastic.A{
{"term": elastic.M{"newsType.keyword": in.Keyword.Canvas.Key}},
}
if in.Keyword.Tags.ID != "" {
tag := elastic.M{"term": elastic.M{"tags.keyword": in.Keyword.Tags.ID}}
must = append(must, tag)
}
timeType := HandleTimeType(in.Keyword.TimeType.Key)
must = append(must, timeType...)
payType := HandlePayType(in.Keyword.PaymentType.Key)
must = append(must, payType...)
short := HandleShort(in.Keyword.OrderBy.Key)
// 根据数据结构组装数据
query := elastic.M{
"query": elastic.M{
"bool": elastic.M{
"must": must,
},
},
"sort": short,
"from": from,
"size": size + 1,
}
res, err := vidmod.SearchByCondWithTotal(query)
if err != nil {
return resp, nil
}
hasNext := false
videosIDs := make([]primitive.ObjectID, 0)
for _, v := range res.Hits {
videosIDs = append(videosIDs, v.Source.ID)
}
if len(videosIDs) > int(size) {
hasNext = true
videosIDs = videosIDs[:size]
}
vidMods, err := vidmod.GetVideoListByIDs(videosIDs)
if err != nil {
return resp, nil
}
var (
sortVidModels = make([]*vidmod.VideoModel, 0)
vidMap = make(map[primitive.ObjectID]*vidmod.VideoModel)
)
for _, videoModel := range vidMods {
vidMap[videoModel.ID] = videoModel
}
for _, vid := range videosIDs {
if v, ok := vidMap[vid]; ok {
sortVidModels = append(sortVidModels, v)
}
}
data := vidhelpser.NewEncodeVideoInfoNotStatusForSearch(in.UID, sortVidModels)
resp.List = data
resp.HasNext = hasNext
resp.Total = res.Total.Value
// 查询结果数量出现不一致,说名有的帖子不存在了,需要删除
if len(videosIDs) > 0 && len(videosIDs) != len(vidMods) {
common.Go(func() {
HandleDiffVideo(vidMap, videosIDs)
})
}
return resp, nil
}
func HandleDiffVideo(vidMods map[primitive.ObjectID]*vidmod.VideoModel, videosIDs []primitive.ObjectID) {
var source = elastic.M{}
var diffVid []string
for _, vid := range videosIDs {
if _, ok := vidMods[vid]; !ok {
source[vid.Hex()] = vid.Hex()
diffVid = append(diffVid, vid.Hex())
}
}
if len(source) > 0 {
log.Info(fmt.Sprintf("search delete videosIDs:%v", diffVid))
if err := vidmod.DeleteByCond(source); err != nil {
log.Info(fmt.Sprintf("Elastic del data failed, err:%v", err))
return
}
log.Info(fmt.Sprintf("Elastic del data succsss, count:%v", len(source)))
}
}
func HandleTimeType(key string) elastic.A {
var timeTypeValue = elastic.A{}
now := time.Now()
switch key {
case "1": // 本月
timeTypeValue = append(timeTypeValue,
elastic.M{"range": elastic.M{"reviewAt": elastic.M{"gt": now.AddDate(0, -1, 0)}}},
)
case "2": // 3月内
timeTypeValue = append(timeTypeValue,
elastic.M{"range": elastic.M{"reviewAt": elastic.M{"gt": now.AddDate(0, -3, 0), "lt": now.AddDate(0, -1, 0)}}},
)
case "3": // 半年内
timeTypeValue = append(timeTypeValue,
elastic.M{"range": elastic.M{"reviewAt": elastic.M{"gt": now.AddDate(0, -6, 0), "lt": now.AddDate(0, -3, 0)}}},
)
case "4": // 更久
timeTypeValue = append(timeTypeValue,
elastic.M{"range": elastic.M{"reviewAt": elastic.M{"gt": now.AddDate(-5, 0, 0), "lt": now.AddDate(0, -6, 0)}}},
)
default:
timeTypeValue = append(timeTypeValue,
elastic.M{"range": elastic.M{"reviewAt": elastic.M{"gt": now.AddDate(-3, -2, 0)}}},
)
}
return timeTypeValue
}
func HandlePayType(key string) elastic.A {
var payTypeValue = elastic.A{}
switch key {
case "free":
payTypeValue = append(payTypeValue,
elastic.M{"term": elastic.M{"status": 3}},
elastic.M{"term": elastic.M{"coins": 0}},
)
case "vip":
payTypeValue = append(payTypeValue,
elastic.M{"term": elastic.M{"status": 1}},
elastic.M{"term": elastic.M{"coins": 0}},
)
case "point":
payTypeValue = append(payTypeValue,
elastic.M{"terms": elastic.M{"status": []int{1, 3}}},
elastic.M{"range": elastic.M{"coins": elastic.M{"gt": 0}}},
)
default:
payTypeValue = append(payTypeValue, elastic.M{"terms": elastic.M{"status": []int{1, 3}}})
}
return payTypeValue
}
func HandleShort(key string) elastic.A {
var payTypeValue elastic.A
switch key {
case "playNum":
payTypeValue = elastic.A{{"playCount": elastic.M{"order": "desc"}}}
case "love":
payTypeValue = elastic.A{{"collectCount": elastic.M{"order": "desc"}}}
case "new":
payTypeValue = elastic.A{{"reviewAt": elastic.M{"order": "desc"}}}
default:
payTypeValue = elastic.A{{"fakeLikeCount": elastic.M{"order": "desc"}}, {"reviewAt": elastic.M{"order": "desc"}}}
}
return payTypeValue
}