Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

1419 lines
41 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}