package vidser import ( "91porn-server/app/service/taskser" "91porn-server/app/service/vidhelpser" sli "91porn-server/common/slice" "91porn-server/models/v/dailytaskmod" "91porn-server/models/v/modulesectionmod" "91porn-server/models/v/vidtimeonlinemod" "bytes" "encoding/json" "errors" "fmt" "path/filepath" "sort" "strconv" "strings" "sync" "time" "github.com/bxcodec/faker/v3/support/slice" "go.mongodb.org/mongo-driver/mongo/options" "github.com/gin-gonic/gin" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/constant/redisconst" "91porn-server/common/file" "91porn-server/common/httputil" "91porn-server/common/log" "91porn-server/common/stderr" "91porn-server/common/timeutil" "91porn-server/models/commod" "91porn-server/models/v/imagetopmod" "91porn-server/models/v/locmod" "91porn-server/models/v/modulevidmod" "91porn-server/models/v/operationlogmod" "91porn-server/models/v/pushmod" "91porn-server/models/v/tagmod" "91porn-server/models/v/usermod" "91porn-server/models/v/vidmod" "91porn-server/web/service/updownloadser" "91porn-server/web/vidhelp" "91porn-server/web/webg" "github.com/shopspring/decimal" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) // SubmitBase 提交基本信息 func SubmitBase(req vidmod.WebSubmitReq) (code stderr.Code, vid primitive.ObjectID) { tags := make([]primitive.ObjectID, 0, len(req.Tags)) for _, t := range req.Tags { tid, suc := updownloadser.HandleTag(t, req.Cover) if !suc { continue } tags = append(tags, tid) } l := locmod.Location{ Longitude: req.Location.Longitude, Latitude: req.Location.Latitude, Country: req.Location.Country, CountryCode: req.Location.CountryCode, Province: req.Location.Province, ProvinceCode: req.Location.ProvinceCode, City: req.Location.City, CityCode: req.Location.CityCode, Address: req.Location.Address, Visit: 0, } if err := locmod.InsertLocationInfo(l); err != nil { return stderr.ErrDbInsertError, primitive.NilObjectID } lid, err := locmod.GetLocationIDByCity(req.Location.City) if err != nil { return stderr.ErrDbQueryError, primitive.NilObjectID } series := []string{} if len(req.SeriesCover) != 0 { series = req.SeriesCover } oid, _ := primitive.ObjectIDFromHex(req.SourceID) v := vidmod.VideoModel{ ID: oid, NewsType: req.NewsType, PublisherID: req.UID, Title: req.Title, Content: req.Content, MimeType: req.MimeType, Tags: tags, SourceID: req.SourceID, SourceURL: req.SourceURL, PlayTime: req.PlayTime, Cover: req.Cover, CoverThumb: req.CoverThumb, Activity: []primitive.ObjectID{}, SeriesCover: series, SeriesNum: len(series), Via: req.Via, Size: req.Size, Rating: 0, PlayCount: 0, Coins: req.Coins, LikeCount: 0, CommentCount: 0, ShareCount: 0, Status: 0, Location: lid, FreeTime: req.FreeTime, CreatedAt: time.Now(), UpdatedAt: time.Now(), Width: req.Width, Height: req.Height, MDSID: req.MDSID, VerticalCover: req.VerticalCover, Ratio: req.Ratio, Resolution: req.Resolution, SeedLinkUrl: req.SeedLinkUrl, SeedSize: req.SeedSize, SeedPlayTime: req.SeedPlayTime, RichText: req.RichText, PreviewStart: req.PreviewStart, } if len(v.Tags) > 0 && req.Status == 1 { v.Status = req.Status v.ReviewAt = time.Now() v.ReviewAccount = req.Account } else if req.Status == vidmod.OnlinePass { v.Status = req.Status } if vid, err = vidmod.InsertBase(v); err != nil { return stderr.ErrDbInsertError, primitive.NilObjectID } for _, t := range tags { if err = tagmod.IncreaseTagVidCount(t, 1); err != nil { log.Error("SubmitBase error", log.Any("req", req), log.E(err)) continue } } return stderr.Success, vid } // SendBaseInfo2AWs 上传基本文件信息到aws func SendBaseInfo2AWs(req vidmod.WebSubmitReq) (code stderr.Code) { info := vidmod.BaseVid4Aws{ ID: req.SourceID, Tags: req.Tags, CheckSum: req.MD5, Title: req.Title, Actors: []string{req.Actor}, AddedTime: time.Now().Format("2006-01-02"), PlayTime: uint64(req.PlayTime), Size: req.Size, Filename: req.Filename, Via: "q1", Type: "sp", } params, _ := common.ToJsonM(info) respBody := commod.Resp{} if _, err := httputil.DefaultClientPostJsonWithResp(&respBody, webg.Conf.URL.FileInfoUrl, nil, params); err != nil { log.Error("SendBaseInfo2AWs error", log.Any("req", req), log.E(err)) return stderr.Failure } if respBody.Code != stderr.Success { log.Error("SendBaseInfo2AWs status error", log.Any("req", req), log.Any("respBody.Code", respBody.Code)) return stderr.Failure } return stderr.Success } // MAP 别名 type MAP map[string]interface{} func ExportVidList(cond MAP, modeList SortModeList) (buff bytes.Buffer, code stderr.Code) { var err error var secID primitive.ObjectID secID, cond, _, err = dealVidListCond(cond) if code = err.(stderr.Code); code != stderr.Success { return } sort := make(primitive.D, len(modeList)) for i, v := range modeList { sort[i] = bson.E{Key: v.Field, Value: v.Value} } step := 900 page := 1 vModels, total, err := vidmod.GetvideoListByMapCond(cond, sort, uint64(page), uint64(step)) if err != nil { code = stderr.ErrDbQueryError return } if total > 30000 { code = stderr.ErrDbTooMuchRecords return } allVideoInfos, err := vidhelp.EncodeVideoInfo(vModels, secID) if err != nil { code = stderr.ErrDbQueryError return } wg := sync.WaitGroup{} totalPages := int(total)/step + 1 out := make(chan []*vidmod.WebVideo, totalPages) for page++; page <= totalPages; page++ { wg.Add(1) common.GoParam(page, func(i int) { defer wg.Done() vModels, _, err = vidmod.GetvideoListByMapCond(cond, sort, uint64(i), uint64(step)) if err != nil { log.Error("vidmod.GetvideoListByMapCond", log.Any("cond", cond), log.Any("sort", sort), log.Any("page", page), log.Any("step", step), log.E(err)) return } infos, err := vidhelp.EncodeVideoInfo(vModels, secID) if err != nil { return } out <- infos }) } common.Go(func() { wg.Wait() close(out) }) for vs := range out { allVideoInfos = append(allVideoInfos, vs...) } excelFile := file.WriteStruct2Xlsx("帖子列表", allVideoInfos) defer excelFile.Close() _ = excelFile.DeleteSheet("Sheet1") _, _ = excelFile.WriteTo(&buff) return } // GetVidList 获取视频列表 func GetVidList(cond MAP, modeList SortModeList, page, size uint64, isPush bool) (code stderr.Code, data vidmod.ListResp) { // 为了不影响查询结果,点赞率与成交率优先处理 var purchaseRate, likeRate int if item, ok := cond["purchaseRate"]; ok { // 防止panic,断言未命中默认为类型默认值 purchaseRate, _ = item.(int) delete(cond, "purchaseRate") } if item, ok := cond["likeRate"]; ok { likeRate, _ = item.(int) delete(cond, "likeRate") } secID, cond, videoIDs, err := dealVidListCond(cond) if code = err.(stderr.Code); code != stderr.Success { return } s := make(primitive.D, len(modeList)) for i, v := range modeList { s[i] = bson.E{Key: v.Field, Value: v.Value} } //是否强推 if isPush { //查询强推视频 pushList, _, _, err := pushmod.GetPushList(page, size, bson.M{}, bson.D{{Key: "createdAt", Value: -1}}) if err != nil { return stderr.ErrDbQueryError, data } if len(pushList) <= 0 { return stderr.Success, data } vidIds := make([]primitive.ObjectID, len(pushList)) for i, p := range pushList { vidIds[i] = p.VideoID } cond["_id"] = bson.M{"$in": vidIds} } log.Info("---", log.Any("cond", cond), log.Any("sort", s)) // 这里会打乱之前排序 vModels, total, err := vidmod.GetvideoListByMapCond(cond, s, page, size) if err != nil { return stderr.ErrDbQueryError, data } // 因为查询数据库后数据会打乱,因此此处重新根据专题序号进行排序 if !secID.IsZero() { newVideos := make([]*vidmod.VideoModel, 0, len(videoIDs)) for _, v := range videoIDs { for _, vv := range vModels { if v == vv.ID { newVideos = append(newVideos, vv) break } } } vModels = newVideos } infos, err := vidhelp.EncodeVideoInfo(vModels, secID) if err != nil { return stderr.ErrDbQueryError, data } // 当前搜索结果按照成交率排序,正序 if purchaseRate == 1 { sort.Slice(infos, func(i, j int) bool { return infos[i].PurchaseRate < infos[j].PurchaseRate }) } // 倒序 if purchaseRate == -1 { sort.Slice(infos, func(i, j int) bool { return infos[i].PurchaseRate > infos[j].PurchaseRate }) } // 当前搜索结果按照点击率排序,正序 if likeRate == 1 { sort.Slice(infos, func(i, j int) bool { return infos[i].LikeRate < infos[j].LikeRate }) } // 倒序 if likeRate == -1 { sort.Slice(infos, func(i, j int) bool { return infos[i].LikeRate > infos[j].LikeRate }) } return stderr.Success, vidmod.ListResp{VInfos: infos, Total: total} } func dealVidListCond(cond MAP) (primitive.ObjectID, MAP, []primitive.ObjectID, error) { var secID primitive.ObjectID var err error sectionID, ok := cond["sectionID"] if ok { secID, err = primitive.ObjectIDFromHex(sectionID.(string)) if err != nil { log.Error("primitive.ObjectIDFromHex", log.Any("sectionID", sectionID), log.E(err)) return secID, cond, nil, stderr.ErrDbQueryError } var flag bool isSorted, ok := cond["isSortedUnderModule"] if ok { flag = isSorted.(bool) } videoIDs, err := modulevidmod.GetBySectionID(secID, flag) if err != nil { return secID, cond, nil, stderr.ErrDbQueryError } cond["_id"] = bson.M{"$in": videoIDs} delete(cond, "sectionID") delete(cond, "isSortedUnderModule") return secID, cond, videoIDs, stderr.Success } delete(cond, "sectionID") delete(cond, "isSortedUnderModule") return secID, cond, nil, stderr.Success } func updateTagVideoCnt(oldTags, newTags []primitive.ObjectID) { mOld := make(map[primitive.ObjectID]bool) mNew := make(map[primitive.ObjectID]bool) for _, v := range newTags { mNew[v] = true } for _, v := range oldTags { mOld[v] = true } for _, v := range newTags { if !mOld[v] { _ = tagmod.IncreaseTagVidCount(v, 1) } } for _, v := range oldTags { if !mNew[v] { _ = tagmod.IncreaseTagVidCount(v, -1) } } } func updatePublisherRewarded(id string, specReward int) { info, err := vidmod.GetVideoInfo(id) if err != nil { return } _ = usermod.IncRewarded(info.PublisherID, decimal.NewFromInt(int64(specReward))) } // UpdateVidInfo 更新视频信息 func UpdateVidInfo(req vidmod.EditReq, manager string) (code stderr.Code, data interface{}) { vidInfo, err := vidmod.GetVideoInfo(req.ID) if err != nil { log.Error("UpdateVidInfo error", log.Any("req", req), log.E(err)) return stderr.CommentVideoInfoErr, nil } // —— 上架相关时做敏感词校验 —— // 触发条件: // 1. status 切换到 上架(CheckPass=1) / 上架并免费(Free=3) // 2. 当前已上架(1/3) 且本次更新涉及 Title/Content/RichText // 校验文本: req 提供则用新值, 否则用 DB 现值(即"落库后"的文本快照) if shouldCheckSensitiveOnUpdate(vidInfo, req) { title := vidInfo.Title content := vidInfo.Content richText := vidInfo.RichText if req.Title != nil { title = *req.Title } if req.Content != nil { content = *req.Content } if req.RichText != nil { richText = *req.RichText } if err := CheckShelvingFields(vidInfo.ID, title, content, richText); err != nil { return stderr.ContentSensitiveHit, err.Error() } } if req.Type == 2 { if !vidInfo.ID.IsZero() && req.RichText != nil { if _, err = vidmod.UpdateVideoByMapCond(vidInfo.ID, bson.M{"richText": req.RichText}); err != nil { return stderr.ErrDbUpdateError, nil } return stderr.Success, nil } return stderr.ErrParamError, nil } if req.Status != nil && *req.Status != vidInfo.Status { if err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Status, BeforeContent: fmt.Sprintf("%v", vidInfo.Status), AfterContent: fmt.Sprintf("%v", *req.Status), CreatedUser: manager, }); err != nil { return stderr.ErrDbInsertError, nil } } if req.Coins != nil && int64(*req.Coins) != vidInfo.Coins { if err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Price, BeforeContent: fmt.Sprintf("%v", vidInfo.Coins), AfterContent: fmt.Sprintf("%v", *req.Coins), CreatedUser: manager, }); err != nil { return stderr.ErrDbInsertError, nil } } if req.Title != nil && *req.Title != vidInfo.Title { if err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Content, BeforeContent: fmt.Sprintf("%v", vidInfo.Title), AfterContent: fmt.Sprintf("%v", *req.Title), CreatedUser: manager, }); err != nil { return stderr.ErrDbInsertError, nil } } oid, err := primitive.ObjectIDFromHex(req.ID) if err != nil { log.Error("UpdateVidInfo error", log.Any("req", req), log.E(err)) return stderr.ErrParamError, nil } if req.FakeLikeCount != nil { gt, err := vidmod.FakeLikeGteLike(req.ID, *req.FakeLikeCount) if err != nil { return stderr.ErrDbQueryError, nil } if !gt { return stderr.ErrParamError, "fakelikecount need gte likecount." } } oldTags, err := vidmod.GetVideoTag(req.ID) if err != nil { return stderr.ErrDbQueryError, nil } set := bson.M{} if req.SeriesCover != nil { sn := len(*req.SeriesCover) req.SeriesNum = &sn } videoInfo, err := vidmod.GetVideoInfo(req.ID) if err != nil { return } if req.WorksSort != nil { if *req.WorksSort != 0 && videoInfo.WorksSort != *req.WorksSort { // 查询并修改重复排序 err = vidmod.UpdateWorksSort(videoInfo.PublisherID, videoInfo.WorksSort, *req.WorksSort) if err != nil { return } } } if req.Type == 0 { set, _ = common.ToBsonM(req.EditInfo) } newTags := []primitive.ObjectID{} if len(req.Tags) > 0 { newTags = vidhelp.AddTags(req.ID, req.Tags) set["tags"] = newTags } if len(req.TagSort) > 0 { set["tagSort"] = covertTagSort(req) } if req.CreatedAt != nil && len(*req.CreatedAt) != 0 { createdAt := timeutil.StrTimeToTime(*req.CreatedAt) set["createdAt"] = createdAt } if req.Status != nil && *req.Status == vidmod.CheckPass && *req.Status != vidInfo.Status { set["reviewAt"] = time.Now() } if req.PublisherID != nil { set["publisherID"] = req.PublisherID } if req.Content != nil { set["content"] = req.Content } if req.PreviewURL != nil { set["previewURL"] = *req.PreviewURL } if req.UpTag != nil { set["upTag"] = *req.UpTag } if req.NewsType != nil { set["newsType"] = req.NewsType } if req.TimeNodeList != nil { set["timeNodeList"] = *req.TimeNodeList } //if req.LiaoBaTop != nil { // set["liaoBaTop"] = *req.LiaoBaTop //} applyLiaoBaTopSort(set, req.LiaoBaTopSort) if req.ActivityID != nil { if *req.ActivityID == "" { set["activityId"] = primitive.NilObjectID } else { activityId, err := primitive.ObjectIDFromHex(*req.ActivityID) if err != nil { log.Error(fmt.Sprintf("解析参赛作品活动ID异常:%v", err), log.Any("activityID:", *req.ActivityID)) return stderr.ErrParamError, nil } set["activityId"] = activityId } } if req.HappinessPlazaTop != nil { set["happinessPlazaTop"] = req.HappinessPlazaTop id, _ := primitive.ObjectIDFromHex(req.ID) if *req.HappinessPlazaTop == 1 { //插入幸福广场置顶 imgTop := imagetopmod.ImageTopModel{ ID: primitive.NilObjectID, NewSort: 0, NewEndTime: &time.Time{}, RecommendSort: 0, RecommendEndTime: &time.Time{}, UpdatedAt: time.Now(), } imgTop.ImageId = id //插入数据 if err = imagetopmod.InsertOne(&imgTop); err != nil { return stderr.ErrDbInsertError, nil } } else { //删除幸福广场置顶 if err = imagetopmod.DeleteOne(id); err != nil { return stderr.ErrDbDeleteError, nil } } } if req.ShareSort != nil { set["shareSort"] = *req.ShareSort } delete(set, "fakeRewarded") if _, err = vidmod.UpdateVideoByMapCond(oid, set); err != nil { return stderr.ErrDbUpdateError, nil } if req.NewsType != nil { // 更新 modulevidmod.UpdateNewsTypeByVideoID(oid, *req.NewsType) } updateTagVideoCnt(oldTags, newTags) if req.FakeRewarded != nil { updatePublisherRewarded(req.ID, *req.FakeRewarded) _ = vidmod.IncSpecFakeRewarded(oid, decimal.NewFromInt(int64(*req.FakeRewarded))) } format := redisconst.VideoInfoKey() s := fmt.Sprintf(format, videoInfo.ID.Hex()) if _, err = webg.Redis.Del(s); err != nil { return stderr.ErrDbUpdateError, nil } return stderr.Success, nil } // applyLiaoBaTopSort only builds the update for the video currently being // edited. Duplicate sort values are intentional and must not mutate another // video that already uses the same value. func applyLiaoBaTopSort(set bson.M, sortCode *int) { if sortCode == nil { return } set["liaoBaTop"] = *sortCode > 0 set["liaoBaTopSort"] = *sortCode } func covertTagSort(info vidmod.EditReq) bson.M { set := make(bson.M, 10) for _, v := range info.TagSort { set[v.TagId] = v.SortCode } return set } // DeleteManyByTTL 删除视频 被删除的视屏归属为officialUID,未审核或审核未通过的视屏通过TTL删除 func DeleteManyByTTL(ids ...primitive.ObjectID) (code stderr.Code, data interface{}) { delSetDoc := (&vidmod.SetDoc{}).SetPublisherID(constant.DeleteUID).SetDeleteAt(time.Now()).SetStatus(vidmod.CheckFailure) if _, err := vidmod.ModifyMany(ids, *delSetDoc); err != nil { return stderr.ErrDbUpdateError, nil } if err := modulevidmod.DeleteManyByVids(ids); err != nil { return stderr.ErrDbDeleteError, nil } return stderr.Success, nil } func BatchUpdateReco(req vidmod.BatchUpdateRecoRequest) error { return vidmod.BatchUpdateReco(req.IDs, req.Reco) } // UpdateVids 批量更新视频信息 func UpdateVids(req vidmod.EditManyReq) (code stderr.Code, data interface{}) { if len(req.IDs) == 0 { return } oids := make([]primitive.ObjectID, 0, len(req.IDs)) for _, id := range req.IDs { oid, err := primitive.ObjectIDFromHex(id) if err != nil { log.Error("UpdateVids error", log.Any("req", req), log.E(err)) continue } oids = append(oids, oid) } // 上架(UpStatus=1 上架 / 3 上架并免费)前置敏感词校验 if req.UpStatus != nil && (*req.UpStatus == vidmod.CheckPass || *req.UpStatus == vidmod.Free) { if err := CheckShelvingByIDs(oids); err != nil { return stderr.ContentSensitiveHit, err.Error() } } newTags := vidhelp.AddTags(req.IDs[0], req.Tags) req.TagIds = append(req.TagIds, newTags...) req.TagIds = common.Deduplication(req.TagIds) var ( count int64 err error ) if req.Field != "" { // 只更新某个状态 count, err = vidmod.UpdateVideoBatch(oids, req.Field, req.Status) if err != nil { if err.Error() == "overLiaoBaLimit" { return stderr.LiaoBaTopOverLimit, nil } return stderr.ErrDbUpdateError, nil } } else if req.OriginPublisherID != nil && req.PublisherID != nil { filter := bson.M{"publisherID": *req.OriginPublisherID} updateData := bson.M{ "$set": bson.M{"publisherID": *req.PublisherID}, } // 进行视频转移 err = vidmod.UpdateMany(filter, updateData) if err != nil { log.Error("帖子批量转移失败", log.Any("OriginPublisherID", *req.OriginPublisherID), log.Any("PublisherID", *req.PublisherID), log.E(err), ) return stderr.ErrDbUpdateError, nil } } else { // 更新tag或者价格 var updater = bson.M{ "updatedAt": time.Now(), } if req.Coins != nil { updater["coins"] = req.Coins } if req.PublisherID != nil { updater["publisherID"] = req.PublisherID } if req.UpStatus != nil { updater["status"] = *req.UpStatus } if req.FreeTime != nil { updater["freeTime"] = *req.FreeTime } if req.DownloadAllow != nil { updater["downloadAllow"] = *req.DownloadAllow } // 这里不去判断tag存不存在,因为tag列表是从服务器拉下去的数据,选中上来的tag也是在数据中存在的 if len(req.TagIds) > 0 { updater["tags"] = req.TagIds // 获取相关视频 videoList, err := vidmod.GetVideoListByIDsNoStatus(oids) if err != nil { log.Error(fmt.Sprintf("UpdateVidTags vidmod.GetVideoListByIDsNoStatus err:%v, ids:%v", err, req.IDs)) return stderr.ErrDbQueryError, nil } var tagMap = map[primitive.ObjectID]int{} for _, v := range videoList { for _, v := range v.Tags { tagMap[v] = tagMap[v] - 1 } } for _, tid := range req.TagIds { tagMap[tid] = len(videoList) } // 修改旧tag与新tag的视频数量 tagmod.IncTagVidCountByMap(tagMap) } count, err = vidmod.UpdateManyVideo(oids, updater) if err != nil { log.Error(fmt.Sprintf("UpdateVids vidmod.UpdateManyVideo err:%v, ids:%v", err, req.IDs)) return stderr.ErrDbQueryError, nil } } return stderr.Success, vidmod.OperateResult{Count: count} } // PassAndEditVids 批量审核更新更新视频信息 func PassAndEditVids(req vidmod.PassManyReq, manager string) (code stderr.Code, data interface{}) { // 上架(含直接通过 / 通过并免费)前置敏感词校验 if req.Pass == vidmod.CheckPass || req.Pass == vidmod.Free { ids := make([]string, 0, len(req.Infos)) for _, info := range req.Infos { ids = append(ids, info.ID) } if err := CheckShelvingByIDs(parseObjectIDs(ids)); err != nil { return stderr.ContentSensitiveHit, err.Error() } } var cnt int64 = 0 var mergingCnt, convertingCnt, mergeErrorCnt, convertErrorCnt int64 for _, info := range req.Infos { oid, err := primitive.ObjectIDFromHex(info.ID) if err != nil { log.Error("UpdateVids error", log.Any("req", req), log.E(err)) continue } //非图集才需要同步 isCover, err := vidmod.IsNewsCover(info.ID) if err != nil { return stderr.ErrDbQueryError, nil } if req.Pass == 3 || req.Pass == 1 { if !isCover { if code := SyncFileFromFs(info.ID); code != stderr.UpLoadFileComplete { switch code { case stderr.FileMerging: mergingCnt++ case stderr.FileConverting: convertingCnt++ case stderr.ErrMergeFile: mergeErrorCnt++ case stderr.ErrConvertFile: convertErrorCnt++ } continue } } } if req.Pass == 2 { if !isCover { _ = SyncFileFromFsForNoPassed(info.ID) } } set := make(bson.M) set["status"] = req.Pass set["coins"] = info.Coins if req.Pass == vidmod.Free { set["status"] = vidmod.CheckPass set["coins"] = 0 } now := time.Now() set["reason"] = info.Reason set["freeTime"] = info.FreeTime set["updatedAt"] = now set["reviewAt"] = now set["reviewAccount"] = manager // 查询视频信息 videoInfo, err := vidmod.GetVideoInfo(oid.Hex()) if err != nil { return stderr.ErrDbQueryError, nil } if err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Status, BeforeContent: fmt.Sprintf("%v", videoInfo.Status), AfterContent: fmt.Sprintf("%v", req.Pass), CreatedUser: manager, }); err != nil { return stderr.ErrDbInsertError, nil } if err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Remark, BeforeContent: fmt.Sprintf("%v", ""), AfterContent: fmt.Sprintf("%v", info.Reason), CreatedUser: manager, }); err != nil { return stderr.ErrDbInsertError, nil } if set["status"] == vidmod.CheckPass { tagSort := make(bson.M, len(videoInfo.Tags)) for _, v := range videoInfo.Tags { tagSort[v.Hex()] = 0 } set["tagSort"] = tagSort } _, err = vidmod.UpdateVideoByMapCond(oid, set) if err != nil { continue } cnt++ } or := vidmod.OperateResult{Count: cnt, MergingCnt: mergingCnt, ConvertingCnt: convertingCnt, MerErrCnt: mergeErrorCnt, ConvertErrCnt: convertErrorCnt} return stderr.Success, or } // PassVid 通过审核 func PassVid(req vidmod.BatchReq, manager string) (code stderr.Code, data interface{}) { var mergingCnt, convertingCnt, mergeErrorCnt, convertErrorCnt int64 if req.Pass != 1 && req.Pass != 2 && req.Pass != 3 { log.Error("PassVid error", log.Any("req", req)) return stderr.ErrParamError, nil } // 上架(含直接通过 / 通过并免费)前置敏感词校验 if req.Pass == vidmod.CheckPass || req.Pass == vidmod.Free { if err := CheckShelvingByIDs(parseObjectIDs(req.IDs)); err != nil { return stderr.ContentSensitiveHit, err.Error() } } oids := make([]primitive.ObjectID, 0, len(req.IDs)) for _, id := range req.IDs { oid, err := primitive.ObjectIDFromHex(id) if err != nil { log.Error("PassVid2 error", log.Any("req", req), log.E(err)) continue } //非图集才需要同步 info, err := vidmod.GetVideoInfo(id) if err != nil { return stderr.ErrDbQueryError, nil } if req.Pass == 3 || req.Pass == 1 { if !info.IsCover() { if code = SyncFileFromFs(id); code != stderr.UpLoadFileComplete { switch code { case stderr.FileMerging: mergingCnt++ case stderr.FileConverting: convertingCnt++ case stderr.ErrMergeFile: mergeErrorCnt++ case stderr.ErrConvertFile: convertErrorCnt++ } log.Info(fmt.Sprintf("check vid pass code[%v]", code)) continue } } } if req.Pass == 2 { if !info.IsCover() { _ = SyncFileFromFsForNoPassed(id) } } //此行代码 明白意思 再移动顺序,没有明白则,不要随便移动 oids = append(oids, oid) tagSort := make(bson.M, len(info.Tags)) for _, v := range info.Tags { tagSort[v.Hex()] = 0 } _, err = vidmod.PassVids(oid, req.Pass, req.Reason, manager, tagSort) if err != nil { return stderr.ErrDbUpdateError, nil } // 完成发布任务 _ = taskser.CompleteDailyTask(nil, info.PublisherID, dailytaskmod.DailyPublish) } if len(oids) > 0 { err := operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Status, //BeforeContent: fmt.Sprintf("%v", videoInfo.Status), AfterContent: fmt.Sprintf("%v", req.Pass), CreatedUser: manager, }) if err != nil { return stderr.ErrDbInsertError, nil } err = operationlogmod.Insert(operationlogmod.OperationLog{ ID: primitive.NewObjectID(), OperationType: constant.Media_Edit_Remark, BeforeContent: fmt.Sprintf("%v", ""), AfterContent: fmt.Sprintf("%v", req.Reason), CreatedUser: manager, }) if err != nil { return stderr.ErrDbInsertError, nil } } or := vidmod.OperateResult{Count: 1, MergingCnt: mergingCnt, ConvertingCnt: convertingCnt, MerErrCnt: mergeErrorCnt, ConvertErrCnt: convertErrorCnt} return stderr.Success, or } // PushVid 推送视频 func PushVid(req pushmod.AddReq) (code stderr.Code, data interface{}) { oid, err := primitive.ObjectIDFromHex(req.VideoID) if err != nil { log.Error("PushVid error", log.Any("req", req), log.E(err)) return stderr.ErrParamError, nil } doc := pushmod.PushModel{ VideoID: oid, Title: req.Title, Type: vidmod.SP, CreatedAt: time.Now(), } if err = pushmod.InsertOne(doc); err != nil { return stderr.ErrDbInsertError, "" } return stderr.Success, nil } // PushVidList 获取推送列表 func PushVidList(req pushmod.ListReq) (code stderr.Code, data interface{}) { cond := bson.M{} if len(req.VideoID) != 0 { oid, err := primitive.ObjectIDFromHex(req.VideoID) if err != nil { log.Error("PushVidList error", log.Any("req", req), log.E(err)) return stderr.ErrParamError, nil } cond = bson.M{"videoID": oid} } sort := bson.D{{Key: "createdAt", Value: -1}} infos, _, total, err := pushmod.GetPushList(req.PageNumber, req.PageSize, cond, sort) if err != nil { return stderr.ErrDbQueryError, nil } return stderr.Success, pushmod.ListResp{VInfos: infos, Total: total} } // DeletePushVid 删除推送视频 func DeletePushVid(ids []string) (code stderr.Code, data interface{}) { oids := make([]primitive.ObjectID, 0, len(ids)) for _, id := range ids { oid, err := primitive.ObjectIDFromHex(id) if err != nil { log.Error("DeletePushVid error", log.Any("ids", ids), log.E(err)) continue } oids = append(oids, oid) } if oids == nil { return stderr.ErrParamError, nil } cnt, err := pushmod.Delete(bson.M{"videoID": bson.M{"$in": oids}}) if err != nil { return stderr.ErrDbDeleteError, "" } return stderr.Success, pushmod.OperateResult{Count: cnt} } // SyncFileFromFs 从文件服务器同步文件信息未审核通过 func SyncFileFromFsForNoPassed(vID string) (code stderr.Code) { v, _ := vidmod.GetVideoInfo(vID) resp, err := updownloadser.PullFileInfo(v.SourceID) if err != nil { return stderr.ErrConnectToFs } var resolution, actor, direction, quality string oid, _ := primitive.ObjectIDFromHex(resp.Data.ID) sourceID := resp.Data.ID playTime := resp.Data.PlayTime freeTime := vidmod.GetFreeTime(resp.Data.PlayTime) if v.FreeTime != 0 && freeTime != v.FreeTime { freeTime = v.FreeTime } seriesCover := resp.Data.CoverImg width := resp.Data.Width height := resp.Data.Height if width != 0 && height != 0 { resolution = strconv.FormatInt(int64(resp.Data.Width), 10) + "*" + strconv.FormatInt(int64(resp.Data.Height), 10) identifi := common.IdentifiVideoInfo(int64(resp.Data.Width), int64(resp.Data.Height)) direction = string(identifi.Direction) quality = string(identifi.Quality) } md5 := resp.Data.CheckSum if len(resp.Data.Actors) > 0 { actor = strings.Join(resp.Data.Actors, ",") } size := resp.Data.Size filename := resp.Data.Filename via := resp.Data.Via ratio := resp.Data.Ratio update := vidmod.WebVideoUpdateDoc{ ID: oid, SourceID: &sourceID, PlayTime: &playTime, FreeTime: &freeTime, SeriesCover: &seriesCover, Resolution: &resolution, Width: &width, Height: &height, MD5: &md5, Actor: &actor, Size: &size, Filename: &filename, Via: &via, Ratio: &ratio, Direction: &direction, Quality: &quality, } if resp.Code == stderr.UpLoadFileComplete { if v.Cover == "" { cover := resp.Data.FieldNameFs + "-1.jpg" update.Cover = &cover } if v.CoverThumb == "" { coverThumb := resp.Data.FieldNameFs + "-2.jpg" update.CoverThumb = &coverThumb } } _, _ = vidmod.UpdateVideoResolutionPlayTime(v.ID, update) return resp.Code } // SyncFileFromFs 从文件服务器同步文件信息 func SyncFileFromFs(vID string) (code stderr.Code) { v, _ := vidmod.GetVideoInfo(vID) resp, err := updownloadser.PullFileInfo(v.SourceID) if err != nil { return stderr.ErrConnectToFs } if resp.Code == stderr.UpLoadFileComplete { oid, _ := primitive.ObjectIDFromHex(resp.Data.ID) sourceID := resp.Data.ID playTime := resp.Data.PlayTime freeTime := vidmod.GetFreeTime(resp.Data.PlayTime) if v.FreeTime != 0 && freeTime != v.FreeTime { freeTime = v.FreeTime } seriesCover := resp.Data.CoverImg resolution := strconv.FormatInt(int64(resp.Data.Width), 10) + "*" + strconv.FormatInt(int64(resp.Data.Height), 10) width := resp.Data.Width height := resp.Data.Height identifi := common.IdentifiVideoInfo(int64(resp.Data.Width), int64(resp.Data.Height)) direction := string(identifi.Direction) quality := string(identifi.Quality) md5 := resp.Data.CheckSum actor := strings.Join(resp.Data.Actors, ",") size := resp.Data.Size filename := resp.Data.Filename via := resp.Data.Via ratio := resp.Data.Ratio update := vidmod.WebVideoUpdateDoc{ ID: oid, SourceID: &sourceID, PlayTime: &playTime, FreeTime: &freeTime, VideoCover: &seriesCover, Resolution: &resolution, Width: &width, Height: &height, MD5: &md5, Actor: &actor, Size: &size, Filename: &filename, Via: &via, Ratio: &ratio, Direction: &direction, Quality: &quality, } if v.Cover == "" { cover := resp.Data.FieldNameFs + "-1.jpg" update.Cover = &cover } if v.CoverThumb == "" { coverThumb := resp.Data.FieldNameFs + "-2.jpg" update.CoverThumb = &coverThumb } if v.SeriesCover == nil || len(v.SeriesCover) <= 0 { update.SeriesCover = &seriesCover } _, _ = vidmod.UpdateVideoResolutionPlayTime(v.ID, update) } return resp.Code } // SyncBaseInfoFromFs 从文件服务器同步基本信息 func SyncBaseInfoFromFs(vID string) (code stderr.Code) { oid, _ := primitive.ObjectIDFromHex(vID) resp, err := updownloadser.PullFileInfo(vID) if err != nil { return stderr.ErrConnectToFs } if resp.Code == stderr.UpLoadFileComplete { update := vidmod.WebSyncServerFileEdit{ PlayTime: resp.Data.PlayTime, Resolution: strconv.FormatInt(int64(resp.Data.Width), 10) + "*" + strconv.FormatInt(int64(resp.Data.Height), 10), MD5: resp.Data.CheckSum, Size: resp.Data.Size, Ratio: resp.Data.Ratio, } set, _ := common.ToBsonM(update) if _, err = vidmod.UpdateVideoByMapCond(oid, set); err != nil { return stderr.Failure } } return resp.Code } // 查看视频转码状态 func ViewVideoFromFs(id string) (code stderr.Code, data interface{}) { resp, err := updownloadser.PullFileInfo(id) if err != nil { return stderr.ErrConnectToFs, nil } back := make(map[string]interface{}, 10) back["playTime"] = resp.Data.PlayTime back["size"] = resp.Data.Size back["width"] = resp.Data.Width back["height"] = resp.Data.Height back["status"] = resp.Data.Status back["ratio"] = resp.Data.Ratio fileName := resp.Data.Filename fieldNameFs := resp.Data.FieldNameFs back["fileName"] = filepath.Join(filepath.Dir(fieldNameFs), fileName) switch resp.Data.Status { case vidmod.UploadLoadingToFs: back["statusDesc"] = "正在上传中" case vidmod.UploadCompleted: back["statusDesc"] = "上传完成" case vidmod.FileUploadError: back["statusDesc"] = "上传错误" case vidmod.Merging: back["statusDesc"] = "正在合并中" case vidmod.MergeCompleted: back["statusDesc"] = "合并完成" case vidmod.MergeError: back["statusDesc"] = "合并失败" case vidmod.Converting: back["statusDesc"] = "正在转码中" case vidmod.ConvertError: back["statusDesc"] = "转码失败" case vidmod.ConvertCompleted, vidmod.Completed: back["statusDesc"] = "完成状态" } return stderr.Success, back } type ( GetVideoListByTagReq struct { TagID string `form:"tagID" json:"tagID" binding:"required"` // 标签ID SortType int `form:"sortType" json:"sortType" binding:"required"` // 排序类型 1:最新上架 2:最多收藏 3:本月最热 4:最多观看 NewsType string `json:"newsType" form:"newsType" binding:"-"` // 帖子类型: SP、SHORT、COVER commod.Page } GetVideoListByTagRep struct { List []*vidmod.VideoInfo `json:"list"` // 视频列表 HasNext bool `json:"hasNext"` // 是否还有下一页 } ) // GetVideoListByTag 通过标签id获取视频列表信息 func (req *GetVideoListByTagReq) GetVideoListByTag() (rep GetVideoListByTagRep, err error) { excludedModuleIDs, err := excludedTagSearchModuleIDs() if err != nil { return rep, err } redisKey := tagSearchCacheKey( redisconst.GetTagSortVideoKey(req.TagID, req.NewsType, req.SortType, req.Skip(), req.Limit()), excludedModuleIDs, ) s, err := appg.Redis.Get(redisKey) if err != nil { log.Error("GetVideoListByTag redis get error", log.E(err), log.Any("req", req)) } if s != nil && *s != "" { err = json.Unmarshal([]byte(*s), &rep) if err == nil { return rep, nil } log.Error("GetVideoListByTag json unmarshal error", log.E(err), log.Any("val", *s)) } // -------- tid, _ := primitive.ObjectIDFromHex(req.TagID) filter := bson.M{ "tags": tid, "status": 1, //状态,0 未审核 1通过 2审核失败 3视为免费 默认为0 } applyTagSearchVisibilityFilter(filter, excludedModuleIDs) if req.NewsType != "" { filter["newsType"] = req.NewsType } // 本项目用到以下3个 //1: 最新:按最新上架时间 //2: 收藏:按总收藏量排序 //4: 观看:播放量最多的数据 var sortCond bson.D switch req.SortType { case 2: sortCond = bson.D{{Key: "collectCount", Value: -1}, {Key: "reviewAt", Value: -1}} case 3: filter["reviewAt"] = bson.M{"$gte": time.Now().AddDate(0, -3, 0)} sortCond = bson.D{{Key: "likeCount", Value: -1}, {Key: "reviewAt", Value: -1}} case 4: sortCond = bson.D{{"playCount", -1}, {Key: "reviewAt", Value: -1}} case 5: sortCond = bson.D{{Key: "hot", Value: -1}, {Key: "reviewAt", Value: -1}} case 1: // 最新 fallthrough default: sortCond = bson.D{{Key: "reviewAt", Value: -1}} } opt := options.Find(). SetSkip(int64(req.Skip())). SetLimit(int64(req.Limit())). SetProjection(bson.M{"chosenDate": 0}). SetSort(sortCond) out, next, err := vidmod.FindList(filter, opt) if err != nil { return rep, err } rep.HasNext = next rep.List = vidhelpser.EncodeVideoInfoNoUID(out) go func() { b, _ := json.Marshal(rep) _ = appg.Redis.Set(redisKey, string(b), redisconst.GetTagSortVideoExpire()) }() return rep, nil } // BatchAuditVidTimeOnlineInfo 批量新增自动上架视频信息 func BatchAuditVidTimeOnlineInfo(req vidtimeonlinemod.BatchAuditReq, price int, manager string) (code stderr.Code, data interface{}) { if len(req.Vids) > 20 { return stderr.BatchAuditLengthTooLong, nil } // 定时上架前置敏感词校验 if err := CheckShelvingByIDs(req.Vids); err != nil { return stderr.ContentSensitiveHit, err.Error() } //now := time.Now() //if !req.OnlineTime.After(now) { // req.OnlineTime = now //} // 获取专题 section, err := modulesectionmod.GetBySectionByID(req.SectionID) if err != nil { return stderr.ErrDbQueryError, nil } var adds []vidtimeonlinemod.VidTimeOnlineModel var updateIds []primitive.ObjectID var reErr stderr.Code var vIds []primitive.ObjectID reErr = stderr.Success for _, id := range req.Vids { vid, _ := primitive.ObjectIDFromHex(id.Hex()) //查询视频信息 vidInfo, err := vidmod.FindOneByFilter(bson.M{"_id": vid}) if err != nil { log.Error("AddVidTimeOnlineInfo vidmod.FindOneByFilter error", log.Any("req", req), log.Any("err", err)) reErr = stderr.ErrDbQueryError break } if vidInfo == nil || vidInfo.ID.IsZero() { reErr = stderr.VidNotExists break } // 取消限制长视频 //if vidInfo.PlayTime > 300 && vidInfo.PublisherID > 150000 { // return stderr.OnlyOfficialVideo, nil //} // 自动上架支持类型短视频、帖子、长视频 if vidInfo.NewsType != vidmod.SP && vidInfo.NewsType != vidmod.COVER && vidInfo.NewsType != vidmod.SHORT { reErr = stderr.VideoTypeDissatisfy return reErr, nil } if len(vidInfo.Tags) <= 0 { return stderr.VideoTagNoExists, nil } //查询上传用户信息 publisherInfo, err1 := usermod.FindUserByUID(vidInfo.PublisherID) if err1 != nil { log.Error("AddVidTimeOnlineInfo usermod.FindUserByUID error", log.Any("req", req), log.Any("err", err), log.Any("publisherID", vidInfo.PublisherID)) //reErr = stderr.ErrDbQueryError continue } // 查询该帖子是否已经在定时上架列表,如果存在则更新 videos, err := vidtimeonlinemod.GetByVidIds([]primitive.ObjectID{vidInfo.ID}) if err != nil { return stderr.ErrDbQueryError, nil } vIds = append(vIds, vidInfo.ID) if len(videos) > 0 { // 不需要添加只需要修改上线时间 updateIds = append(updateIds, vidInfo.ID) } else { add := vidtimeonlinemod.VidTimeOnlineModel{ ID: primitive.NewObjectID(), PublisherID: vidInfo.PublisherID, Portrait: publisherInfo.Portrait, VidType: vidInfo.NewsType, VidId: vidInfo.ID, VidTitle: vidInfo.Title, Cover: vidInfo.Cover, CreatedAt: time.Now(), ReviewAccount: manager, OnlineTime: req.OnlineTime, SectionId: req.SectionID, } if vidInfo.NewsType == vidmod.SP || vidInfo.NewsType == vidmod.SHORT { add.SourceURL = vidInfo.SourceURL } else { add.SeriesCover = vidInfo.SeriesCover } adds = append(adds, add) } reErr = stderr.Success } if reErr != stderr.Success { return reErr, nil } if len(adds) > 0 { err := vidtimeonlinemod.InsertMany(adds) if err != nil { log.Error("AddVidTimeOnlineInfo vidTimeOnlineMod InsertMany error", log.Any("err", err)) return stderr.ErrDbInsertError, nil } } if len(updateIds) > 0 { updateData := bson.M{ "onlineTime": req.OnlineTime, } if !section.ID.IsZero() { updateData["sectionId"] = section.ID } err := vidtimeonlinemod.UpdateMany(updateIds, updateData) if err != nil { log.Error("AddVidTimeOnlineInfo vidTimeOnlineMod UpdateMany error", log.Any("err", err)) return stderr.ErrDbInsertError, nil } } if len(vIds) > 0 { // 批量更新视频信息 update := bson.M{} update["updatedAt"] = time.Now() update["status"] = vidmod.OnlinePass // 定时贴上架标记 applyScheduledVideoModuleID(update, section.SubModuleID) if price >= 0 { update["coins"] = price } result, err := vidmod.UpdateManyVideo(vIds, update) if err != nil { return stderr.Failure, err } if result == 0 { return stderr.Failure, errors.New("video update failed") } } return stderr.Success, nil } func applyScheduledVideoModuleID(update bson.M, moduleID primitive.ObjectID) { if !moduleID.IsZero() { update["mId"] = moduleID.Hex() } } // GetTimeOnlineVidList 获取定时上线视频列表 func GetTimeOnlineVidList(req *vidtimeonlinemod.ListReq) (code stderr.Code, data interface{}) { var err error cond := bson.M{} res := gin.H{"vidModels": "", "total": 0} if req.UID > 0 { cond["publisherID"] = req.UID } if req.VidId != "" { vid, _ := primitive.ObjectIDFromHex(req.VidId) cond["vidId"] = vid } if req.Tag != nil { id, err := tagmod.GetTagIDByName(*req.Tag) if err != nil { return stderr.ErrDbQueryError, res } cond["tags"] = id } vidModels, total, err := vidtimeonlinemod.GetAllByPage(cond, req.Page) if err != nil { return stderr.ErrDbQueryError, res } var ( tags []primitive.ObjectID resp []vidtimeonlinemod.VidTimeOnlineModelResp ) if len(vidModels) > 0 { for _, v := range vidModels { tags = append(tags, v.Tags...) } } else { return stderr.Success, res } mTags := vidhelp.GetTagsByIDs2Map(tags) for _, v := range vidModels { t := vidhelp.GetPartTags(v.Tags, mTags) resp = append(resp, vidtimeonlinemod.VidTimeOnlineModelResp{ ID: v.ID, Tags: t, VidType: v.VidType, PublisherID: v.PublisherID, Portrait: v.Portrait, VidTitle: v.VidTitle, VidId: v.VidId, OnlineSort: v.OnlineSort, SourceURL: v.SourceURL, Cover: v.Cover, SeriesCover: v.SeriesCover, OnlineTime: v.OnlineTime, CreatedAt: v.CreatedAt, ReviewAccount: v.ReviewAccount, SectionId: v.SectionId, }) } res["total"] = total res["vidModels"] = resp return stderr.Success, res } // DeleteTimeOnlineVid 删除定时上线视频 func DeleteTimeOnlineVid(ids []primitive.ObjectID) (code stderr.Code) { list, err := vidtimeonlinemod.GetByVidIds(ids) if err != nil { return stderr.ErrDbQueryError } vids := []primitive.ObjectID{} for _, v := range list { vids = append(vids, v.VidId) } err = vidtimeonlinemod.DeleteByIds(ids) if err != nil { return stderr.ErrDbDeleteError } // 把帖子状态更新为下架 err = vidmod.UpdateMany(bson.M{"_id": bson.M{"$in": vids}}, bson.M{"$set": bson.M{"status": vidmod.CheckFailure}}) if err != nil { return stderr.ErrDbDeleteError } return stderr.Success } // UpdateVidNewsType 批量修改帖子类型 func UpdateVidNewsType(req vidmod.BatchUpdateVidNewsTypeReq) (code stderr.Code) { newsTypeList := []string{ vidmod.SP, vidmod.SHORT, vidmod.PIC, vidmod.COVER, vidmod.AD_COVER, vidmod.AD_SP, vidmod.SEED_LINK, } if !slice.Contains(newsTypeList, req.NewsType) { return stderr.ErrParamError } err := vidmod.UpdateVideosByMapCond(req.Ids, bson.M{ "newsType": req.NewsType, }) if err != nil { log.Error("UpdateVideosByMapCond error", log.Any("req", req), log.E(err)) } return stderr.Success } // UpdateVidTags 批量更新视频信息 func UpdateVidTags(req vidmod.EditVideTagsReq) (code stderr.Code) { if len(req.Ids) <= 0 || len(req.Tags) <= 0 { return stderr.ErrParamError } ids := make([]primitive.ObjectID, 0, len(req.Ids)) for _, id := range req.Ids { oid, err := primitive.ObjectIDFromHex(id) if err != nil { log.Error("UpdateVids error", log.Any("req", req), log.E(err)) continue } ids = append(ids, oid) } // 获取相关视频 videoList, err := vidmod.GetVideoListByIDsNoStatus(ids) if err != nil { log.Error(fmt.Sprintf("UpdateVidTags vidmod.GetVideoListByIDsNoStatus err:%v, ids:%v", err, req.Ids)) return } var newTags []primitive.ObjectID var oldTags []primitive.ObjectID update := bson.M{"updatedAt": time.Now()} if req.Tags != nil { newTags = vidhelp.AddTags(req.Ids[0], req.Tags) } for _, v := range videoList { oldTags = v.Tags // 获取旧的tags if req.Overwrite { update["tags"] = newTags } else { update["tags"] = sli.RemoveRep(append(v.Tags, newTags...)) } // 修改视频tag _, err = vidmod.UpdateOneByID(v.ID, update) if err != nil { log.Error(fmt.Sprintf("UpdateVidTags vidmod.UpdateOneByID err:%v, id:%v, update: %v", err, v.ID, update)) continue } // 修改旧tag与新tag的视频数量 updateTagVideoCnt(oldTags, newTags) } return stderr.Success }