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) }