package vidhelpser import ( "91porn-server/app/service/searchaccessser" "91porn-server/models/v/cmtmod" "91porn-server/models/v/moduleconfmod" "math" "sync" "time" "91porn-server/app/appg" "91porn-server/app/service/walletser" "91porn-server/common" "91porn-server/common/constant/redisconst" "91porn-server/common/log" "91porn-server/common/maths" "91porn-server/common/redis" "91porn-server/common/timeutil/timerange" "91porn-server/models/l/payvidlgmod" "91porn-server/models/s/statrecordmod" "91porn-server/models/v/collectmod" "91porn-server/models/v/discount_area_mod" "91porn-server/models/v/followmod" "91porn-server/models/v/freeVidmod" "91porn-server/models/v/likemod" "91porn-server/models/v/locmod" "91porn-server/models/v/tagmod" "91porn-server/models/v/usermod" "91porn-server/models/v/videodiscountmod" "91porn-server/models/v/vidmod" "91porn-server/skd/skdg" "github.com/shopspring/decimal" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) // OID 别名 type OID = primitive.ObjectID const ( //MaxNewestCacheNum 最新帖子最大缓存条数 MaxNewestCacheNum = 500 // 视频点赞数放大倍数 VideoLikeCountMultiplier = 33 // 视频播放量放大倍数 VideoPlayCountMultiplier = 99 ) // GetLocationByIP 通过ip获取地理位置 func GetLocationByIP(ip string) string { r := getRedis() city, err := r.GetString(redisconst.IpLocationFmt(ip)) if city != "" && err == nil { return city } city, _ = common.GetLocationByIP(ip) _ = r.Set(redisconst.IpLocationFmt(ip), city, redisconst.IPLocationExpireMax) return city } func addUserInfo(srcMap map[uint64]*vidmod.Publisher, add []*usermod.BaseInfoVip) map[uint64]*vidmod.Publisher { if srcMap == nil { srcMap = make(map[uint64]*vidmod.Publisher) } for _, info := range add { p := vidmod.Publisher{ UInfo: *info, } srcMap[info.UID] = &p } return srcMap } // GetUserInfo2Map 获取用户基本信息到map func GetUserInfo2Map(uid uint64, uids []uint64) map[uint64]*vidmod.Publisher { m, unExistsUID, err := getUsersBaseInfoFromRedis(uids) if err != nil || len(unExistsUID) != 0 { unExistsUsers, err := getUsersBaseInfoFromMongo(unExistsUID) if err != nil { return nil } common.Go(func() { _ = setUsersBaseInfo2Redis(unExistsUsers) }) m = addUserInfo(m, unExistsUsers) } var wg sync.WaitGroup var followMap map[uint64]bool // var rchgLevelMap map[uint64]usermod.RechargeLevel var userWorks []vidmod.WorkCount wg.Add(2) common.Go(func() { defer wg.Done() followMap, _ = followmod.IsFollowUsers(uid, uids) }) // common.Go(func() { // defer wg.Done() // rchgLevelMap, _ = walletser.GetUidsRchgLevel(uids) // }) common.Go(func() { defer wg.Done() var filter = bson.M{} filter["publisherID"] = bson.M{"$in": uids} filter["status"] = vidmod.CheckPass userWorks, _ = vidmod.TotalWorks(filter) }) wg.Wait() for u, p := range m { p.HasFollowed = followMap[u] // p.RechargeLevel = rchgLevelMap[u].Level for _, w := range userWorks { if p.UID == w.Uid { p.TotalWorks = w.Count } } } return m } // GetUserInfo2MapNoUID 获取用户基本信息到map func GetUserInfo2MapNoUID(uids []uint64) map[uint64]*vidmod.Publisher { m, unExistsUID, err := getUsersBaseInfoFromRedis(uids) if err != nil || len(unExistsUID) != 0 { unExistsUsers, err := getUsersBaseInfoFromMongo(unExistsUID) if err != nil { return nil } common.Go(func() { _ = setUsersBaseInfo2Redis(unExistsUsers) }) m = addUserInfo(m, unExistsUsers) } var rchgLevelMap map[uint64]usermod.RechargeLevel rchgLevelMap, _ = walletser.GetUidsRchgLevel(uids) for u, p := range m { p.RechargeLevel = rchgLevelMap[u].Level } return m } // GetSimpleUserMapNoUID 获取用户基本信息到map func GetSimpleUserMapNoUID(uids []uint64) map[uint64]*vidmod.Publisher { m, unExistsUID, err := getUsersBaseInfoFromRedis(uids) if err != nil || len(unExistsUID) != 0 { unExistsUsers, err := getUsersBaseInfoFromMongo(unExistsUID) if err != nil { return nil } common.Go(func() { _ = setUsersBaseInfo2Redis(unExistsUsers) }) m = addUserInfo(m, unExistsUsers) } return m } // GetUserStatus 获取 用户状态信息 func GetUserStatus(uid uint64, videoIDs []OID) map[OID]vidmod.VideoStatus { mLike := make(map[primitive.ObjectID]bool) mPay := make(map[primitive.ObjectID]bool) mCollect := make(map[primitive.ObjectID]bool) var wg sync.WaitGroup wg.Add(3) common.Go(func() { defer wg.Done() mLike, _ = likemod.IsLikeVideos(uid, videoIDs) }) common.Go(func() { defer wg.Done() mPay, _ = payvidlgmod.IsPay4Videos(uid, videoIDs) }) common.Go(func() { defer wg.Done() mCollect, _ = collectmod.IsCollectVideos(uid, videoIDs) }) wg.Wait() m := make(map[OID]vidmod.VideoStatus) for _, v := range videoIDs { info := vidmod.VideoStatus{ HasLiked: mLike[v], HasPaid: mPay[v], HasCollected: mCollect[v], TodayRank: 0, TodayPlayCnt: 0, } m[v] = info } return m } // GetTagsByIDs 通过tagid列表获取tag详情列表 func GetTagsByIDs(ids []OID) ([]vidmod.TagInfo, bool) { tags, unExistsTid, err := getTagsByIDsFromRedis(ids) if err != nil || len(unExistsTid) != 0 { unExistsTInfo, err := getTagsByIDsFromMongo(unExistsTid) if err != nil { return nil, false } common.Go(func() { _ = setTagsByIDs2Redis(unExistsTInfo) }) tags = append(tags, unExistsTInfo...) } return tags, true } // GetTagsByIDs2Map 通过tagid列表获取tag详情列表 func GetTagsByIDs2Map(ids []OID) map[primitive.ObjectID]vidmod.TagInfo { m := make(map[primitive.ObjectID]vidmod.TagInfo) tags, ok := GetTagsByIDs(ids) if !ok { return m } for _, info := range tags { m[info.ID] = vidmod.TagInfo{ ID: info.ID, Name: info.Name, CoverImg: info.CoverImg, Description: info.Description, } } return m } // GetPartTags 从内存获取tagInfo func GetPartTags(ids []OID, mTags map[primitive.ObjectID]vidmod.TagInfo) []vidmod.TagInfo { infos := make([]vidmod.TagInfo, 0, len(ids)) for _, i := range ids { if !mTags[i].ID.IsZero() { infos = append(infos, mTags[i]) } } return infos } func getVideoParentCommentCountMap(objIDs []primitive.ObjectID) map[primitive.ObjectID]int { res := make(map[primitive.ObjectID]int, len(objIDs)) countMap, err := cmtmod.CmtFindParentCountByObjIDs(cmtmod.CmtTypeVideo, objIDs) if err != nil { return res } for id, cnt := range countMap { res[id] = int(cnt) } return res } // Transfer2Info 将视频数据库模式转为返回模式 func transfer2Info(v *vidmod.VideoModel, uInfo vidmod.Publisher, vStatus vidmod.VideoStatus, loc locmod.Location, ti []vidmod.TagInfo, com vidmod.CommentInfo, originCoins int64) *vidmod.VideoInfo { if v.Status == 3 { v.Status = 1 } newType := vidmod.SP if len(v.NewsType) != 0 { newType = v.NewsType } base := vidmod.VideoBase{ ID: v.ID, NewsType: newType, Title: v.Title, Content: v.Content, Tags: ti, PlayTime: v.PlayTime, Cover: v.Cover, CoverThumb: v.CoverThumb, SeriesCover: v.SeriesCover, PlayCount: (v.PlayCount + v.FakePlayCount) * VideoPlayCountMultiplier, LikeCount: v.LikeCount, // 移除点赞数放大倍数,修复各处点赞数不一致问题 PageViewCount: v.PageViewCount * 3, CommentCount: v.CommentCount, ShareCount: v.FakeShareCount, Coins: v.Coins, Size: v.Size, Resolution: v.Resolution, Ratio: v.Ratio, PurchaseCount: v.PurchaseCount, CreatedAt: v.CreatedAt, ReviewAt: v.ReviewAt, FreeTime: v.FreeTime, Status: v.Status, Reason: v.Reason, SourceURL: v.SourceURL, H265Url: H265URLForApp(v), PreviewURL: v.PreviewURL, LinkUrl: v.LinkUrl, IsHideLocation: v.IsHideLocation, FreeArea: v.FreeArea, IsTopping: v.IsTopping, IsChoosen: v.IsChoosen, IsRecommend: v.IsRecommend, Rewarded: v.Rewarded.Add(v.FakeRewarded), OriginCoins: originCoins, CollectCount: v.CollectCount, SeedLinkUrl: v.SeedLinkUrl, SeedSize: v.SeedSize, SeedPlayTime: v.SeedPlayTime, RichText: v.RichText, PreviewStart: v.PreviewStart, TimeNodeList: v.TimeNodeList, DownloadAllow: v.DownloadAllow, ShowType: v.ShowType, } if v.Via == "laosiji" { base.LsjId = v.SourceID } locInfo := vidmod.LocInfo{ ID: loc.ID, City: loc.City, Cover: loc.Cover, Visit: loc.FakeVisit, CreatedAt: loc.CreatedAt, } info := vidmod.VideoInfo{ VideoBase: base, UInfo: uInfo, Location: locInfo, VidStatus: vStatus, Comment: com, } return &info } // EncodeVideoInfoBlogger 视频上传博主查看自己上传视频(临时解决) func EncodeVideoInfoBlogger(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { infos = filterOutsideSearch(infos) infosLen := len(infos) back := make([]*vidmod.VideoInfoResp, infosLen) if infosLen == 0 { return back } uids := make([]uint64, infosLen) videos := make([]primitive.ObjectID, infosLen) lids := make([]primitive.ObjectID, infosLen) var tags []primitive.ObjectID for i, v := range infos { uids[i] = v.PublisherID videos[i] = v.ID lids[i] = v.Location tags = append(tags, v.Tags...) } var ( mUser map[uint64]*vidmod.Publisher mStatus map[OID]vidmod.VideoStatus mTags map[primitive.ObjectID]vidmod.TagInfo wg sync.WaitGroup ) wg.Add(3) common.Go(func() { defer wg.Done() mUser = GetUserInfo2Map(uid, uids) }) common.Go(func() { defer wg.Done() mStatus = GetUserStatus(uid, videos) }) common.Go(func() { defer wg.Done() mTags = GetTagsByIDs2Map(tags) }) wg.Wait() commentCountMap := getVideoParentCommentCountMap(videos) for i, v := range infos { originCoins := v.Coins var uInfo vidmod.Publisher if mUser[v.PublisherID] != nil { uInfo = *mUser[v.PublisherID] } back[i] = NewTransfer2Info(v, uInfo, mStatus[v.ID], GetPartTags(v.Tags, mTags), originCoins) back[i].CommentCount = commentCountMap[v.ID] if v.Status != vidmod.CheckPass && v.PublisherID == uid { back[i].PageViewCount = 0 } } return back } // EncodeVideoInfo 封装返回的视频列表信息 func EncodeVideoInfo(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfo { return encodeVideoInfo(uid, filterOutsideSearch(infos)) } // EncodeVideoInfoForRecommend 过滤配置为不进入推荐场景的亚模块内容。 func EncodeVideoInfoForRecommend(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfo { return encodeVideoInfo(uid, filterRecommend(infos)) } // EncodeVideoInfoForSearch 保留搜索专属内容,并给结果签发短时效访问凭证。 func EncodeVideoInfoForSearch(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfo { result := encodeVideoInfo(uid, filterSearch(infos)) attachVideoInfoSearchTokens(uid, result) return result } func encodeVideoInfo(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfo { if uid == 0 { return encodeVideoInfoNoUID(infos) } infosLen := len(infos) //now := time.Now() back := make([]*vidmod.VideoInfo, infosLen) if infosLen == 0 { return back } uids := make([]uint64, infosLen) videos := make([]primitive.ObjectID, infosLen) lids := make([]primitive.ObjectID, infosLen) discountAreaIds := []primitive.ObjectID{} var tags []primitive.ObjectID mids := []primitive.ObjectID{} for i, v := range infos { uids[i] = v.PublisherID videos[i] = v.ID lids[i] = v.Location if !v.DiscountAreaId.IsZero() { discountAreaIds = append(discountAreaIds, v.DiscountAreaId) } tags = append(tags, v.Tags...) mid, _ := primitive.ObjectIDFromHex(v.MID) if !mid.IsZero() { mids = append(mids, mid) } } var mUser map[uint64]*vidmod.Publisher var mStatus map[OID]vidmod.VideoStatus var mLocation map[primitive.ObjectID]locmod.Location var mComment map[primitive.ObjectID]vidmod.CommentInfo var mTags map[primitive.ObjectID]vidmod.TagInfo var mFree map[uint64]bool discountAreaMap := make(map[primitive.ObjectID]*discount_area_mod.DiscountArea) var self *usermod.User var videoDiscLog videodiscountmod.VideoDiscountLog var largestDiscount int moduleMap := make(map[string]moduleconfmod.ModuleConf) var wg sync.WaitGroup wg.Add(9) common.Go(func() { defer wg.Done() moduleList, err := moduleconfmod.FindByIDs(mids) if err != nil { log.Error("EncodeVideoInfo: moduleconfmod.FindByIDs fail", log.E(err)) return } for _, v := range moduleList { moduleMap[v.ID.Hex()] = v } }) common.Go(func() { defer wg.Done() // 通过折扣专区id获取专区 vidDiscountArea, err := discount_area_mod.GetDiscountAreaByIds(discountAreaIds) if err != nil { log.Error("EncodeVideoInfo: discount_area_mod.GetDiscountAreaByIds", log.Any("discountAreaIds", discountAreaIds), log.E(err)) return } for _, v := range vidDiscountArea { discountAreaMap[v.ID] = &v } }) common.Go(func() { defer wg.Done() user, findErr := usermod.FindUserByUID(uid) if findErr != nil { log.Error("EncodeVideoInfo: usermod.FindUserByUID", log.Any("uid", uid), log.E(findErr)) return } self = user //if self != nil && !self.IsVIP(now) { // largestDiscount, err = productmod.GetLargestDiscountVIPCard() // if err != nil { // log.Error("GetLargestDiscountVIPCard", log.Any("uid", uid), log.E(err)) // } //} }) common.Go(func() { defer wg.Done() mUser = GetUserInfo2Map(uid, uids) }) common.Go(func() { defer wg.Done() mStatus = GetUserStatus(uid, videos) }) common.Go(func() { defer wg.Done() //mLocation, _ = locmod.GetLocationInfoByIDs(lids) mLocation = make(map[primitive.ObjectID]locmod.Location) }) common.Go(func() { defer wg.Done() mTags = GetTagsByIDs2Map(tags) }) common.Go(func() { defer wg.Done() mFree = freeVidmod.GetFreeVideoMapByUID(uid) }) common.Go(func() { defer wg.Done() discountLog, findErr := videodiscountmod.GetByUID(uid) if findErr != nil { log.Error("videodiscountmod.GetByUID", log.Any("uid", uid), log.E(findErr)) return } videoDiscLog = discountLog }) wg.Wait() freeTrialContext := FreeTrialBadgeContextForUser(uid, self) commentCountMap := getVideoParentCommentCountMap(videos) for i, v := range infos { var uInfo vidmod.Publisher if mUser[v.PublisherID] != nil { uInfo = *mUser[v.PublisherID] } else { //处理作者被删除的情况 if len(mUser) > 0 { for i := range mUser { uInfo = *mUser[i] break } } } // 金币帖子 - 图片帖子对所有人原价 // 非图片帖子再按规则计算折扣价 originCoins := v.Coins infos[i].Coins = originCoins if v.NewsType != vidmod.COVER { infos[i].Coins = CalcVideoCoins(v.Coins, v.PublisherID, self, mFree, videoDiscLog, largestDiscount, discountAreaMap[v.DiscountAreaId]) } info := transfer2Info(v, uInfo, mStatus[v.ID], mLocation[v.Location], GetPartTags(v.Tags, mTags), mComment[v.ID], originCoins) if v.NewsType != vidmod.COVER { // 计算折扣专区价格 info.DiscountAreaPrice = CalcDiscountAreaPrice(originCoins, discountAreaMap[v.DiscountAreaId]) } info.ShowFreeTrialBadge, info.FreeTrialRemaining, info.CanUseFreeTrial = freeTrialContext.Fields( info.NewsType, originCoins, v.FreeArea, v.PublisherID, ) info.HappinessPlazaTop = infos[i].HappinessPlazaTop back[i] = info module, ok := moduleMap[v.MID] if ok { info.VideoTypeId = module.ID.Hex() info.VideoTypeName = module.ModuleName } // 与 comment/list 口径一致:不统计置顶公共评论(该评论不在评论表内)。 back[i].CommentCount = commentCountMap[info.ID] } common.Go(func() { incVideoPageView(videos) }) return back } // NewEncodeVideoInfoNotStatus 封装返回的视频列表信息 func NewEncodeVideoInfoNotStatus(infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { return newEncodeVideoInfoNotStatus(filterOutsideSearch(infos)) } // NewEncodeVideoInfoNotStatusForUser 封装需要用户免费观看状态的无状态视频列表。 func NewEncodeVideoInfoNotStatusForUser(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { result := newEncodeVideoInfoNotStatus(filterOutsideSearch(infos)) ApplyFreeTrialBadgeToVideoInfoResps(LoadFreeTrialBadgeContext(uid), result) return result } // NewEncodeVideoInfoNotStatusForRecommend 过滤配置为不进入推荐场景的亚模块内容。 func NewEncodeVideoInfoNotStatusForRecommend(infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { return newEncodeVideoInfoNotStatus(filterRecommend(infos)) } // NewEncodeVideoInfoNotStatusForSearch 保留搜索专属内容,并给结果签发短时效访问凭证。 func NewEncodeVideoInfoNotStatusForSearch(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { result := newEncodeVideoInfoNotStatus(filterSearch(infos)) ApplyFreeTrialBadgeToVideoInfoResps(LoadFreeTrialBadgeContext(uid), result) attachVideoInfoRespSearchTokens(uid, result) return result } func newEncodeVideoInfoNotStatus(infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { if len(infos) == 0 { return nil } var ( back []*vidmod.VideoInfoResp uids []uint64 videos []primitive.ObjectID lids []primitive.ObjectID tags []primitive.ObjectID mUser = map[uint64]*vidmod.Publisher{} mTags = map[primitive.ObjectID]vidmod.TagInfo{} mStatus = map[OID]vidmod.VideoStatus{} ) for _, i := range infos { uids = append(uids, i.PublisherID) videos = append(videos, i.ID) lids = append(lids, i.Location) tags = append(tags, i.Tags...) } var wg sync.WaitGroup wg.Add(2) common.Go(func() { defer wg.Done() mUser = GetUserInfo2Map(0, uids) }) common.Go(func() { defer wg.Done() mTags = GetTagsByIDs2Map(tags) }) wg.Wait() commentCountMap := getVideoParentCommentCountMap(videos) for i, v := range infos { temp := v originCoins := temp.Coins var uInfo vidmod.Publisher if mUser[temp.PublisherID] != nil { uInfo = *mUser[temp.PublisherID] } else { //处理作者被删除的情况 if len(mUser) > 0 { for i := range mUser { uInfo = *mUser[i] break } } } infos[i].Coins = originCoins info := NewTransfer2Info(temp, uInfo, mStatus[temp.ID], GetPartTags(temp.Tags, mTags), originCoins) info.CommentCount = commentCountMap[temp.ID] back = append(back, info) } common.Go(func() { incVideoPageView(videos) }) return back } // NewEncodeVideoInfo 封装返回的视频列表信息 func NewEncodeVideoInfo(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { return newEncodeVideoInfo(uid, filterOutsideSearch(infos)) } func newEncodeVideoInfo(uid uint64, infos []*vidmod.VideoModel) []*vidmod.VideoInfoResp { var back []*vidmod.VideoInfoResp if uid == 0 { return back } if len(infos) == 0 { return back } var uids []uint64 var videos []primitive.ObjectID var lids []primitive.ObjectID var tags []primitive.ObjectID for _, i := range infos { uids = append(uids, i.PublisherID) videos = append(videos, i.ID) lids = append(lids, i.Location) tags = append(tags, i.Tags...) } var ( mUser map[uint64]*vidmod.Publisher mStatus map[OID]vidmod.VideoStatus mTags map[primitive.ObjectID]vidmod.TagInfo ) var wg sync.WaitGroup wg.Add(3) common.Go(func() { defer wg.Done() mUser = GetUserInfo2Map(uid, uids) }) common.Go(func() { defer wg.Done() mStatus = GetUserStatus(uid, videos) }) common.Go(func() { defer wg.Done() mTags = GetTagsByIDs2Map(tags) }) wg.Wait() freeTrialContext := LoadFreeTrialBadgeContext(uid) commentCountMap := getVideoParentCommentCountMap(videos) for i, v := range infos { temp := v originCoins := temp.Coins var uInfo vidmod.Publisher if mUser[temp.PublisherID] != nil { uInfo = *mUser[temp.PublisherID] } else { //处理作者被删除的情况 if len(mUser) > 0 { for i := range mUser { uInfo = *mUser[i] break } } } infos[i].Coins = originCoins info := NewTransfer2Info(temp, uInfo, mStatus[temp.ID], GetPartTags(v.Tags, mTags), originCoins) info.ShowFreeTrialBadge, info.FreeTrialRemaining, info.CanUseFreeTrial = freeTrialContext.Fields( info.NewsType, originCoins, temp.FreeArea, temp.PublisherID, ) info.CommentCount = commentCountMap[temp.ID] back = append(back, info) } common.Go(func() { incVideoPageView(videos) }) return back } // CalcVideoCoins 计算折扣后视频金币数,判断了包括用户是否为VIP,以及是否购买了视频折扣卡等状态 // 注意:调用此函数时,在用户为VIP的情况下,largestDiscount必须设为0! func CalcVideoCoins(originCoins int64, publisherID uint64, self *usermod.User, mFree map[uint64]bool, videoDiscLog videodiscountmod.VideoDiscountLog, largestDiscount int, discountArea *discount_area_mod.DiscountArea) (discountedCoins int64) { if originCoins == 0 { discountedCoins = 0 return } discountedCoins = math.MaxInt64 now := time.Now() // 如果在折扣专区里,直接按照折扣专区的价格来 if self != nil && self.IsVIP(now) && discountArea != nil { // 计算折扣专区价格 discountedCoins = CalcDiscountAreaPrice(originCoins, discountArea) return } //判断当前视频 是否对uid为免费视频 则将金币置为0 if b, ok := mFree[publisherID]; ok && b { discountedCoins = 0 return } goldVideoFreeLimit := usermod.GetGoldVideoFreeLimit(self) // 预售卡全部金币视频免费 if self != nil && self.AllGoldVideoFree && self.GoldVideoFreeExpire.After(now) { discountedCoins = 0 return } // 用户购买了视频免费卡(视频金币数少于30) if originCoins <= goldVideoFreeLimit && self != nil && self.VideoFreeExpiration != nil && self.VideoFreeExpiration.After(now) { discountedCoins = 0 return } // 用户购买VIP时赠送了视频免费天数 if self != nil && originCoins > 0 && originCoins <= goldVideoFreeLimit && (self.GoldVideoFreeExpire.After(now) || (self.VideoFreeExpiration != nil && self.VideoFreeExpiration.After(now))) { discountedCoins = 0 return } // 用户购买VIP后赠送视频折扣 if originCoins > 0 && self != nil && self.IsVIP(now) && self.PayVidDiscount > 0 { discountedCoins = decimal.NewFromInt(int64(self.PayVidDiscount)).Shift(-1). Mul(decimal.NewFromInt(originCoins)).Round(0).IntPart() } if originCoins > 0 { if videoDiscLog.VideoDiscount > 0 { // 用户购买了视频折扣卡 newDiscountedCoins := decimal.NewFromInt(int64(videoDiscLog.VideoDiscount)).Shift(-1). Mul(decimal.NewFromInt(originCoins)).Round(0).IntPart() if newDiscountedCoins < discountedCoins { discountedCoins = newDiscountedCoins } } else if largestDiscount > 0 { // 用户未购买视频折扣卡,此时按系统中所有VIP卡最大折扣计算 newDiscountedCoins := decimal.NewFromInt(int64(largestDiscount)).Shift(-1). Mul(decimal.NewFromInt(originCoins)).Round(0).IntPart() if newDiscountedCoins < discountedCoins { discountedCoins = newDiscountedCoins } } } if discountedCoins == math.MaxInt64 { discountedCoins = originCoins } return } // CalcDiscountAreaPrice 计算折扣专区的价格 func CalcDiscountAreaPrice(originCoins int64, discountArea *discount_area_mod.DiscountArea) (price int64) { price = originCoins // 判断当前视频是否在vip折扣区 if discountArea != nil { price = decimal.NewFromInt(int64(discountArea.Discount)).Shift(-2). Mul(decimal.NewFromInt(originCoins)).Round(0).IntPart() } return } // EncodeVideoInfoNoUID 封装返回的视频列表信息(无用户) func EncodeVideoInfoNoUID(infos []*vidmod.VideoModel) []*vidmod.VideoInfo { return encodeVideoInfoNoUID(filterOutsideSearch(infos)) } // EncodeVideoInfoNoUIDForRecommend 过滤配置为不进入推荐场景的亚模块内容。 func EncodeVideoInfoNoUIDForRecommend(infos []*vidmod.VideoModel) []*vidmod.VideoInfo { return encodeVideoInfoNoUID(filterRecommend(infos)) } func encodeVideoInfoNoUID(infos []*vidmod.VideoModel) []*vidmod.VideoInfo { infosLen := len(infos) back := make([]*vidmod.VideoInfo, infosLen) if infosLen == 0 { return back } uids := make([]uint64, infosLen) videos := make([]primitive.ObjectID, infosLen) lids := make([]primitive.ObjectID, infosLen) var tags []primitive.ObjectID for i, v := range infos { uids[i] = v.PublisherID videos[i] = v.ID lids[i] = v.Location tags = append(tags, v.Tags...) } var mUser map[uint64]*vidmod.Publisher var mLocation map[primitive.ObjectID]locmod.Location var mComment map[primitive.ObjectID]vidmod.CommentInfo var mTags map[primitive.ObjectID]vidmod.TagInfo var wg sync.WaitGroup wg.Add(3) common.Go(func() { defer wg.Done() mUser = GetUserInfo2MapNoUID(uids) }) common.Go(func() { defer wg.Done() mLocation, _ = locmod.GetLocationInfoByIDs(lids) }) common.Go(func() { defer wg.Done() mTags = GetTagsByIDs2Map(tags) }) wg.Wait() commentCountMap := getVideoParentCommentCountMap(videos) for i, v := range infos { originCoins := v.Coins var uInfo vidmod.Publisher if mUser[v.PublisherID] != nil { uInfo = *mUser[v.PublisherID] } noUserStatus := vidmod.VideoStatus{} info := transfer2Info(v, uInfo, noUserStatus, mLocation[v.Location], GetPartTags(v.Tags, mTags), mComment[v.ID], originCoins) info.CommentCount = commentCountMap[v.ID] back[i] = info } common.Go(func() { incVideoPageView(videos) }) return back } func filterOutsideSearch(infos []*vidmod.VideoModel) []*vidmod.VideoModel { if len(infos) == 0 { return infos } moduleIDs := make([]string, 0, len(infos)) for _, video := range infos { if video != nil && video.MID != "" { moduleIDs = append(moduleIDs, video.MID) } } blocked, err := moduleconfmod.BlockedOutsideSearchModuleIDs(moduleIDs, time.Now()) if err != nil { log.Error("filterOutsideSearch module query failed", log.E(err)) return []*vidmod.VideoModel{} } if len(blocked) == 0 { return infos } filtered := make([]*vidmod.VideoModel, 0, len(infos)) for _, video := range infos { if video == nil { continue } if _, exists := blocked[video.MID]; exists { continue } filtered = append(filtered, video) } return filtered } func filterRecommend(infos []*vidmod.VideoModel) []*vidmod.VideoModel { if len(infos) == 0 { return infos } excluded, err := moduleconfmod.ExcludedVideoModuleIDs(time.Now(), true) if err != nil { log.Error("filterRecommend module query failed", log.E(err)) return []*vidmod.VideoModel{} } blocked := make(map[string]struct{}, len(excluded)) for _, moduleID := range excluded { blocked[moduleID] = struct{}{} } if len(blocked) == 0 { return infos } filtered := make([]*vidmod.VideoModel, 0, len(infos)) for _, video := range infos { if video == nil { continue } if _, exists := blocked[video.MID]; exists { continue } filtered = append(filtered, video) } return filtered } func filterSearch(infos []*vidmod.VideoModel) []*vidmod.VideoModel { if len(infos) == 0 { return infos } excluded, err := moduleconfmod.ExcludedSearchModuleIDs() if err != nil { log.Error("filterSearch module query failed", log.E(err)) return []*vidmod.VideoModel{} } blocked := make(map[string]struct{}, len(excluded)) for _, moduleID := range excluded { blocked[moduleID] = struct{}{} } if len(blocked) == 0 { return infos } filtered := make([]*vidmod.VideoModel, 0, len(infos)) for _, video := range infos { if video == nil { continue } if _, exists := blocked[video.MID]; exists { continue } filtered = append(filtered, video) } return filtered } func attachVideoInfoSearchTokens(uid uint64, videos []*vidmod.VideoInfo) { now := time.Now() for _, video := range videos { if video == nil { continue } token, err := searchaccessser.Issue(uid, video.ID.Hex(), now) if err != nil { log.Error("issue video search access token failed", log.Any("videoID", video.ID), log.E(err)) continue } video.SearchAccessToken = token } } func attachVideoInfoRespSearchTokens(uid uint64, videos []*vidmod.VideoInfoResp) { now := time.Now() for _, video := range videos { if video == nil { continue } token, err := searchaccessser.Issue(uid, video.ID.Hex(), now) if err != nil { log.Error("issue video search access token failed", log.Any("videoID", video.ID), log.E(err)) continue } video.SearchAccessToken = token } } func incVideoPageView(vids []primitive.ObjectID) { now := time.Now() recentMinute := timerange.RecentMinute(now, statrecordmod.FiveMinuteScale) key := redisconst.VidPageViewListKey(recentMinute.Unix()) vidStrs := make([]string, len(vids)) for i := range vids { vidStrs[i] = vids[i].Hex() } if err := appg.Redis.RPush(key, vidStrs); err != nil { return } _, _ = appg.Redis.ExpireKey(key, time.Minute*20) } func incTagCount(v tagmod.Tag, isValidPlay bool) error { // 增加总播放量 tPlayCount := v.TPlayCount + 1 // 增加有效播放量量&更新播放率 vPlayCount := v.VPlayCount var playRating float64 if isValidPlay { vPlayCount++ playRating = maths.Decimal6Bit(float64(vPlayCount) / float64(tPlayCount)) } else { playRating = maths.Decimal6Bit(float64(vPlayCount) / float64(tPlayCount)) } doc := tagmod.TagUpdateDoc{ VPlayCount: &vPlayCount, TPlayCount: &tPlayCount, PlayRating: &playRating, UpdatedAt: time.Now(), } if err := tagmod.UpdateOneTagByID(v.ID, doc); err != nil { log.Error("incTagCount error", log.Any("tid", v.ID), log.Any("isValidPlay", isValidPlay), log.E(err)) return err } return nil } // IncreaseTag 播放视频对标签计数 func IncreaseTag(vidID string, tid primitive.ObjectID, isValidPlay bool) error { if !tid.IsZero() { tag, err := tagmod.FindOneTagByID(tid) if err != nil { return err } _ = incTagCount(tag, isValidPlay) return err } tags, err := vidmod.GetVideoTag(vidID) if err != nil || len(tags) <= 0 { log.Error("IncreaseTag error", log.Any("vidID", vidID), log.Any("isValidPlay", isValidPlay), log.E(err)) return err } tagList, err := tagmod.FindTagsByIDS(tags) if err != nil { return err } for _, v := range tagList { _ = incTagCount(v, isValidPlay) } return nil } // GetVideosByIDs 通过id获取视频列表信息 func GetVideosByIDs(uid uint64, ids []primitive.ObjectID) []*vidmod.VideoInfo { if len(ids) == 0 { log.Warn("GetVideosByIDs error", log.Any("uid", uid), log.Any("ids", ids)) return nil } vInfo, unExistsVid, err := getVideoListByIDsFromRedis(ids) if err != nil || len(unExistsVid) != 0 { unExistsVInfo, err := getVideoListByIDsFromMongo(unExistsVid) if err != nil { log.Error("GetVideosByIDs getVideoListByIDsFromMongo", log.Any("udi", uid), log.Any("ids", ids), log.E(err)) return nil } common.Go(func() { if cacheErr := setVideoListByIDs2Redis(unExistsVInfo); cacheErr != nil { log.Error("GetVideosByIDs setVideoListByIDs2Redis", log.Any("unExistsVid", unExistsVid), log.Any("uid", uid), log.E(cacheErr)) } }) vInfo = append(vInfo, unExistsVInfo...) } if uid == 0 { // 用户未登录 return EncodeVideoInfoNoUID(vInfo) } return EncodeVideoInfo(uid, vInfo) } // GetVideosByIDsNoCache 通过id直接从MongoDB获取视频列表信息,不读写Redis缓存。 // 按传入ID顺序返回,保证收藏列表维持收藏时间倒序。 func GetVideosByIDsNoCache(uid uint64, ids []primitive.ObjectID) []*vidmod.VideoInfo { if len(ids) == 0 { return nil } vInfo, err := getVideoListByIDsFromMongo(ids) if err != nil { log.Error("GetVideosByIDsNoCache getVideoListByIDsFromMongo", log.Any("uid", uid), log.Any("ids", ids), log.E(err)) return nil } vInfo = orderVideoModelsByIDs(ids, vInfo) if uid == 0 { return EncodeVideoInfoNoUID(vInfo) } return EncodeVideoInfo(uid, vInfo) } func orderVideoModelsByIDs(ids []primitive.ObjectID, videos []*vidmod.VideoModel) []*vidmod.VideoModel { videoByID := make(map[primitive.ObjectID]*vidmod.VideoModel, len(videos)) for _, video := range videos { if video != nil { videoByID[video.ID] = video } } ordered := make([]*vidmod.VideoModel, 0, len(videos)) for _, id := range ids { if video, ok := videoByID[id]; ok { ordered = append(ordered, video) } } return ordered } // NewGetVideosByIDs 通过id获取视频列表信息 func NewGetVideosByIDs(uid uint64, ids []primitive.ObjectID) []*vidmod.VideoInfoResp { if len(ids) == 0 { log.Warn("GetVideosByIDs error", log.Any("uid", uid), log.Any("ids", ids)) return nil } vInfo, unExistsVid, err := getVideoListByIDsFromRedis(ids) if err != nil || len(unExistsVid) != 0 { unExistsVInfo, err := getVideoListByIDsFromMongo(unExistsVid) if err != nil { log.Error("GetVideosByIDs getVideoListByIDsFromMongo", log.Any("udi", uid), log.Any("ids", ids), log.E(err)) return nil } common.Go(func() { if cacheErr := setVideoListByIDs2Redis(unExistsVInfo); cacheErr != nil { log.Error("GetVideosByIDs setVideoListByIDs2Redis", log.Any("unExistsVid", unExistsVid), log.Any("uid", uid), log.E(cacheErr)) } }) vInfo = append(vInfo, unExistsVInfo...) // 将redis中获取不到的写入切片中后得按照原来id顺序的重新排序 videoMap := make(map[primitive.ObjectID]*vidmod.VideoModel) for _, v := range vInfo { videoMap[v.ID] = v } videoList := []*vidmod.VideoModel{} for _, id := range ids { video, ok := videoMap[id] if !ok { continue } videoList = append(videoList, video) } vInfo = videoList } //if uid == 0 { // 用户未登录 // return EncodeVideoInfoNoUID(vInfo) //} return NewEncodeVideoInfo(uid, vInfo) } func getVideoModelByIDs(ids []primitive.ObjectID) []*vidmod.VideoModel { if len(ids) == 0 { log.Warn("getVideoModelByIDs error", log.Any("ids", ids)) return nil } vInfo, unExistsVid, err := getVideoListByIDsFromRedis(ids) if err != nil || len(unExistsVid) != 0 { unExistsVInfo, err := getVideoListByIDsFromMongoBaseNoStatus(unExistsVid) if err != nil { return nil } common.Go(func() { _ = setVideoListByIDs2Redis(unExistsVInfo) }) vInfo = append(vInfo, unExistsVInfo...) } return vInfo } // GetVideoListByIDsNoStatus 通过id获取视频列表信息 底层不判断视频状态, 由上次业务保证ID的正确性 func GetVideoListByIDsNoStatus(uid uint64, ids []primitive.ObjectID) []*vidmod.VideoInfoResp { vInfo := getVideoModelByIDs(ids) return NewEncodeVideoInfo(uid, vInfo) } func getRedis() *redis.Client { if appg.Redis != nil { return appg.Redis } if skdg.Redis != nil { return skdg.Redis } return nil } func setCache(recentMinute time.Time) { common.Go(func() { ids := getNewsTop200FromMongo(recentMinute) if len(ids) != 0 { setNewestNews2Redis(ids, recentMinute) } }) } func setCache_old() { common.Go(func() { ids := getNewsTop200FromMongo_old() if len(ids) != 0 { setNewestNews2Redis_old(ids) } }) } func setShortVideoCache() { common.Go(func() { ids := getNewsShortVideoTop200FromMongo() if len(ids) != 0 { setNewestShortVideo2Redis(ids) } }) } // GetNewestNews 获取最新帖子 // 最新两百条存redis,其他数据库查找 func GetNewestNews(page, size uint64, reqTime time.Time) ([]*vidmod.VideoModel, bool, error) { recentMinute := timerange.RecentMinute(reqTime, statrecordmod.FiveMinuteScale) endpoint := page * size if endpoint > MaxNewestCacheNum { //大于200条的数据库获取 return getNewestNewsFromMongo(page, size, recentMinute) } ids, err := getNewestNewsFromRedis(page, size, recentMinute) if err != nil || uint64(len(ids)) < size { //缓存获取失败,数据库获取 infos, hasNext, err := getNewestNewsFromMongo(page, size, recentMinute) setCache(recentMinute) return infos, hasNext, err } oids := common.String2ObjectID(ids) infos := getVideoModelByIDs(oids) if len(infos) == 0 { log.Warn("info length error", log.Any("page", page), log.Any("size", size), log.Any("reqTime", reqTime), log.Any("recentMinute", recentMinute), log.Any("ids", ids)) } return infos, true, nil } // GetNewestNews 获取最新帖子 // 最新两百条存redis,其他数据库查找 func GetNewestNews_old(page, size uint64) ([]*vidmod.VideoModel, bool, error) { if endpoint := page * size; endpoint > MaxNewestCacheNum { //大于200条的数据库获取 return getNewestNewsFromMongo_old(page, size) } ids, err := getNewestNewsFromRedis_old(page, size) if err != nil || uint64(len(ids)) < size { //缓存获取失败,数据库获取 infos, hasNext, err := getNewestNewsFromMongo_old(page, size) setCache_old() return infos, hasNext, err } oids := common.String2ObjectID(ids) infos := getVideoModelByIDs(oids) if len(infos) == 0 { log.Warn("info length error_old", log.Any("page", page), log.Any("size", size), log.Any("ids", ids)) } return infos, true, nil } func GetNewestShortVideo(page, size uint64) ([]*vidmod.VideoModel, bool, error) { if endpoint := page * size; endpoint > MaxNewestCacheNum { //大于200条的数据库获取 return getNewestShortVideoFromMongo(page, size) } ids, err := getNewestShortVideoFromRedis(page, size) if err != nil || uint64(len(ids)) < size { //缓存获取失败,数据库获取 infos, hasNext, err := getNewestShortVideoFromMongo(page, size) setShortVideoCache() return infos, hasNext, err } oids := common.String2ObjectID(ids) infos := getVideoModelByIDs(oids) if len(infos) == 0 { log.Warn("info length error_old", log.Any("page", page), log.Any("size", size), log.Any("ids", ids)) } return infos, true, nil } // IncCommentCount 评论统计次数加加 func IncCommentCount(id primitive.ObjectID) error { if err := vidmod.IncCommentCount(id); err != nil { return err } incComment(id) return nil } // 修正用户喜欢的视频记录 func FixLikeVideoRecord(uid uint64, oids []primitive.ObjectID) { back, err := vidmod.GetVideoListByIDsUnPublish(oids) if err != nil || len(back) == 0 { return } vids := make([]primitive.ObjectID, len(back)) for i, v := range back { vids[i] = v.ID } } // NewTransfer2Info 将视频数据库模式转为返回模式 func NewTransfer2Info(v *vidmod.VideoModel, uInfo vidmod.Publisher, vStatus vidmod.VideoStatus, tags []vidmod.TagInfo, originCoins int64) *vidmod.VideoInfoResp { if v.Status == 3 { v.Status = 1 } newType := vidmod.SP if len(v.NewsType) != 0 { newType = v.NewsType } base := vidmod.VideoInfoResp{ ID: v.ID, NewsType: newType, Title: v.Title, PlayTime: v.PlayTime, Cover: v.Cover, SeriesCover: v.SeriesCover, PlayCount: (v.PlayCount + v.FakePlayCount) * VideoPlayCountMultiplier, LikeCount: v.LikeCount, // 移除like数量加倍,修复各处like数不一致问题 PageViewCount: v.PageViewCount * 3, CommentCount: v.CommentCount, Coins: v.Coins, Size: v.Size, Resolution: v.Resolution, Ratio: v.Ratio, CreatedAt: v.CreatedAt, ReviewAt: v.ReviewAt, FreeTime: v.FreeTime, Status: v.Status, Reason: v.Reason, SourceURL: v.SourceURL, H265Url: H265URLForApp(v), LinkUrl: v.LinkUrl, //IsTopping: v.IsTopping, IsChoosen: v.IsChoosen, IsRecommend: v.IsRecommend, Rewarded: v.Rewarded.Add(v.FakeRewarded), OriginCoins: originCoins, Chosen: v.Chosen, DownloadAllow: v.DownloadAllow, ShowType: v.ShowType, FreeArea: v.FreeArea, Tags: tags, } if v.LiaoBaTopSort > 0 { base.IsTopping = true } var vipLevel int if uInfo.VipExpireDate.After(time.Now()) { vipLevel = uInfo.VipLevel } u := vidmod.AppPublisherResp{ UID: uInfo.UID, Name: uInfo.Name, Portrait: uInfo.Portrait, HasFollowed: uInfo.HasFollowed, VipLevel: vipLevel, VipName: uInfo.VipName, } base.UInfo = u base.VidStatus = vStatus info := base return &info } func ToVideoBaseInfo(v *vidmod.VideoModel, ti []vidmod.TagInfo, originCoins int64) *vidmod.VideoBase { if v.Status == 3 { v.Status = 1 } newType := vidmod.SP if len(v.NewsType) != 0 { newType = v.NewsType } base := vidmod.VideoBase{ ID: v.ID, NewsType: newType, Title: v.Title, Content: v.Content, Tags: ti, SourceURL: v.SourceURL, H265Url: H265URLForApp(v), PreviewURL: v.PreviewURL, LinkUrl: v.LinkUrl, PlayTime: v.PlayTime, Cover: v.Cover, CoverThumb: v.CoverThumb, SeriesCover: v.SeriesCover, LikeCount: v.LikeCount, // 移除like数量加倍,修复各处like数不一致问题 PlayCount: (v.PlayCount + v.FakePlayCount) * VideoPlayCountMultiplier, PurchaseCount: v.PurchaseCount, CommentCount: v.CommentCount, ShareCount: v.FakeShareCount, Coins: v.Coins, Size: v.Size, Resolution: v.Resolution, Ratio: v.Ratio, Status: v.Status, Reason: v.Reason, FreeTime: v.FreeTime, IsHideLocation: v.IsHideLocation, FreeArea: v.FreeArea, CreatedAt: v.CreatedAt, ReviewAt: v.ReviewAt, IsTopping: v.IsTopping, IsRecommend: v.IsRecommend, IsChoosen: v.IsChoosen, Rewarded: v.Rewarded.Add(v.FakeRewarded), OriginCoins: originCoins, CollectCount: v.CollectCount, PageViewCount: v.PageViewCount * 3, SeedLinkUrl: v.SeedLinkUrl, SeedSize: v.SeedSize, SeedPlayTime: v.SeedPlayTime, PreviewStart: v.PreviewStart, RichText: v.RichText, TimeNodeList: v.TimeNodeList, DownloadAllow: v.DownloadAllow, ShowType: v.ShowType, } return &base }