Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
365 lines
12 KiB
Go
365 lines
12 KiB
Go
package srv_videoinfo
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"net/http"
|
|
"time"
|
|
"videoweb/pub/common/godef"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/common/srvdef"
|
|
"videoweb/pub/common/useragent"
|
|
"videoweb/pub/common/utils"
|
|
"videoweb/pub/models/videocreatermod"
|
|
"videoweb/pub/models/videoinformationmod"
|
|
"videoweb/pub/mongos"
|
|
"videoweb/pub/storage"
|
|
"videoweb/pub/storage/proxy"
|
|
"videoweb/web/service/srv_report"
|
|
"videoweb/web/service/srv_search"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"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{}
|
|
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.CreatorId != nil && *param.CreatorId > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.CreatorId] = *param.CreatorId
|
|
}
|
|
if param.TopicId != nil && *param.TopicId > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.TopicIds] = bson.M{"$in": []int64{*param.TopicId}}
|
|
}
|
|
if param.Status != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.Status] = *param.Status
|
|
}
|
|
if param.CategoryId != nil && *param.CategoryId > 0 {
|
|
filter[videoinformationmod.FN.VideoInformation.CategoryIds] = bson.M{"$in": []int64{*param.CategoryId}}
|
|
}
|
|
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.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.IsTop != nil {
|
|
filter[videoinformationmod.FN.VideoInformation.IsTop] = *param.IsTop
|
|
}
|
|
if param.IsH265 != nil {
|
|
if *param.IsH265 {
|
|
filter[videoinformationmod.FN.VideoInformation.HevcStatus] = videoinformationmod.HevcStatusSuccess
|
|
} else {
|
|
filter[videoinformationmod.FN.VideoInformation.HevcStatus] = bson.M{"$in": []videoinformationmod.HevcStatus{
|
|
videoinformationmod.HevcStatusNone,
|
|
videoinformationmod.HevcStatusPending,
|
|
videoinformationmod.HevcStatusFailed,
|
|
}}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
ads, serr := videoinformationmod.FindManyVideoInformations(ctx, filter, opt)
|
|
if serr != nil {
|
|
return resp, serr
|
|
}
|
|
resp.List = ads
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// 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 {
|
|
serr := SyncOneVideo(ctx, media)
|
|
if serr != nil {
|
|
log.Warn("SyncVideoInfo SYncOneVideo failed", log.Any("filter", req.FileIds), log.E(serr))
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DelVideoInfo 删除指定APP
|
|
func DelVideoInfo(ctx context.Context, id int64) *srvdef.SrvErr {
|
|
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, "", "")
|
|
}
|
|
// 同步删除 ES 中的数据,避免搜索到已删除的视频
|
|
go srv_search.DelVideoFromES(id)
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// setVideoFakeField 使用BulkWrite批量设置假流量字段,避免批量请求逐条访问数据库
|
|
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, "", "")
|
|
}
|
|
return &SetFakeCountResp{MatchedCount: result.MatchedCount, ModifiedCount: result.ModifiedCount}, nil
|
|
}
|
|
|
|
// normalizeFakeFlowIds 校验并去重视频ID,减少无效批量写入
|
|
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
|
|
}
|
|
|
|
func randomFakeFlowValue(r *rand.Rand, min, max int64) int64 {
|
|
if min == max {
|
|
return min
|
|
}
|
|
return min + r.Int63n(max-min+1)
|
|
}
|
|
|
|
// UpdateVideoInfo 更新指定APP
|
|
func UpdateVideoInfo(ctx context.Context, id, userId int64, param VideoInfoUpdate, ua useragent.UA) *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.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 {
|
|
// if *param.Status == videoinformationmod.InformationSatusVerifyOK {
|
|
// param.AddedTime = &now
|
|
// }
|
|
//}
|
|
adFilter := bson.M{videoinformationmod.FN.VideoInformation.Id: id}
|
|
param.IndexNow = nil
|
|
if (param.Status != nil && *param.Status == videoinformationmod.InformationSatusVerifyOK) ||
|
|
(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
|
|
}
|
|
// status 发生变更时实时同步 ES,避免搜索结果与 MongoDB 不一致
|
|
if param.Status != nil {
|
|
newStatus := *param.Status
|
|
go func() {
|
|
if newStatus != videoinformationmod.InformationSatusVerifyOK {
|
|
// 下架/重置状态:立即从 ES 删除
|
|
srv_search.DelVideoFromES(id)
|
|
} else {
|
|
// 上架:从 MongoDB 读取最新完整数据后写入 ES
|
|
latest, serr := videoinformationmod.FindOneVideoInformation(context.Background(), adFilter)
|
|
if serr != nil {
|
|
log.Warn("UpdateVideoInfo: read latest for ES upsert failed", log.Any("id", id), log.E(serr))
|
|
return
|
|
}
|
|
srv_search.UpsertVideoToES(latest)
|
|
}
|
|
}()
|
|
}
|
|
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, ua)
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func AddVideoCollection(ctx context.Context, param *AddVideoCollectionReq) *srvdef.SrvErr {
|
|
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: videoinformationmod.InformationSatusWaiting,
|
|
Video: videoinformationmod.VideoInfo{},
|
|
CategoryIds: make([]int64, 0),
|
|
Tags: param.Tags,
|
|
Real: videoinformationmod.Flow{},
|
|
VideoCount: 0,
|
|
IndexNow: 0,
|
|
UpdatedAt: time.Now(),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
return videoinformationmod.InsertOneVideoInformation(ctx, &info)
|
|
}
|