Files
2026-10-26 16:28:14 +08:00

488 lines
15 KiB
Go

package searchser
import (
"context"
"fmt"
"sync"
"time"
"videoweb/app/api/proto"
"videoweb/app/service/configser"
"videoweb/pub/common/log"
"videoweb/pub/common/netutil"
"videoweb/pub/common/srvdef"
"videoweb/pub/models/contenttagmod"
"videoweb/pub/models/esmod"
"videoweb/pub/models/playletmod"
"videoweb/pub/models/playletthememod"
"videoweb/pub/models/videoinformationmod"
"videoweb/pub/pubdef"
"github.com/patrickmn/go-cache"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
Cache *cache.Cache
)
func init() {
Cache = cache.New(5*time.Minute, 5*time.Minute)
}
func GetHotSearchItem(ctx context.Context) []*proto.HotSearchItem {
cacheKey := fmt.Sprintf("mdnet_hotsearchitem")
var resp []*proto.HotSearchItem
if cacheData, exists := Cache.Get(cacheKey); exists {
if item, ok := cacheData.([]*proto.HotSearchItem); ok {
resp = item
}
}
if resp == nil || len(resp) == 0 {
opts := &options.FindOptions{}
opts.SetSort(bson.M{
videoinformationmod.FN.VideoInformation.Sort: -1,
})
opts.SetLimit(15)
list, _ := videoinformationmod.FindManyVideoInformations(ctx, bson.M{
videoinformationmod.FN.VideoInformation.AddedTime: bson.M{
"$gte": time.Now().AddDate(0, 0, -7),
},
videoinformationmod.FN.VideoInformation.Status: pubdef.InformationStatusVerifyOK,
}, opts)
data := make([]*proto.HotSearchItem, 0)
if len(list) > 0 {
for _, item := range list {
data = append(data, &proto.HotSearchItem{
Id: item.Id,
Title: item.Title,
HotValue: item.Real.WatchTimes * 100,
})
}
}
resp = data
Cache.Set(cacheKey, data, 5*time.Minute)
}
return resp
}
// Search 查询搜索结果
func Search(ctx context.Context, title string, mediaType int64, page int64, sort string) *proto.SearchResp {
resp := &proto.SearchResp{
TotalSize: 0,
Sort: make([]proto.SearchSortItem, 0),
SearchRecommend: make([]string, 0),
Media: make([]proto.VideoInfoData, 0),
Playlets: make([]proto.PlayletData, 0),
PageNum: page,
PageSize: pubdef.PageSize,
}
resp.InTitle = title
resp.InType = mediaType
resp.Sort = getSearchSort()
resp.SearchRecommend = getSearchRecommend(title)
resp.Config = configser.GetRequestConfig(ctx)
var err error
if len(title) > 0 {
if mediaType < 0 {
searchAll(ctx, resp, title, page, sort)
} else if mediaType == 0 {
resp.TotalSize, resp.Media, err = searchVideo(ctx, title, page, sort)
if err != nil {
log.Warn("Search searchVideo Failed", log.Any("mediaType", mediaType), log.Any("title", title), log.E(err))
return resp
}
} else if mediaType == 6 {
resp.TotalSize, resp.Playlets, err = searchPlaylet(ctx, title, page, sort)
}
}
resp.TotalSize = pubdef.GetTotalPage(resp.TotalSize, pubdef.PageSize)
return resp
}
// searchAll 并发搜索全部资源类型
func searchAll(ctx context.Context, resp *proto.SearchResp, title string, page int64, sort string) {
var wg sync.WaitGroup
var mu sync.Mutex
var (
mediaList []proto.VideoInfoData
playletList []proto.PlayletData
)
addTotal := func(total int64) {
mu.Lock()
resp.TotalSize += total
mu.Unlock()
}
wg.Add(2)
go func() {
defer wg.Done()
total, data, err := searchVideo(ctx, title, page, sort)
if err != nil {
log.Warn("SearchAll searchVideo Failed", log.Any("title", title), log.E(err))
return
}
mu.Lock()
mediaList = data
mu.Unlock()
addTotal(total)
}()
go func() {
defer wg.Done()
total, data, err := searchPlaylet(ctx, title, page, sort)
if err != nil {
log.Warn("SearchAll searchPlaylet Failed", log.Any("title", title), log.E(err))
return
}
mu.Lock()
playletList = data
mu.Unlock()
addTotal(total)
}()
wg.Wait()
resp.Media = mediaList
resp.Playlets = playletList
}
// searchVideo 搜索视频数据
func searchVideo(ctx context.Context, title string, page int64, sort string) (int64, []proto.VideoInfoData, error) {
data := make([]proto.VideoInfoData, 0)
total := int64(0)
skip := (page - 1) * pubdef.PageSize
total, list, err := esmod.SearchVideo(title, skip, pubdef.PageSize, sort, false)
if err != nil {
log.Warn("Search SearchVideo", log.Any("title", title), log.Any("sort", sort), log.E(err))
return total, data, err
}
if list != nil && len(list) > 0 {
config := configser.GetRequestConfig(ctx)
ids := make([]int64, len(list))
for i, v := range list {
ids[i] = v.Id
}
medias, err := videoinformationmod.FindManyVideoInformations(ctx, bson.M{
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": ids},
videoinformationmod.FN.VideoInformation.Status: pubdef.InformationStatusVerifyOK,
}, options.Find().SetProjection(searchVideoProjection()))
if err != nil {
log.Warn("Search FindManyVideoInformations", log.Any("title", title), log.E(err))
return total, data, err
}
mediaMap := make(map[int64]*videoinformationmod.VideoInformation, len(medias))
for _, media := range medias {
mediaMap[media.Id] = media
}
for _, id := range ids {
media := mediaMap[id]
if media == nil {
continue
}
info := proto.VideoInfoToData(media, config.ImgCdn, configser.GetCreateInfo(media.CreatorId, config.ImgCdn))
for _, tagId := range media.Tags {
tagInfo := configser.GetVideoTag(tagId)
if tagInfo != nil {
info.Tags = append(info.Tags, proto.TagsInfoData{
Id: tagInfo.Id,
Name: tagInfo.Name,
Slug: tagInfo.Slug,
})
}
}
data = append(data, info)
}
}
return total, data, nil
}
// searchPlaylet 搜索短剧数据
func searchPlaylet(ctx context.Context, title string, page int64, sort string) (int64, []proto.PlayletData, error) {
data := make([]proto.PlayletData, 0)
total := int64(0)
skip := (page - 1) * pubdef.PageSize
total, list, err := esmod.SearchPlaylet(title, skip, pubdef.PageSize, sort)
if err != nil {
log.Warn("Search SearchPlaylet", log.Any("title", title), log.Any("sort", sort), log.E(err))
return total, data, err
}
if len(list) == 0 {
return total, data, nil
}
config := configser.GetRequestConfig(ctx)
ids := make([]int64, 0, len(list))
for _, item := range list {
if item != nil && item.Id > 0 {
ids = append(ids, item.Id)
}
}
if len(ids) == 0 {
return total, data, nil
}
playlets, err := playletmod.FindManyPlaylet(ctx, bson.M{
playletmod.FN.Playlet.Id: bson.M{"$in": ids},
playletmod.FN.Playlet.Status: playletmod.StatusUp,
}, options.Find().SetProjection(searchPlayletProjection()))
if err != nil {
log.Warn("Search FindManyPlaylet", log.Any("title", title), log.E(err))
return total, data, err
}
playletMap := make(map[int64]*playletmod.Playlet, len(playlets))
for _, item := range playlets {
if item != nil {
playletMap[item.Id] = item
}
}
tagNameMap, themeNameMap, err := buildSearchPlayletTagThemeNameMaps(ctx, playlets)
if err != nil {
return total, data, err
}
data = make([]proto.PlayletData, 0, len(ids))
for _, id := range ids {
item := playletMap[id]
if item == nil {
continue
}
data = append(data, searchPlayletToData(item, config.ImgCdn, tagNameMap, themeNameMap))
}
return total, data, nil
}
// searchVideoProjection 返回搜索视频列表需要的Mongo字段
func searchVideoProjection() bson.M {
return bson.M{
videoinformationmod.FN.VideoInformation.Id: 1,
videoinformationmod.FN.VideoInformation.MediaType: 1,
videoinformationmod.FN.VideoInformation.Title: 1,
videoinformationmod.FN.VideoInformation.Slug: 1,
videoinformationmod.FN.VideoInformation.Keyworld: 1,
videoinformationmod.FN.VideoInformation.AddedTime: 1,
videoinformationmod.FN.VideoInformation.CreatorId: 1,
videoinformationmod.FN.VideoInformation.CoverImg: 1,
videoinformationmod.FN.VideoInformation.Video.Cover: 1,
videoinformationmod.FN.VideoInformation.Video.Url: 1,
videoinformationmod.FN.VideoInformation.Video.H265Url: 1,
videoinformationmod.FN.VideoInformation.Video.VideoTime: 1,
videoinformationmod.FN.VideoInformation.IsHot: 1,
videoinformationmod.FN.VideoInformation.Real.ShowTimes: 1,
videoinformationmod.FN.VideoInformation.Real.WatchTimes: 1,
videoinformationmod.FN.VideoInformation.Desc: 1,
videoinformationmod.FN.VideoInformation.Bango: 1,
videoinformationmod.FN.VideoInformation.IssuedBrand: 1,
videoinformationmod.FN.VideoInformation.IssuedAt: 1,
videoinformationmod.FN.VideoInformation.UpdatedAt: 1,
videoinformationmod.FN.VideoInformation.PageUpdatedAt: 1,
videoinformationmod.FN.VideoInformation.Tags: 1,
}
}
// searchPlayletProjection 返回搜索短剧列表需要的Mongo字段
func searchPlayletProjection() bson.M {
return bson.M{
playletmod.FN.Playlet.Id: 1,
playletmod.FN.Playlet.Status: 1,
playletmod.FN.Playlet.IsRecommend: 1,
playletmod.FN.Playlet.IsFeatured: 1,
playletmod.FN.Playlet.IsOriginal: 1,
playletmod.FN.Playlet.ShowOriginal: 1,
playletmod.FN.Playlet.MediaCenterId: 1,
playletmod.FN.Playlet.OnlineAt: 1,
playletmod.FN.Playlet.MediaCreatedAt: 1,
playletmod.FN.Playlet.MediaUpdatedAt: 1,
playletmod.FN.Playlet.Title: 1,
playletmod.FN.Playlet.Slug: 1,
playletmod.FN.Playlet.Desc: 1,
playletmod.FN.Playlet.Anchor: 1,
playletmod.FN.Playlet.Cover: 1,
playletmod.FN.Playlet.CompressedCover: 1,
playletmod.FN.Playlet.Preview: 1,
playletmod.FN.Playlet.TagIds: 1,
playletmod.FN.Playlet.ThemeIds: 1,
playletmod.FN.Playlet.ContentType: 1,
playletmod.FN.Playlet.PayType: 1,
playletmod.FN.Playlet.SingleCoins: 1,
playletmod.FN.Playlet.TotalEpisode: 1,
playletmod.FN.Playlet.UpdateStatus: 1,
playletmod.FN.Playlet.CountBrowse: 1,
playletmod.FN.Playlet.VirtualCountBrowse: 1,
playletmod.FN.Playlet.HeatValue: 1,
playletmod.FN.Playlet.VirtualHeatValue: 1,
playletmod.FN.Playlet.CountCollect: 1,
playletmod.FN.Playlet.CountLike: 1,
playletmod.FN.Playlet.VirtualCountLike: 1,
playletmod.FN.Playlet.SeoInfo.Title: 1,
playletmod.FN.Playlet.SeoInfo.Description: 1,
playletmod.FN.Playlet.SeoInfo.Keyworld: 1,
playletmod.FN.Playlet.CreatedAt: 1,
playletmod.FN.Playlet.UpdatedAt: 1,
}
}
func searchPlayletToData(item *playletmod.Playlet, imgCdn string, tagNameMap contenttagmod.ContentTagMap, themeNameMap map[int64]string) proto.PlayletData {
if item == nil {
return proto.PlayletData{}
}
return proto.PlayletData{
Id: item.Id,
Status: item.Status,
IsRecommend: item.IsRecommend,
IsNew: searchPlayletIsNew(item.OnlineAt),
IsFeatured: item.IsFeatured,
IsOriginal: item.IsOriginal || item.MediaCenterId == 0,
ShowOriginal: item.ShowOriginal,
OnlineAt: item.OnlineAt,
MediaCreatedAt: item.MediaCreatedAt,
MediaUpdatedAt: item.MediaUpdatedAt,
Title: item.Title,
Slug: item.Slug,
Desc: item.Desc,
Anchor: item.Anchor,
Cover: netutil.MergeURL(imgCdn, item.DisplayCover()),
Preview: netutil.MergeURL(imgCdn, item.Preview),
TagIds: item.TagIds,
TagNames: searchPlayletTagNamesFromMap(item.TagIds, tagNameMap),
TagSlugs: searchPlayletTagSlugsFromMap(item.TagIds, tagNameMap),
ThemeIds: item.ThemeIds,
ThemeNames: searchPlayletNamesFromMap(item.ThemeIds, themeNameMap),
ContentType: item.ContentType,
PayType: item.PayType,
SingleCoins: item.SingleCoins,
TotalEpisode: item.TotalEpisode,
UpdateStatus: item.UpdateStatus,
CountBrowse: item.DisplayCountBrowse(),
HeatValue: item.DisplayHeatValue(),
CountCollect: item.CountCollect,
CountLike: item.CountLike,
VirtualCountLike: item.VirtualCountLike,
DisplayCountLike: item.CountLike + item.VirtualCountLike,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
SeoInfo: item.SeoInfo,
}
}
func buildSearchPlayletTagThemeNameMaps(ctx context.Context, list playletmod.PlayletList) (contenttagmod.ContentTagMap, map[int64]string, *srvdef.SrvErr) {
tagIds := searchPlayletRelationIds(list, func(item *playletmod.Playlet) []int64 {
return item.TagIds
})
themeIds := searchPlayletRelationIds(list, func(item *playletmod.Playlet) []int64 {
return item.ThemeIds
})
tagNameMap, err := buildSearchPlayletTagNameMap(ctx, tagIds)
if err != nil {
return nil, nil, err
}
themeNameMap, err := buildSearchPlayletThemeNameMap(ctx, themeIds)
if err != nil {
return nil, nil, err
}
return tagNameMap, themeNameMap, nil
}
func buildSearchPlayletTagNameMap(ctx context.Context, ids []int64) (contenttagmod.ContentTagMap, *srvdef.SrvErr) {
result := make(contenttagmod.ContentTagMap, len(ids))
if len(ids) == 0 {
return result, nil
}
return contenttagmod.GetMapTags(ctx, ids)
}
func searchPlayletTagNamesFromMap(ids []int64, tags contenttagmod.ContentTagMap) []string {
result := make([]string, 0, len(ids))
for _, id := range ids {
if tag := tags[id]; tag != nil && tag.Name != "" {
result = append(result, tag.Name)
}
}
return result
}
func searchPlayletTagSlugsFromMap(ids []int64, tags contenttagmod.ContentTagMap) []string {
result := make([]string, 0, len(ids))
for _, id := range ids {
if tag := tags[id]; tag != nil && tag.Name != "" {
result = append(result, tag.Slug)
}
}
return result
}
func buildSearchPlayletThemeNameMap(ctx context.Context, ids []int64) (map[int64]string, *srvdef.SrvErr) {
result := make(map[int64]string, len(ids))
if len(ids) == 0 {
return result, nil
}
list, err := playletthememod.FindManyPlayletTheme(ctx, bson.M{
playletthememod.FN.PlayletTheme.Id: bson.M{"$in": ids},
}, options.Find().SetProjection(bson.D{
{Key: playletthememod.FN.PlayletTheme.Id, Value: 1},
{Key: playletthememod.FN.PlayletTheme.Name, Value: 1},
}))
if err != nil {
return nil, err
}
for _, item := range list {
if item != nil {
result[item.Id] = item.Name
}
}
return result, nil
}
func searchPlayletRelationIds(list playletmod.PlayletList, pick func(item *playletmod.Playlet) []int64) []int64 {
result := make([]int64, 0)
seen := make(map[int64]struct{})
for _, item := range list {
if item == nil {
continue
}
for _, id := range pick(item) {
if id <= 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
result = append(result, id)
}
}
return result
}
func searchPlayletNamesFromMap(ids []int64, nameMap map[int64]string) []string {
result := make([]string, 0, len(ids))
for _, id := range ids {
if name := nameMap[id]; name != "" {
result = append(result, name)
}
}
return result
}
func searchPlayletIsNew(onlineAt time.Time) bool {
if onlineAt.IsZero() {
return false
}
return onlineAt.After(time.Now().AddDate(0, 0, -7))
}
// getSearchRecommend 返回搜索推荐词
func getSearchRecommend(title string) []string {
return make([]string, 0)
}
func getSearchSort() []proto.SearchSortItem {
resp := make([]proto.SearchSortItem, 0)
resp = append(resp, proto.SearchSortItem{
Key: string(proto.SearchSortTypeDefault),
Title: "相关度",
})
resp = append(resp, proto.SearchSortItem{
Key: string(proto.SearchSortTypeHot),
Title: "热度最高",
})
resp = append(resp, proto.SearchSortItem{
Key: string(proto.SearchSortTypeNew),
Title: "最新发布",
})
return resp
}