993 lines
30 KiB
Go
993 lines
30 KiB
Go
package srv_videoinfo
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"net/http"
|
|
"time"
|
|
"videoweb/pub/common/godef"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/common/playletcache"
|
|
"videoweb/pub/common/slugx"
|
|
"videoweb/pub/common/srvdef"
|
|
"videoweb/pub/common/utils"
|
|
"videoweb/pub/models/actress"
|
|
"videoweb/pub/models/contenttagmod"
|
|
"videoweb/pub/models/videocategorymod"
|
|
"videoweb/pub/models/videocollectionmediamod"
|
|
"videoweb/pub/models/videocreatermod"
|
|
"videoweb/pub/models/videoinformationmod"
|
|
"videoweb/pub/mongos"
|
|
"videoweb/pub/pubdef"
|
|
"videoweb/pub/storage"
|
|
"videoweb/pub/storage/proxy"
|
|
"videoweb/web/service/srv_contenttag"
|
|
"videoweb/web/service/srv_report"
|
|
"videoweb/web/service/srv_seopush"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
// GetVideoInfos 按照参数搜索APP
|
|
func GetVideoInfos(ctx context.Context, param *VideoInfoReq) (*VideoInfoResp, *srvdef.SrvErr) {
|
|
filter := bson.M{}
|
|
if param.MediaType != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.MediaType] = *param.MediaType
|
|
}
|
|
if param.Id != nil && *param.Id > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Id] = *param.Id
|
|
}
|
|
if param.Title != nil && len(*param.Title) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Title] = bson.M{"$regex": *param.Title}
|
|
}
|
|
if param.Bango != nil && *param.Bango != "" {
|
|
filter[videoinformationmod.FN.VideoInformation.Bango] = bson.M{"$regex": *param.Bango}
|
|
}
|
|
if param.CreatorId != nil && *param.CreatorId > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.CreatorId] = *param.CreatorId
|
|
}
|
|
if param.Status != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.Status] = *param.Status
|
|
}
|
|
if param.Tag != nil && *param.Tag > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Tags] = bson.M{"$in": []int64{*param.Tag}}
|
|
}
|
|
if param.IsHot != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.IsHot] = *param.IsHot
|
|
}
|
|
if param.IsRecommend != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.IsRecommend] = *param.IsRecommend
|
|
}
|
|
if param.CreatorName != nil && len(*param.CreatorName) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.CreatorName] = bson.M{"$regex": *param.CreatorName}
|
|
}
|
|
if param.Keyworld != nil && len(*param.Keyworld) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Keyworld] = bson.M{"$in": []string{*param.Keyworld}}
|
|
}
|
|
if param.VideoTaskId != nil && len(*param.VideoTaskId) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Video.TaskId] = *param.VideoTaskId
|
|
}
|
|
if param.VideoFileId != nil && len(*param.VideoFileId) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.Video.FileId] = *param.VideoFileId
|
|
}
|
|
if param.CategoryId != nil && *param.CategoryId > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.CategoryId] = *param.CategoryId
|
|
}
|
|
if param.IsTop != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.IsTop] = *param.IsTop
|
|
}
|
|
if param.ActressIds != nil && len(*param.ActressIds) > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.ActressIds] = bson.M{"$in": *param.ActressIds}
|
|
}
|
|
if param.StartTime != nil && param.EndTime != nil {
|
|
start := utils.GetDayBeginByTime(*param.StartTime)
|
|
end := utils.GetDayEndByTime(*param.EndTime)
|
|
if end.Unix() > start.Unix() {
|
|
filter[videoinformationmod.FN.VideoInformation.CreatedAt] = bson.M{
|
|
"$gte": start, "$lte": end,
|
|
}
|
|
}
|
|
}
|
|
if param.OptionAccount != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.OptionAccount] = *param.OptionAccount
|
|
}
|
|
opt := &options.FindOptions{}
|
|
resp := &VideoInfoResp{
|
|
Total: 0,
|
|
List: make(videoinformationmod.VideoInformationList, 0),
|
|
}
|
|
|
|
opt.SetSort(bson.M{videoinformationmod.FN.VideoInformation.Id: -1})
|
|
switch param.Sort {
|
|
case 0:
|
|
opt.SetSort(bson.M{videoinformationmod.FN.VideoInformation.CreatedAt: -1})
|
|
case 1:
|
|
opt.SetSort(bson.M{videoinformationmod.FN.VideoInformation.AddedTime: -1})
|
|
case 2:
|
|
opt.SetSort(bson.M{videoinformationmod.FN.VideoInformation.UpdatedAt: -1})
|
|
case 3:
|
|
opt.SetSort(bson.D{
|
|
{videoinformationmod.FN.VideoInformation.Sort, -1},
|
|
{videoinformationmod.FN.VideoInformation.AddedTime, -1},
|
|
})
|
|
case 4:
|
|
opt.SetSort(bson.D{
|
|
{videoinformationmod.FN.VideoInformation.Real.WatchTimes, -1},
|
|
{videoinformationmod.FN.VideoInformation.AddedTime, -1},
|
|
})
|
|
}
|
|
opt.SetSkip((param.PageNum - 1) * param.PageSize).SetLimit(param.PageSize)
|
|
|
|
total, serr := videoinformationmod.CountVideoInformations(ctx, filter)
|
|
if serr != nil {
|
|
return resp, serr
|
|
}
|
|
resp.Total = total
|
|
if total == 0 {
|
|
return resp, nil
|
|
}
|
|
var ads videoinformationmod.VideoInformationList
|
|
if param.Sort == 3 && param.CategoryId != nil && *param.CategoryId > 0 {
|
|
ads, serr = videoinformationmod.FindManyVideoByCategoryRank(ctx, filter, *param.CategoryId, (param.PageNum-1)*param.PageSize, param.PageSize, nil)
|
|
} else {
|
|
ads, serr = videoinformationmod.FindManyVideoInformations(ctx, filter, opt)
|
|
}
|
|
if serr != nil {
|
|
return resp, serr
|
|
}
|
|
resp.List = ads
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// SetVideoRecommend 单/批量设置视频是否推荐
|
|
func SetVideoRecommend(ctx context.Context, req *SetRecommendReq) *srvdef.SrvErr {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "ids不能为空")
|
|
}
|
|
ids, err := normalizeFakeFlowIds(req.Ids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = videoinformationmod.UpdateManyVideoInformation(ctx, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": ids},
|
|
}, bson.M{
|
|
"$set": bson.M{
|
|
videoinformationmod.FN.VideoInformation.IsRecommend: req.IsRecommend,
|
|
videoinformationmod.FN.VideoInformation.UpdatedAt: time.Now(),
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
return nil
|
|
}
|
|
|
|
// SetVideoFakeLikes 单/批量随机设置视频假点赞数
|
|
func SetVideoFakeLikes(ctx context.Context, req *SetFakeLikesReq) (*SetFakeCountResp, *srvdef.SrvErr) {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "ids不能为空")
|
|
}
|
|
if req.LikesMin < 0 || req.LikesMax < req.LikesMin {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "点赞数区间无效")
|
|
}
|
|
ids, serr := normalizeFakeFlowIds(req.Ids)
|
|
if serr != nil {
|
|
return nil, serr
|
|
}
|
|
return setVideoFakeField(ctx, ids, videoinformationmod.FN.VideoInformation.Fake.Likes, req.LikesMin, req.LikesMax)
|
|
}
|
|
|
|
// SetVideoFakeWatchTimes 单/批量随机设置视频假观看次数
|
|
func SetVideoFakeWatchTimes(ctx context.Context, req *SetFakeWatchTimesReq) (*SetFakeCountResp, *srvdef.SrvErr) {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "ids不能为空")
|
|
}
|
|
if req.WatchTimesMin < 0 || req.WatchTimesMax < req.WatchTimesMin {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "观看次数区间无效")
|
|
}
|
|
ids, err := normalizeFakeFlowIds(req.Ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return setVideoFakeField(ctx, ids, videoinformationmod.FN.VideoInformation.Fake.WatchTimes, req.WatchTimesMin, req.WatchTimesMax)
|
|
}
|
|
|
|
// SetVideoHeat 单/批量随机设置视频热度
|
|
func SetVideoHeat(ctx context.Context, req *SetVideoHeatReq) (*SetFakeCountResp, *srvdef.SrvErr) {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "ids不能为空")
|
|
}
|
|
if req.HeatMin < 0 || req.HeatMax < req.HeatMin {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "热度区间无效")
|
|
}
|
|
ids, err := normalizeFakeFlowIds(req.Ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return setVideoFakeField(ctx, ids, videoinformationmod.FN.VideoInformation.HeatValue, req.HeatMin, req.HeatMax)
|
|
}
|
|
|
|
func setVideoFakeField(ctx context.Context, ids []int64, field string, min, max int64) (*SetFakeCountResp, *srvdef.SrvErr) {
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
docs := make([]mongo.WriteModel, 0, len(ids))
|
|
for _, id := range ids {
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{videoinformationmod.FN.VideoInformation.Id: id}).
|
|
SetUpdate(bson.M{"$set": bson.M{field: randomFakeFlowValue(r, min, max)}}))
|
|
}
|
|
result, err := videoinformationmod.BulkWriteVideoInformation(ctx, docs, options.BulkWrite().SetOrdered(false))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result.MatchedCount == 0 {
|
|
return nil, srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
return &SetFakeCountResp{MatchedCount: result.MatchedCount, ModifiedCount: result.ModifiedCount}, nil
|
|
}
|
|
|
|
func normalizeFakeFlowIds(rawIds []int64) ([]int64, *srvdef.SrvErr) {
|
|
ids := make([]int64, 0, len(rawIds))
|
|
seen := make(map[int64]struct{}, len(rawIds))
|
|
for _, id := range rawIds {
|
|
if id <= 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "ids包含无效id")
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
ids = append(ids, id)
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "ids不能为空")
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
const (
|
|
defaultFakeFlowMin = int64(1000)
|
|
defaultFakeFlowMax = int64(10000)
|
|
)
|
|
|
|
func randomDefaultFakeFlowPair(r *rand.Rand) (watchTimes, likes int64) {
|
|
if r == nil {
|
|
r = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
}
|
|
likes = randomFakeFlowValue(r, defaultFakeFlowMin, defaultFakeFlowMax-1)
|
|
watchTimes = randomFakeFlowValue(r, likes+1, defaultFakeFlowMax)
|
|
return watchTimes, likes
|
|
}
|
|
|
|
func applyDefaultFakeFlow(fake *pubdef.FakeFlow, r *rand.Rand) {
|
|
if fake == nil {
|
|
return
|
|
}
|
|
fake.WatchTimes, fake.Likes = randomDefaultFakeFlowPair(r)
|
|
}
|
|
|
|
func defaultFakeFlowSetDoc(r *rand.Rand) bson.M {
|
|
watchTimes, likes := randomDefaultFakeFlowPair(r)
|
|
return bson.M{
|
|
videoinformationmod.FN.VideoInformation.Fake.WatchTimes: watchTimes,
|
|
videoinformationmod.FN.VideoInformation.Fake.Likes: likes,
|
|
}
|
|
}
|
|
|
|
const fillZeroFakeFlowBatchSize = 500
|
|
|
|
func zeroFakeFlowFilter() bson.M {
|
|
return bson.M{
|
|
"$or": []bson.M{
|
|
{
|
|
"$expr": bson.M{
|
|
"$lt": []interface{}{
|
|
bson.M{"$add": []interface{}{"$real.watchTimes", "$fake.watchTimes"}},
|
|
defaultFakeFlowMin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"$expr": bson.M{
|
|
"$lt": []interface{}{
|
|
bson.M{"$add": []interface{}{"$real.likes", "$fake.likes"}},
|
|
defaultFakeFlowMin,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// FillZeroVideoFakeFlow 为播放量或收藏总量小于 1000 的视频随机设置 fake 播放量和收藏数(1000-10000,播放量大于收藏量)。
|
|
func FillZeroVideoFakeFlow(ctx context.Context) (*SetFakeCountResp, *srvdef.SrvErr) {
|
|
filter := zeroFakeFlowFilter()
|
|
opt := options.Find().
|
|
SetProjection(bson.M{videoinformationmod.FN.VideoInformation.Id: 1}).
|
|
SetBatchSize(fillZeroFakeFlowBatchSize).
|
|
SetLimit(fillZeroFakeFlowBatchSize)
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
resp := &SetFakeCountResp{}
|
|
var lastID int64
|
|
|
|
for {
|
|
pageFilter := filter
|
|
if lastID > 0 {
|
|
pageFilter = bson.M{
|
|
"$and": []bson.M{
|
|
filter,
|
|
{videoinformationmod.FN.VideoInformation.Id: bson.M{"$gt": lastID}},
|
|
},
|
|
}
|
|
}
|
|
findOpt := opt.SetSort(bson.M{videoinformationmod.FN.VideoInformation.Id: 1})
|
|
list, serr := videoinformationmod.FindManyVideoInformations(ctx, pageFilter, findOpt)
|
|
if serr != nil {
|
|
return nil, serr
|
|
}
|
|
if len(list) == 0 {
|
|
break
|
|
}
|
|
|
|
docs := make([]mongo.WriteModel, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil || item.Id <= 0 {
|
|
continue
|
|
}
|
|
lastID = item.Id
|
|
resp.MatchedCount++
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{videoinformationmod.FN.VideoInformation.Id: item.Id}).
|
|
SetUpdate(bson.M{"$set": defaultFakeFlowSetDoc(r)}))
|
|
}
|
|
if len(docs) == 0 {
|
|
break
|
|
}
|
|
result, err := videoinformationmod.BulkWriteVideoInformation(ctx, docs, options.BulkWrite().SetOrdered(false))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp.ModifiedCount += result.ModifiedCount
|
|
if len(list) < fillZeroFakeFlowBatchSize {
|
|
break
|
|
}
|
|
}
|
|
if resp.ModifiedCount > 0 {
|
|
playletcache.ClearHomeCache()
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func randomFakeFlowValue(r *rand.Rand, min, max int64) int64 {
|
|
if min == max {
|
|
return min
|
|
}
|
|
return min + r.Int63n(max-min+1)
|
|
}
|
|
|
|
// SyncVideoInfo 同步视频
|
|
func SyncVideoInfo(ctx context.Context,
|
|
req *AddVideoReq) *srvdef.SrvErr {
|
|
|
|
ids := make([]string, 0)
|
|
for _, id := range req.FileIds {
|
|
ids = append(ids, id)
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
|
|
queryMediaReq := &proxy.MediaQueryesReq{
|
|
Ids: ids,
|
|
}
|
|
resp, err := storage.QueryMedias(ctx, queryMediaReq, "")
|
|
if err != nil || resp.Code != http.StatusOK {
|
|
if resp != nil {
|
|
log.Warn("AddVideoInfo.QueryMedias failed", log.Any("filter", req.FileIds), log.E(err), log.Any("respCode", resp.Code))
|
|
} else {
|
|
log.Warn("AddVideoInfo.QueryMedias failed", log.Any("filter", req.FileIds), log.E(err))
|
|
}
|
|
return srvdef.New(srvdef.ErrMediaNotExsit, err.Error(), err.Error())
|
|
}
|
|
if len(resp.Data) == 0 {
|
|
return nil
|
|
}
|
|
for _, media := range resp.Data {
|
|
se := SyncOneVideo(ctx, media)
|
|
if se != nil {
|
|
log.Warn("SyncVideoInfo SYncOneVideo failed", log.Any("filter", req.FileIds), log.E(se))
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mediaFileDomain(sourceVersion string) string {
|
|
if sourceVersion == "v1" {
|
|
return proxy.BucketVideoAddressV1
|
|
}
|
|
return proxy.BucketVideoAddressV2
|
|
}
|
|
|
|
func buildMediaRespFromSyncItem(item SyncMediaFileItem) (proxy.MediaResp, error) {
|
|
oid, err := primitive.ObjectIDFromHex(item.Id)
|
|
if err != nil {
|
|
return proxy.MediaResp{}, err
|
|
}
|
|
return proxy.MediaResp{
|
|
Id: oid,
|
|
MediaSourceId: oid,
|
|
MediaType: 2,
|
|
Title: item.Title,
|
|
FieldNameFs: item.FieldNameFs,
|
|
FirstTag: item.FirstTag,
|
|
SecondTags: item.SecondTags,
|
|
Keyworld: item.Keyworld,
|
|
VideoTime: item.VideoTime,
|
|
CoverImg: item.CoverImg,
|
|
Publish: item.Publish,
|
|
Bango: item.Bango,
|
|
TaskID: item.TaskID,
|
|
Width: item.Width,
|
|
Height: item.Height,
|
|
}, nil
|
|
}
|
|
|
|
// SyncVideoFromMediaFile 从媒资库直同步到视频列表(不经过切片任务)。
|
|
func SyncVideoFromMediaFile(ctx context.Context, req *SyncMediaFileReq) (*SyncMediaFileResp, *srvdef.SrvErr) {
|
|
result := &SyncMediaFileResp{}
|
|
if req == nil {
|
|
return result, nil
|
|
}
|
|
minVideoSeconds := req.MinVideoSeconds
|
|
|
|
syncOne := func(id string, media proxy.MediaResp) {
|
|
if media.Id.IsZero() {
|
|
result.Failed++
|
|
return
|
|
}
|
|
if minVideoSeconds > 0 && media.VideoTime > 0 && media.VideoTime <= minVideoSeconds {
|
|
log.Info("SyncVideoFromMediaFile skip short video",
|
|
log.Any("id", id),
|
|
log.Any("videoTime", media.VideoTime))
|
|
result.Failed++
|
|
return
|
|
}
|
|
if len(media.FieldNameFs) == 0 {
|
|
log.Warn("SyncVideoFromMediaFile empty fieldNameFs", log.Any("id", id))
|
|
result.Failed++
|
|
return
|
|
}
|
|
media.MediaSourceId = media.Id
|
|
media.MediaType = 2
|
|
if req.FirstTagMap != nil {
|
|
if firstTag, ok := req.FirstTagMap[id]; ok && len(firstTag) > 0 {
|
|
media.FirstTag = firstTag
|
|
}
|
|
}
|
|
if se := SyncOneVideo(ctx, media); se != nil {
|
|
log.Warn("SyncVideoFromMediaFile SyncOneVideo failed", log.Any("id", id), log.E(se))
|
|
result.Failed++
|
|
return
|
|
}
|
|
result.Synced++
|
|
}
|
|
|
|
if len(req.VideoList) > 0 {
|
|
for _, item := range req.VideoList {
|
|
media, err := buildMediaRespFromSyncItem(item)
|
|
if err != nil {
|
|
log.Warn("SyncVideoFromMediaFile invalid item", log.Any("id", item.Id), log.E(err))
|
|
result.Failed++
|
|
continue
|
|
}
|
|
syncOne(item.Id, media)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
domain := mediaFileDomain(req.SourceVersion)
|
|
for _, id := range req.FileIds {
|
|
if len(id) == 0 {
|
|
continue
|
|
}
|
|
resp, err := storage.QueryMedia(ctx, &proxy.MediaQueryeReq{Id: id}, domain)
|
|
if err != nil || resp == nil || resp.Code != proxy.Success {
|
|
if resp != nil {
|
|
log.Warn("SyncVideoFromMediaFile QueryMedia failed", log.Any("id", id), log.E(err), log.Any("respCode", resp.Code))
|
|
} else {
|
|
log.Warn("SyncVideoFromMediaFile QueryMedia failed", log.Any("id", id), log.E(err))
|
|
}
|
|
result.Failed++
|
|
continue
|
|
}
|
|
media := resp.Data
|
|
if media.Id.IsZero() {
|
|
oid, parseErr := primitive.ObjectIDFromHex(id)
|
|
if parseErr != nil {
|
|
log.Warn("SyncVideoFromMediaFile invalid id", log.Any("id", id), log.E(parseErr))
|
|
result.Failed++
|
|
continue
|
|
}
|
|
media.Id = oid
|
|
}
|
|
syncOne(id, media)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// DelVideoInfo 删除指定APP
|
|
func DelVideoInfo(ctx context.Context, id int64) *srvdef.SrvErr {
|
|
info, err1 := videoinformationmod.FindOneVideoInformation(ctx, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: id,
|
|
}, options.FindOne().SetProjection(bson.D{
|
|
{Key: videoinformationmod.FN.VideoInformation.Id, Value: 1},
|
|
{Key: videoinformationmod.FN.VideoInformation.Tags, Value: 1},
|
|
}))
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
adFilter := bson.M{videoinformationmod.FN.VideoInformation.Id: id}
|
|
//删除
|
|
count, serr := videoinformationmod.DelOneVideoInformation(ctx, adFilter)
|
|
if serr != nil {
|
|
return serr
|
|
}
|
|
if count == 0 {
|
|
return srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
// 删除后检查一下合集数据
|
|
collectionInfos, _ := videocollectionmediamod.FindManyVideoCollectionMediaModels(ctx, bson.M{
|
|
videocollectionmediamod.FN.VideoCollectionMediaModel.VideoId: id,
|
|
})
|
|
if len(collectionInfos) > 0 {
|
|
collectionIds := make([]int64, 0)
|
|
for _, collectionInfo := range collectionInfos {
|
|
collectionIds = append(collectionIds, collectionInfo.CollectionID)
|
|
}
|
|
if len(collectionIds) > 0 {
|
|
_ = videoinformationmod.UpdateManyVideoInformation(ctx, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": collectionInfos},
|
|
}, bson.M{"$inc": bson.M{
|
|
videoinformationmod.FN.VideoInformation.VideoCount: -1,
|
|
}})
|
|
}
|
|
}
|
|
_, _ = videocollectionmediamod.DelManyVideoCollectionMediaModel(ctx, bson.M{
|
|
videocollectionmediamod.FN.VideoCollectionMediaModel.VideoId: id,
|
|
})
|
|
if len(info.Tags) > 0 {
|
|
oldTagIds := append([]int64{}, info.Tags...)
|
|
godef.Go(func() {
|
|
_ = srv_contenttag.SyncContentTagBindCount(context.Background(), oldTagIds, nil, contenttagmod.ContentTagTypeVideo)
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateVideoInfo 更新指定APP
|
|
func UpdateVideoInfo(ctx context.Context, id, userId int64, param VideoInfoUpdate) *srvdef.SrvErr {
|
|
now := time.Now()
|
|
info, serr := videoinformationmod.FindOneVideoInformation(ctx, bson.M{videoinformationmod.FN.VideoInformation.Id: id})
|
|
if serr != nil {
|
|
return serr
|
|
}
|
|
param.UpdatedAt = &now
|
|
param.SecondUpdatedAt = &now
|
|
if param.CreatorId != nil {
|
|
name := ""
|
|
if *param.CreatorId == 0 {
|
|
param.CreatorName = &name
|
|
} else {
|
|
// 查询作者
|
|
c, serr := videocreatermod.FindOneVideoCreaterInfo(ctx, bson.M{
|
|
videocreatermod.FN.VideoCreaterInfo.Id: *param.CreatorId,
|
|
})
|
|
if serr != nil {
|
|
return serr
|
|
}
|
|
name = c.Name
|
|
param.CreatorName = &name
|
|
}
|
|
}
|
|
if param.Tags != nil {
|
|
tagIds, err1 := srv_contenttag.NormalizeContentTagIds(*param.Tags)
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
if err1 := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypeVideo); err1 != nil {
|
|
return err1
|
|
}
|
|
param.Tags = &tagIds
|
|
}
|
|
if param.CategoryRanks != nil {
|
|
seen := make(map[int64]bool, len(*param.CategoryRanks))
|
|
for _, rank := range *param.CategoryRanks {
|
|
if rank.Id <= 0 || seen[rank.Id] {
|
|
return srvdef.New(srvdef.ErrParamError, "分类ID无效或重复", "分类ID无效或重复")
|
|
}
|
|
seen[rank.Id] = true
|
|
if _, err := videocategorymod.FindOneVideoCategory(ctx, bson.M{videocategorymod.FN.VideoCategory.Id: rank.Id}); err != nil {
|
|
if err.Code == srvdef.ErrNoData {
|
|
return srvdef.New(srvdef.ErrParamError, "视频分类不存在", "视频分类不存在")
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
isChangeActress := checkActressChange(*info, param)
|
|
isChangeTag := checkTagChange(*info, param)
|
|
if param.ActressIds != nil && len(*param.ActressIds) > 0 {
|
|
acts, serr := actress.FindManyActresss(ctx, bson.M{
|
|
actress.FN.Actress.Id: bson.M{"$in": *param.ActressIds},
|
|
})
|
|
if serr != nil {
|
|
log.Warn("UpdateVideoInfo FindManyActresss failed", log.Any("filter", param.ActressIds), log.E(serr))
|
|
return serr
|
|
}
|
|
acIds := make([]int64, 0)
|
|
for _, a := range acts {
|
|
acIds = append(acIds, a.Id)
|
|
}
|
|
param.ActressIds = &acIds
|
|
}
|
|
|
|
if param.Nodes != nil && len(*param.Nodes) > 0 {
|
|
nodes := *param.Nodes
|
|
for i, node := range nodes {
|
|
if len(node.NodeId) == 0 {
|
|
node.NodeId = primitive.NewObjectID().Hex()
|
|
nodes[i] = node
|
|
}
|
|
}
|
|
param.Nodes = &nodes
|
|
}
|
|
if param.Cover != nil && len(*param.Cover) > 0 {
|
|
if len(info.Video.Url) > 0 {
|
|
info.Video.Cover = *param.Cover
|
|
param.Video = &info.Video
|
|
}
|
|
}
|
|
if param.Status != nil && *param.Status != info.Status {
|
|
isChangeActress = true
|
|
tempActressIds := make([]int64, 0)
|
|
if param.ActressIds != nil {
|
|
tempActressIds = *param.ActressIds
|
|
}
|
|
go updateActressWorks(info.ActressIds, tempActressIds)
|
|
}
|
|
title := info.Title
|
|
if param.Title != nil {
|
|
title = *param.Title
|
|
}
|
|
keyworld := info.Keyworld
|
|
if param.Keyworld != nil {
|
|
keyworld = *param.Keyworld
|
|
}
|
|
exists := func(s string) bool {
|
|
doc, _ := videoinformationmod.FindOneVideoInformation(ctx, slugx.Filter(
|
|
videoinformationmod.FN.VideoInformation.Slug, videoinformationmod.FN.VideoInformation.Id, s, id))
|
|
return doc != nil
|
|
}
|
|
slug, changed, slugErr := slugx.PrepareOnUpdate(info.Slug, param.Slug,
|
|
utils.SlugSourceFromKeywordsOrTitle(keyworld, title), exists)
|
|
if slugErr != nil {
|
|
return slugErr
|
|
}
|
|
if changed {
|
|
param.Slug = &slug
|
|
} else {
|
|
param.Slug = nil
|
|
}
|
|
adFilter := bson.M{videoinformationmod.FN.VideoInformation.Id: id}
|
|
param.IndexNow = nil
|
|
isPublished := param.Status != nil &&
|
|
info.Status != *param.Status &&
|
|
*param.Status == pubdef.InformationStatusVerifyOK
|
|
if isPublished || (param.IsReIndexNow != nil && *param.IsReIndexNow) {
|
|
indexNow := int64(0)
|
|
param.IndexNow = &indexNow
|
|
}
|
|
serr = videoinformationmod.UpdateOneVideoInformation(ctx, adFilter, bson.M{"$set": param})
|
|
if serr != nil {
|
|
return serr
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
if isChangeActress {
|
|
tempActressIds := make([]int64, 0)
|
|
if param.ActressIds != nil {
|
|
tempActressIds = *param.ActressIds
|
|
}
|
|
go updateActressWorks(info.ActressIds, tempActressIds)
|
|
}
|
|
if isChangeTag {
|
|
tempIds := make([]int64, 0)
|
|
if param.Tags != nil {
|
|
tempIds = *param.Tags
|
|
}
|
|
oldTagIds := append([]int64{}, info.Tags...)
|
|
newTagIds := append([]int64{}, tempIds...)
|
|
godef.Go(func() {
|
|
_ = srv_contenttag.SyncContentTagBindCount(context.Background(), oldTagIds, newTagIds, contenttagmod.ContentTagTypeVideo)
|
|
})
|
|
}
|
|
if param.Status != nil && info.Status != *param.Status {
|
|
if flag := srv_report.VideoStatusFlag(*param.Status); flag != 0 {
|
|
mediaCopy := *info
|
|
mediaCopy.Status = *param.Status
|
|
godef.Go(func() {
|
|
srv_report.ReportVideoStatusChangeEvent(&mediaCopy, flag, userId)
|
|
})
|
|
}
|
|
}
|
|
newStatus := info.Status
|
|
if param.Status != nil {
|
|
newStatus = *param.Status
|
|
}
|
|
srv_seopush.NotifyInfoAfterWrite(ctx, srv_seopush.KindVideo, id, info.Status, newStatus)
|
|
return nil
|
|
}
|
|
|
|
func checkActressChange(info videoinformationmod.VideoInformation, updateParam VideoInfoUpdate) bool {
|
|
if updateParam.ActressIds != nil {
|
|
if len(info.ActressIds) != len(*updateParam.ActressIds) {
|
|
return true
|
|
}
|
|
actressIds := *updateParam.ActressIds
|
|
for i, v := range info.ActressIds {
|
|
if v != actressIds[i] {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
func checkTagChange(info videoinformationmod.VideoInformation, updateParam VideoInfoUpdate) bool {
|
|
if updateParam.Tags != nil {
|
|
if len(info.Tags) != len(*updateParam.Tags) {
|
|
return true
|
|
}
|
|
tags := *updateParam.Tags
|
|
for i, v := range info.Tags {
|
|
if v != tags[i] {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func updateActressWorks(oldIds []int64, newIds []int64) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
log.Error("updateActressWorks failed", log.Any("err", err))
|
|
}
|
|
}()
|
|
mapIds := make(map[int64]struct{})
|
|
if oldIds == nil {
|
|
oldIds = make([]int64, 0)
|
|
}
|
|
if newIds == nil {
|
|
newIds = make([]int64, 0)
|
|
}
|
|
for _, id := range newIds {
|
|
mapIds[id] = struct{}{}
|
|
}
|
|
for _, id := range oldIds {
|
|
mapIds[id] = struct{}{}
|
|
}
|
|
ids := make([]int64, 0)
|
|
for id := range mapIds {
|
|
ids = append(ids, id)
|
|
}
|
|
ctx := context.Background()
|
|
for _, id := range ids {
|
|
count, err := videoinformationmod.CountVideoInformations(ctx, bson.M{videoinformationmod.FN.VideoInformation.Status: pubdef.InformationStatusVerifyOK, videoinformationmod.FN.VideoInformation.ActressIds: bson.M{"$in": []int64{id}}})
|
|
if err == nil {
|
|
_ = actress.UpdateOneActress(ctx, bson.M{
|
|
actress.FN.Actress.Id: id,
|
|
}, bson.M{
|
|
"$set": bson.M{
|
|
actress.FN.Actress.Works: count,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func AddVideoCollection(ctx context.Context, param *AddVideoCollectionReq) *srvdef.SrvErr {
|
|
tagIds, err1 := srv_contenttag.NormalizeContentTagIds(param.Tags)
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
if err1 := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypeVideo); err1 != nil {
|
|
return err1
|
|
}
|
|
info := videoinformationmod.VideoInformation{
|
|
Id: mongos.Lid.VideoInfo.Get(),
|
|
Title: param.Title,
|
|
MediaType: videoinformationmod.MediaType_Collection,
|
|
AddedTime: time.Now(),
|
|
Keyworld: param.Keyworld,
|
|
FirstTag: param.FirstTag,
|
|
SecondTags: param.SecondTags,
|
|
CoverImg: param.CoverImg,
|
|
Desc: param.Desc,
|
|
Remark: param.Remark,
|
|
Nodes: make([]videoinformationmod.Node, 0),
|
|
Status: pubdef.InformationStatusWaiting,
|
|
Video: videoinformationmod.VideoInfo{},
|
|
Tags: tagIds,
|
|
Real: pubdef.Flow{},
|
|
VideoCount: 0,
|
|
UpdatedAt: time.Now(),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
if err1 := videoinformationmod.InsertOneVideoInformation(ctx, &info); err1 != nil {
|
|
return err1
|
|
}
|
|
return srv_contenttag.SyncContentTagBindCount(ctx, nil, tagIds, contenttagmod.ContentTagTypeVideo)
|
|
}
|
|
|
|
// AddVideoTags 批量追加视频统一标签
|
|
func AddVideoTags(ctx context.Context, req *VideoAddTagsReq) *srvdef.SrvErr {
|
|
if req == nil || len(req.VideoIds) == 0 || len(req.Tags) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "视频ID和标签ID不能为空", "")
|
|
}
|
|
videoIds, err := normalizeFakeFlowIds(req.VideoIds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tagIds, err := srv_contenttag.NormalizeContentTagIds(req.Tags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(tagIds) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "标签ID不能为空", "")
|
|
}
|
|
if err := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypeVideo); err != nil {
|
|
return err
|
|
}
|
|
list, err := videoinformationmod.FindManyVideoInformations(ctx, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": videoIds},
|
|
}, options.Find().SetProjection(bson.D{
|
|
{Key: videoinformationmod.FN.VideoInformation.Id, Value: 1},
|
|
{Key: videoinformationmod.FN.VideoInformation.Tags, Value: 1},
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(list) == 0 {
|
|
return srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
now := time.Now()
|
|
docs := make([]mongo.WriteModel, 0, len(list))
|
|
changes := make([]srv_contenttag.ContentTagBindChange, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
newTagIds, changed := appendMissingVideoTagIds(item.Tags, tagIds)
|
|
if !changed {
|
|
continue
|
|
}
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{videoinformationmod.FN.VideoInformation.Id: item.Id}).
|
|
SetUpdate(bson.M{"$set": bson.M{
|
|
videoinformationmod.FN.VideoInformation.Tags: newTagIds,
|
|
videoinformationmod.FN.VideoInformation.UpdatedAt: now,
|
|
}}))
|
|
changes = append(changes, srv_contenttag.ContentTagBindChange{
|
|
OldTagIds: append([]int64{}, item.Tags...),
|
|
NewTagIds: newTagIds,
|
|
})
|
|
}
|
|
if len(docs) == 0 {
|
|
return nil
|
|
}
|
|
if _, err := videoinformationmod.BulkWriteVideoInformation(ctx, docs, options.BulkWrite().SetOrdered(false)); err != nil {
|
|
return err
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
return srv_contenttag.SyncContentTagBindCountBatch(ctx, changes, contenttagmod.ContentTagTypeVideo)
|
|
}
|
|
|
|
// SetVideoTags 单/批量覆盖设置视频统一标签,并同步新标签表视频绑定数量
|
|
func SetVideoTags(ctx context.Context, req *VideoSetTagsReq) *srvdef.SrvErr {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "视频ID不能为空", "")
|
|
}
|
|
if req.TagIds == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "标签ID不能为空", "")
|
|
}
|
|
videoIds, err := normalizeFakeFlowIds(req.Ids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tagIds, err := srv_contenttag.NormalizeContentTagIds(*req.TagIds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypeVideo); err != nil {
|
|
return err
|
|
}
|
|
list, err := videoinformationmod.FindManyVideoInformations(ctx, bson.M{
|
|
videoinformationmod.FN.VideoInformation.Id: bson.M{"$in": videoIds},
|
|
}, options.Find().SetProjection(bson.D{
|
|
{Key: videoinformationmod.FN.VideoInformation.Id, Value: 1},
|
|
{Key: videoinformationmod.FN.VideoInformation.Tags, Value: 1},
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(list) == 0 {
|
|
return srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
now := time.Now()
|
|
docs := make([]mongo.WriteModel, 0, len(list))
|
|
changes := make([]srv_contenttag.ContentTagBindChange, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil || sameVideoTagIds(item.Tags, tagIds) {
|
|
continue
|
|
}
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{videoinformationmod.FN.VideoInformation.Id: item.Id}).
|
|
SetUpdate(bson.M{"$set": bson.M{
|
|
videoinformationmod.FN.VideoInformation.Tags: tagIds,
|
|
videoinformationmod.FN.VideoInformation.UpdatedAt: now,
|
|
}}))
|
|
changes = append(changes, srv_contenttag.ContentTagBindChange{
|
|
OldTagIds: append([]int64{}, item.Tags...),
|
|
NewTagIds: append([]int64{}, tagIds...),
|
|
})
|
|
}
|
|
if len(docs) == 0 {
|
|
return nil
|
|
}
|
|
if _, err := videoinformationmod.BulkWriteVideoInformation(ctx, docs, options.BulkWrite().SetOrdered(false)); err != nil {
|
|
return err
|
|
}
|
|
playletcache.ClearHomeCache()
|
|
return srv_contenttag.SyncContentTagBindCountBatch(ctx, changes, contenttagmod.ContentTagTypeVideo)
|
|
}
|
|
|
|
func sameVideoTagIds(current, target []int64) bool {
|
|
if len(current) != len(target) {
|
|
return false
|
|
}
|
|
for i := range current {
|
|
if current[i] != target[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func appendMissingVideoTagIds(current, addIds []int64) ([]int64, bool) {
|
|
result := append([]int64{}, current...)
|
|
tagMap := make(map[int64]struct{}, len(current)+len(addIds))
|
|
for _, tagId := range current {
|
|
if tagId > 0 {
|
|
tagMap[tagId] = struct{}{}
|
|
}
|
|
}
|
|
changed := false
|
|
for _, tagId := range addIds {
|
|
if _, ok := tagMap[tagId]; ok {
|
|
continue
|
|
}
|
|
tagMap[tagId] = struct{}{}
|
|
result = append(result, tagId)
|
|
changed = true
|
|
}
|
|
return result, changed
|
|
}
|