package searcher import ( "sync" "time" "91porn-server/app/service/searchaccessser" "91porn-server/app/service/vidhelpser" "91porn-server/common" "91porn-server/common/log" "91porn-server/models/l/payvidlgmod" "91porn-server/models/v/cmtmod" "91porn-server/models/v/freeVidmod" "91porn-server/models/v/moduleconfmod" "91porn-server/models/v/productmod" "91porn-server/models/v/tagmod" "91porn-server/models/v/usermod" "91porn-server/models/v/videodiscountmod" "91porn-server/models/v/vidmod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) // A A type A = bson.A // M M type M = bson.M // D D type D = bson.D // E E type E = bson.E // ObjectID ObjectID type ObjectID = primitive.ObjectID // VideoInfo VideoInfo type VideoInfo struct { ID *ObjectID `json:"id" bson:"_id"` NewsType *string `json:"newsType"` //帖子类型, VID,视频帖子,COVER Title *string `json:"title,omitempty"` //视频标题 Tags []TagInfo `json:"tags"` //视频标签 SourceID *string `json:"sourceID,omitempty"` //视频在仓库中的资源ID SourceURL *string `json:"sourceURL,omitempty"` //视频资源地址Path H265Url *string `json:"h265Url"` // H.265 视频资源地址 PlayTime *uint `json:"playTime,omitempty"` //影片长度 Cover *string `json:"cover,omitempty"` //封面大图 CoverThumb *string `json:"coverThumb,omitempty"` //封⾯小图 SeriesCover []string `json:"seriesCover"` //封面套图 Via *string `json:"via,omitempty"` //来源 自拍,上传 PlayCount *int `json:"playCount,omitempty"` //总的播放量量(假数据) LikeCount *int `json:"likeCount,omitempty"` //点赞数(假数据) PayCount *int `json:"payCount,omitempty"` //购买数 CommentCount *int `json:"commentCount,omitempty"` //评论数(假数据) ShareCount *int `json:"shareCount,omitempty"` //分享数(假数据) Coins *int64 `json:"coins,omitempty"` //折扣后价格 Size *int `json:"size,omitempty"` //文件大小 byte Status *int `json:"status,omitempty"` //状态,0 未审核 1通过 2审核失败 3视为免费 默认为0 Resolution *string `json:"resolution,omitempty"` //分辨率 Ratio *float64 `json:"ratio,omitempty"` //宽高比 MimeType *string `json:"mimeType,omitempty"` //视频格式类型 Actor *string `json:"actor,omitempty"` FreeTime *int `json:"freeTime,omitempty"` //免费观影时长 CreatedAt *time.Time `json:"createdAt,omitempty"` //创建时间 ReviewedAt *time.Time `json:"reviewAt,omitempty"` // 审核时间 UInfo vidmod.Publisher `json:"publisher,omitempty" bson:"publisher"` //用户信息 FreeArea *bool `json:"freeArea,omitempty"` //免费专区 PublisherID *uint64 `json:"-"` //发布者ID(user) LocationID *primitive.ObjectID `json:"-"` //位置ID IsTopping *bool `json:"isTopping"` IsRecommend *bool `json:"isRecommend"` //力荐 IsChoosen *bool `json:"isChoosen"` //置精 OriginCoins *int64 `json:"originCoins"` // 视频原价 SearchAccessToken *string `json:"searchAccessToken,omitempty"` ShowFreeTrialBadge bool `json:"showFreeTrialBadge" bson:"-"` FreeTrialRemaining uint64 `json:"freeTrialRemaining" bson:"-"` CanUseFreeTrial bool `json:"canUseFreeTrial" bson:"-"` } // VideoMap VideoMap func videoMap(vidList []ObjectID, uid uint64, allowSearchOnly bool) (map[ObjectID]*VideoInfo, error) { if len(vidList) == 0 { return make(map[ObjectID]*VideoInfo), nil } videoMap, vInfo, err := vidmod.SearchVideoMap(vidList) if err != nil { return nil, err } blockedModules := make(map[string]struct{}) if allowSearchOnly { excludedModules, excludeErr := moduleconfmod.ExcludedSearchModuleIDs() if excludeErr != nil { return nil, excludeErr } for _, moduleID := range excludedModules { blockedModules[moduleID] = struct{}{} } } else { moduleIDs := make([]string, 0, len(vInfo)) for _, video := range vInfo { if video.MID != "" { moduleIDs = append(moduleIDs, video.MID) } } blockedModules, err = moduleconfmod.BlockedOutsideSearchModuleIDs(moduleIDs, time.Now()) if err != nil { return nil, err } } var ( vidInfoMap = make(map[ObjectID]*VideoInfo, len(videoMap)) tidUniqueMap = make(map[ObjectID]byte) mFree = map[uint64]bool{} self *usermod.User videoDiscLog videodiscountmod.VideoDiscountLog largestDiscount int wg sync.WaitGroup mUser map[uint64]*vidmod.Publisher ) uids := make([]uint64, len(vInfo)) for i, v := range vInfo { uids[i] = v.PublisherID } wg.Add(5) common.Go(func() { defer wg.Done() self, err = usermod.FindUserByUID(uid) if err != nil { log.Error("VideoMap: usermod.FindUserByUID", log.Any("uid", uid), log.E(err)) return } }) common.Go(func() { defer wg.Done() mUser = vidhelpser.GetUserInfo2Map(uid, uids) }) common.Go(func() { defer wg.Done() largestDiscount, err = productmod.GetLargestDiscountVIPCard() if err != nil { log.Error("VideoMap GetLargestDiscountVIPCard", log.Any("uid", uid), log.E(err)) } }) common.Go(func() { defer wg.Done() mFree = freeVidmod.GetFreeVideoMapByUID(uid) }) common.Go(func() { defer wg.Done() videoDiscLog, err = videodiscountmod.GetByUID(uid) if err != nil { log.Error("VideoMap videodiscountmod.GetByUID", log.Any("uid", uid), log.E(err)) } }) wg.Wait() freeTrialContext := vidhelpser.FreeTrialBadgeContextForUser(uid, self) for vid, vidInfo := range videoMap { videoPtr := vidInfo if _, blocked := blockedModules[videoPtr.MID]; blocked { continue } status := videoPtr.Status if status == 3 { status = 1 } var uInfo vidmod.Publisher if mUser[vidInfo.PublisherID] != nil { uInfo = *mUser[vidInfo.PublisherID] } else { //处理作者被删除的情况 if len(mUser) > 0 { for i := range mUser { uInfo = *mUser[i] break } } } newsType := vidmod.SP if len(videoPtr.NewsType) != 0 { newsType = videoPtr.NewsType } discountCoins := vidhelpser.CalcVideoCoins(videoPtr.Coins, videoPtr.PublisherID, self, mFree, videoDiscLog, largestDiscount, nil) h265URL := vidhelpser.H265URLForApp(videoPtr) vInfo := VideoInfo{ ID: &videoPtr.ID, NewsType: &newsType, Title: &videoPtr.Title, //视频标题 Tags: []TagInfo{}, //视频标签 SourceID: &videoPtr.SourceID, //视频在仓库中的资源ID SourceURL: &videoPtr.SourceURL, //视频资源地址Path H265Url: &h265URL, // H.265 视频资源地址 PlayTime: &videoPtr.PlayTime, //影片长度 Cover: &videoPtr.Cover, //封面大图 CoverThumb: &videoPtr.CoverThumb, //封⾯小图 SeriesCover: videoPtr.SeriesCover, Via: &videoPtr.Via, //来源 自拍,上传 PlayCount: &videoPtr.FakePlayCount, //总的播放量量(假数据) LikeCount: &videoPtr.FakeLikeCount, //点赞数(假数据) CommentCount: &videoPtr.FakeCommentCount, //评论数(假数据) ShareCount: &videoPtr.FakeShareCount, //分享数(假数据) Size: &videoPtr.Size, //文件大小 byte Ratio: &videoPtr.Ratio, //分辨率 Status: &status, //状态,0 未审核 1通过 2审核失败 3视为免费 默认为0 Resolution: &videoPtr.Resolution, //分辨率 MimeType: &videoPtr.MimeType, //视频格式类型 Actor: &videoPtr.Actor, FreeTime: &videoPtr.FreeTime, //免费观影时长 PublisherID: &videoPtr.PublisherID, //发布者ID(user) FreeArea: &videoPtr.FreeArea, //免费专区 LocationID: &videoPtr.Location, //位置ID CreatedAt: &videoPtr.CreatedAt, //创建时间 ReviewedAt: &videoPtr.ReviewAt, UInfo: uInfo, IsTopping: &videoPtr.IsTopping, IsChoosen: &videoPtr.IsChoosen, IsRecommend: &videoPtr.IsRecommend, OriginCoins: &videoPtr.Coins, // 定价 Coins: &discountCoins, // 折扣价 } vInfo.ShowFreeTrialBadge, vInfo.FreeTrialRemaining, vInfo.CanUseFreeTrial = freeTrialContext.Fields( newsType, videoPtr.Coins, videoPtr.FreeArea, videoPtr.PublisherID, ) if allowSearchOnly { token, tokenErr := searchaccessser.Issue(uid, videoPtr.ID.Hex(), time.Now()) if tokenErr != nil { return nil, tokenErr } vInfo.SearchAccessToken = &token } if len(videoPtr.Tags) != 0 { for _, tid := range videoPtr.Tags { tidUniqueMap[tid] = 0 } } //fill video Map vidInfoMap[vid] = &vInfo } tidList := make([]ObjectID, len(tidUniqueMap)) i := 0 for k := range tidUniqueMap { tidList[i] = k i++ } //获取所有tagInfo List tInfoList, err := TagInfoListByTIDList(&uid, tidList) if err != nil { return nil, err } tInfoMap := ToTagMap(tInfoList) //vidInfoMap填充tagInfo for vid, vidInfo := range vidInfoMap { video := videoMap[vid] tidList := video.Tags tInfoList := make([]TagInfo, 0, len(tidList)) for _, t := range video.Tags { tInfo := tInfoMap[t] tInfoList = append(tInfoList, tInfo) } vidInfo.Tags = tInfoList } return vidInfoMap, nil } // VidStatus VidStatus type VidStatus struct { HasPaid *bool `json:"hasPaid,omitempty"` //已支付 } // VidAndStatus VidAndStatus type VidAndStatus struct { VID ObjectID *VidStatus } // VidStatusMap VidStatusMap func VidStatusMap(vidList []ObjectID, uid uint64) map[ObjectID]VidStatus { var ( payUniqList = make([]string, 0, len(vidList)) payUniqMap = make(map[ObjectID]string, len(vidList)) ) for _, v := range vidList { payUniq := payvidlgmod.Unique(uid, v) payUniqList = append(payUniqList, payUniq) payUniqMap[v] = payUniq } payStatueMap, err := payvidlgmod.PayStatueMap(payUniqList) if err != nil { return map[ObjectID]VidStatus{} } vidStatusMap := make(map[ObjectID]VidStatus, len(vidList)) for _, v := range vidList { payHash := payUniqMap[v] payStatue := payStatueMap[payHash] vidStatusMap[v] = VidStatus{ HasPaid: &payStatue, } } return vidStatusMap } // Publisher Publisher type Publisher = UserRes // VideoRes VideoRes type VideoRes struct { VideoInfo `bson:",inline"` //视屏 LocInfo `json:"location" bson:"location"` //位置 VidStatus `json:"vidStatus" bson:"vidStatus"` //用户与视频状态信息 Publisher `json:"publisher" bson:"publisher"` //发布者 } // GetVideoRes GetVideoRes func GetVideoRes(vid ObjectID, vInfoMap map[ObjectID]*VideoInfo, publMap map[uint64]*UserRes, locMap map[ObjectID]*LocInfo, statusMap map[ObjectID]VidStatus) VideoRes { res := VideoRes{} video := vInfoMap[vid] if video == nil { return res } res.VideoInfo = *video if video.PublisherID != nil { publisher := publMap[*video.PublisherID] if publisher != nil { res.Publisher = *publisher } } if video.LocationID != nil { loc := locMap[*video.LocationID] if loc != nil { res.LocInfo = *loc } } status := statusMap[vid] res.VidStatus = status return res } func getVideoParentCommentCountMap(vidList []ObjectID) map[ObjectID]int { res := make(map[ObjectID]int, len(vidList)) if len(vidList) == 0 { return res } countMap, err := cmtmod.CmtFindParentCountByObjIDs(cmtmod.CmtTypeVideo, vidList) if err != nil { log.Error("GetVideoResList CmtFindParentCountByObjIDs error", log.E(err), log.Any("vidList", vidList)) return res } for id, count := range countMap { res[id] = int(count) } return res } // GetVideoResList GetVideoResList func GetVideoResList(uid uint64, vidList []ObjectID) ([]VideoRes, error) { return getVideoResList(uid, vidList, false) } // GetVideoResListForSearch 保留当前仅能通过搜索入口访问的视频。 func GetVideoResListForSearch(uid uint64, vidList []ObjectID) ([]VideoRes, error) { return getVideoResList(uid, vidList, true) } func getVideoResList(uid uint64, vidList []ObjectID, allowSearchOnly bool) ([]VideoRes, error) { videoCount := len(vidList) //vid->video Map videoMap, err := videoMap(vidList, uid, allowSearchOnly) if err != nil { return nil, err } commentCountMap := getVideoParentCommentCountMap(vidList) //Publisher //发布者ID List pidList := make([]uint64, 0, videoCount) for _, video := range videoMap { if video.PublisherID == nil { continue } pidList = append(pidList, *video.PublisherID) } //LocationID List lidList := make([]ObjectID, 0, videoCount) for _, v := range videoMap { if v.LocationID == nil { continue } lidList = append(lidList, *v.LocationID) } var ( wg = sync.WaitGroup{} publMap = make(map[uint64]*UserRes) locMap = make(map[ObjectID]*LocInfo) statusMap = make(map[ObjectID]VidStatus) ) //一共3个map,每个携程填充一个Map wg.Add(3) common.Go(func() { //PublisherID->User map defer wg.Done() m, err := UserResMap(pidList, uid) if err != nil { log.Error("GetVideoResList PublisherID->User error", log.E(err)) return } publMap = m }) //Location map common.Go(func() { //LocationID->Location map defer wg.Done() m, err := LocMap(lidList) if err != nil { log.Error("GetVideoResList Location map error", log.E(err)) return } locMap = m }) //Video Status map common.Go(func() { //Vid->视屏状态 map defer wg.Done() statusMap = VidStatusMap(vidList, uid) }) wg.Wait() //组装 []VidInfo videoResList := make([]VideoRes, 0, videoCount) for _, vid := range vidList { info := GetVideoRes(vid, videoMap, publMap, locMap, statusMap) if info.Publisher.UID == nil { log.Warn("Publisher UID is invalid") continue } if info.VideoInfo.ID != nil { commentCount := commentCountMap[*info.VideoInfo.ID] info.CommentCount = &commentCount } info.Publisher.HasFollowed = &videoMap[vid].UInfo.HasFollowed isFree := true info.FreeArea = &isFree videoResList = append(videoResList, info) } return videoResList, nil } // RichVidRes RichVidRes type RichVidRes struct { VideoInfo `bson:",inline"` //视屏 LocInfo `json:"location" bson:"location"` //位置 VidStatus `json:"vidStatus" bson:"vidStatus"` //用户与视频状态信息 Publisher `json:"publisher,omitempty" bson:"publisher"` //发布者 } // GetRichVideoRes GetRichVideoRes func GetRichVideoRes(vid ObjectID, vInfoMap map[ObjectID]*VideoInfo, publMap map[uint64]*UserRes, locMap map[ObjectID]*LocInfo, statusMap map[ObjectID]VidStatus) RichVidRes { res := GetVideoRes(vid, vInfoMap, publMap, locMap, statusMap) if res.VideoInfo.ID == nil { return RichVidRes{} } richRes := RichVidRes(res) return richRes } // GetRichVideoResList GetRichVideoResList func GetRichVideoResList(uid uint64, vidList []ObjectID) ([]RichVidRes, error) { return getRichVideoResList(uid, vidList, false) } // GetRichVideoResListForSearch 保留当前仅能通过搜索入口访问的视频。 func GetRichVideoResListForSearch(uid uint64, vidList []ObjectID) ([]RichVidRes, error) { return getRichVideoResList(uid, vidList, true) } func getRichVideoResList(uid uint64, vidList []ObjectID, allowSearchOnly bool) ([]RichVidRes, error) { videoCount := len(vidList) //vid->video Map videoMap, err := videoMap(vidList, uid, allowSearchOnly) if err != nil { return nil, err } //Publisher //发布者ID List pidList := make([]uint64, 0, videoCount) for _, video := range videoMap { if video.PublisherID == nil { continue } pidList = append(pidList, *video.PublisherID) } //LocationID List lidList := make([]ObjectID, 0, videoCount) for _, v := range videoMap { if v.LocationID == nil { continue } lidList = append(lidList, *v.LocationID) } var ( wg = sync.WaitGroup{} publMap = make(map[uint64]*UserRes) locMap = make(map[ObjectID]*LocInfo) statusMap = make(map[ObjectID]VidStatus) ) //一共4个map,每个携程填充一个Map wg.Add(3) common.Go(func() { //PublisherID->User map defer wg.Done() m, err := UserResMap(pidList, uid) if err != nil { log.Error("GetVideoResList PublisherID->User error", log.E(err)) return } publMap = m }) //Location map common.Go(func() { //LocationID->Location map defer wg.Done() m, err := LocMap(lidList) if err != nil { log.Error("GetVideoResList Location map error", log.E(err)) return } locMap = m }) //Video Status map common.Go(func() { //Vid->视屏状态 map defer wg.Done() statusMap = VidStatusMap(vidList, uid) }) wg.Wait() //组装 []RichVidRes videoResList := make([]RichVidRes, 0, videoCount) for _, vid := range vidList { info := GetRichVideoRes(vid, videoMap, publMap, locMap, statusMap) if info.Publisher.UID == nil { continue } videoResList = append(videoResList, info) } return videoResList, nil } // VideoListByKeyword 通过关键字匹配title和tagName获取VID List func VideoListByKeyword(keyword string, skip int64, limit int64) ([]vidmod.ObjectID, error) { tags, err := tagmod.GetTagsByKeyword(keyword, 0, 3) if err != nil { return nil, err } tidList := make([]primitive.ObjectID, len(tags)) for i, tag := range tags { tidList[i] = tag.ID } return vidmod.VIDListByKeywordOrTags(keyword, tidList, skip, limit) }