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 }