package mediaser import ( "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/db" "91porn-server/common/file" "91porn-server/common/httputil" "91porn-server/common/log" "91porn-server/common/redis" sli "91porn-server/common/slice" "91porn-server/common/stderr" topser "91porn-server/common/top" topasist "91porn-server/common/top/asistant" "91porn-server/models/commod" "91porn-server/models/v/mediacontentmod" "91porn-server/models/v/mediatagmod" "91porn-server/models/v/moduleconfmod" "91porn-server/models/v/modulesectionmod" "91porn-server/models/v/vidmod" "91porn-server/web/webg" "bytes" "errors" "fmt" "strings" "time" "go.mongodb.org/mongo-driver/mongo/options" "91porn-server/models/v/mediamod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) type WebListReq struct { ID *string `form:"id" bson:"id,omitempty"` // ID Title *string `form:"title" json:"title"` // 帖子标题 Kind *int `form:"kind" bson:"kind,omitempty"` // 帖子种类 1:动漫,2:漫画 UpdateStatus *int `form:"updateStatus" bson:"updateStatus,omitempty"` // 连载状态 Type *string `form:"type" bson:"type,omitempty"` // 类型 "video":视频,"image":图片 Status *int `form:"status" bson:"status,omitempty"` // 是否开启 Permission *int `form:"permission" json:"permission"` // 观看权限 Start *time.Time `form:"start" json:"start"` // 开始时间 End *time.Time `form:"end" json:"end"` // 结束时间 Tag *string `form:"tag" json:"tag"` // 标签名称 SID *string `form:"sId" json:"sId"` // 专题ID MID *string `form:"mId" json:"mId"` // 模块ID MediaType *string `form:"mediaType" json:"mediaType"` // 媒体类型 MediaSubType *int `form:"mediaSubType" json:"mediaSubType"` // 媒体子类型(小说使用)0文本小说 1-有声小说 MediaCenterID *uint `form:"mediaCenterId" json:"mediaCenterId"` // 媒资库资源ID PageNumber uint64 `form:"pageNumber" json:"pageNumber" binding:"required,min=1"` // 当前页 PageSize uint64 `form:"pageSize" json:"pageSize" binding:"required,min=1,max=100"` // 每页条数 } type WebListRes struct { Total int64 `json:"total"` List []*mediamod.Media `json:"list"` } func (q *WebListReq) Filter() bson.M { var query = bson.M{} if q.Title != nil { i := make(map[string]string) i["$regex"] = *q.Title i["$options"] = "i" query["title"] = i } if q.Status != nil { query["status"] = *q.Status } if q.Type != nil { query["mediaType"] = *q.Type } if q.Permission != nil { query["permission"] = *q.Permission } if q.Tag != nil { //tag, _ := mediatagmod.FindOneTagByName(*q.Tag) //query["tags"] = tag.ID tagIds, _ := mediatagmod.FindTagIdsByName(*q.Tag) if len(tagIds) > 0 { query["tags"] = bson.M{"$in": tagIds} } } if q.Start != nil && q.End != nil { i := make(map[string]time.Time) i["$gte"] = *q.Start i["$lt"] = *q.End query["createdAt"] = i } if q.ID != nil { id, _ := primitive.ObjectIDFromHex(*q.ID) query["_id"] = id } if q.SID != nil { id, _ := primitive.ObjectIDFromHex(*q.SID) query["sId"] = id } if q.MID != nil { id, _ := primitive.ObjectIDFromHex(*q.MID) query["mId"] = id } if q.MediaType != nil { query["mediaType"] = *q.MediaType } if q.MediaSubType != nil { query["mediaSubType"] = *q.MediaSubType } if q.Kind != nil { query["kind"] = *q.Kind } if q.UpdateStatus != nil && *q.UpdateStatus > 0 { query["updateStatus"] = *q.UpdateStatus } if q.MediaCenterID != nil { query["mediaCenterId"] = *q.MediaCenterID } query["isDelete"] = false return query } func (q *WebListReq) Options() *options.FindOptions { sort := bson.M{"createdAt": -1} if q.SID != nil { sort = bson.M{"sectionSort": -1} } return options.Find().SetSkip(int64((q.PageNumber - 1) * q.PageSize)).SetLimit(int64(q.PageSize)).SetSort(sort) } // GetList 获取列表 func (q *WebListReq) GetList() (res *WebListRes, err error) { var out = &WebListRes{} count, err := mediamod.QueryAllCount(q.Filter()) if err != nil { return nil, err } if count == 0 { out.List = []*mediamod.Media{} out.Total = 0 return out, nil } out.Total = count out.List, err = mediamod.QueryAllList(q.Filter(), q.Options()) if err != nil { log.Error(fmt.Sprintf("mediamod queryAllList error:%+v:", err)) return nil, err } if len(out.List) > 0 { var tags []primitive.ObjectID for _, m := range out.List { if len(m.Tags) > 0 { tags = append(tags, m.Tags...) } } if len(tags) > 0 { toMap, err := mediatagmod.FindFilterToMap(bson.M{"_id": bson.M{"$in": tags}}) if err != nil { return out, stderr.ErrDbQueryError } if toMap != nil { for i, v := range out.List { var tagObjs []mediamod.TagDetail if len(v.Tags) > 0 { for _, id := range v.Tags { if toMap[id] != nil { tagObjs = append(tagObjs, mediamod.TagDetail{ Id: id, Name: toMap[id].Name, }) } } } out.List[i].TagDetails = tagObjs out.List[i].CountPurchasesRate = float32(out.List[i].CountBrowse) / float32(out.List[i].CountView+1) } } } } return out, nil } // Export 导出 func (q *WebListReq) Export() (buff bytes.Buffer, code error) { count, err := mediamod.QueryAllCount(q.Filter()) if err != nil { return } if count == 0 { return } step := 900 totalPages := int(count)/step + 1 list := []*mediamod.WebExportMedia{} for page := 1; page <= totalPages; page++ { q.PageSize = uint64(step) q.PageNumber = uint64(page) res, err := mediamod.QueryAllList(q.Filter(), q.Options()) if err != nil { log.Error(fmt.Sprintf("collectionmod queryAllList error:%+v:", err)) continue } // 没有查到数据直接停止 if len(res) == 0 { break } var tags []primitive.ObjectID for _, m := range res { if len(m.Tags) > 0 { tags = append(tags, m.Tags...) } } tags = sli.RemoveRepObjectID(tags) toMap, err := mediatagmod.FindFilterToMap(bson.M{"_id": bson.M{"$in": tags}}) if err != nil { return } for _, v := range res { item := &mediamod.WebExportMedia{ ID: v.ID, Title: v.Title, Kind: mediamod.KindName(v.Kind), Direction: mediamod.DirectionName(v.Direction), TotalEpisode: v.TotalEpisode, UpdateStatus: mediamod.UpdateStatusName(v.UpdateStatus), Status: mediamod.StatusName(v.Status), ModuleName: v.ModuleName, SectionName: v.SectionName, MediaType: mediamod.MediaTypeName(v.MediaType), FreeTime: v.FreeTime, Price: v.Price, CountBrowse: v.CountBrowse, CountCollect: v.CountCollect, CountComment: v.CountComment, CountLike: v.CountLike, CountPurchases: v.CountPurchases, CountView: v.CountView, Permission: mediamod.PermissionName(v.Permission), PermissionIconHide: v.PermissionIconHide, Summary: v.Summary, UpdatedAct: v.UpdatedAct, CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05"), UpdateTime: v.UpdateTime.Format("2006-01-02 15:04:05"), } tagNameList := []string{} for _, tagId := range v.Tags { tag, ok := toMap[tagId] if !ok { continue } tagNameList = append(tagNameList, tag.Name) } item.Tags = strings.Join(tagNameList, ",") list = append(list, item) } fmt.Println(len(list)) } excelFile := file.WriteStruct2Xlsx("动漫列表", list) defer excelFile.Close() _ = excelFile.DeleteSheet("Sheet1") _, _ = excelFile.WriteTo(&buff) return buff, nil } type WebFindMediaReq struct { ID string `form:"id" bson:"id,omitempty"` // ID } type WebFindMediaRes struct { MediaTitle string `json:"mediaTitle" bson:"mediaTitle"` // 媒体标题 MediaType string `json:"mediaType" bson:"mediaType"` // 媒体类型 IsExist bool `json:"isExist" bson:"isExist"` // 是否存在 } func (q *WebFindMediaReq) Filter() bson.M { var query = bson.M{} id, _ := primitive.ObjectIDFromHex(q.ID) query["_id"] = id query["isDelete"] = false return query } // GetMediaByID 获取列表 func (q *WebFindMediaReq) GetMediaByID() (res *WebFindMediaRes, err error) { var out = &WebFindMediaRes{} data, err := mediamod.QueryMediaByID(q.Filter()) if err != nil { log.Error(fmt.Sprintf("mediamod QueryMediaByID error:%+v:", err)) return nil, err } if data == nil || data.ID.IsZero() { return nil, errors.New("media data is null") } if !data.ID.IsZero() { out.IsExist = true out.MediaTitle = data.Title out.MediaType = data.MediaType } else { out.IsExist = false } return out, nil } type WebCreateReq struct { SortCode int `json:"sortCode" form:"sortCode"` // 排序 Status int `json:"status" form:"status"` // 是否开启 Title string `json:"title" form:"title"` // 标题 HorizontalCover string `json:"horizontalCover" form:"horizontalCover"` // 横版封面 VerticalCover string `json:"verticalCover" form:"verticalCover"` // 竖版封面 Tags []primitive.ObjectID `json:"tags" form:"tags"` // 标签 Summary string `json:"summary" form:"summary"` // 简介 TotalEpisode int `json:"totalEpisode" form:"totalEpisode"` // 总集数 UpdateStatus int `json:"updateStatus" bson:"updateStatus"` // 更新状态 MediaType string `json:"mediaType" form:"mediaType"` // 媒体类型 MediaSubType mediamod.MediaSubType `json:"mediaSubType" form:"mediaSubType"` // 子类型 ,暂时只有小说使用 0-默认文本小说 1-有声小说 Kind int `json:"kind" form:"kind"` // 种类 (1、哩番,2、3D,3、同人动画,4、单行本,5、连载漫画,6、韩漫 ) PermissionIconHide bool `json:"permissionIconHide" form:"permissionIconHide"` // 售卖类型标识隐藏 Permission int `json:"permission" form:"permission"` // 收听权限 0:会员 1:金币购买 2:免费 Price int64 `json:"price" form:"price"` // 价格 Direction int64 `json:"direction" form:"direction"` // 排版方向 0横 1竖(冗余字段) FreeTime int `json:"freeTime" form:"freeTime"` // 免费试看时长 单位 s FreeEpisode int `json:"freeEpisode" form:"freeEpisode"` // 免费集数(前几集免费) Choice primitive.ObjectID `json:"choice" form:"choice"` // 精选 ChoiceSort int `json:"choiceSort" form:"choiceSort"` // 精选专题排序 IsActive bool `json:"isActive" form:"isActive"` // 是否激活 CountComment int64 `json:"countComment" form:"countComment"` // 评论数 CountPurchases int64 `json:"countPurchases" form:"countPurchases"` // 购买数 Style int `json:"style" form:"style"` // 对应五种风格背景颜色 CountBrowse int64 `json:"countBrowse" form:"countBrowse"` // 浏览数 CountCollect int64 `json:"countCollect" form:"countCollect"` // 收藏数 CountLike int64 `json:"countLike" form:"countLike"` // 喜欢数 CountView int64 `json:"countView" form:"countView"` // 展现数 Number int64 `json:"number" form:"number"` // 编号 MediaSize int `form:"mediaSize" json:"mediaSize"` // 媒体大小 SourceID string `form:"sourceID" json:"sourceID"` // 资源ID SourceURL string `form:"sourceURL" json:"sourceURL"` // 资源链接 AudioUrl string `json:"audioUrl" bson:"audioUrl"` // 有声小说地址 Author string `json:"author" bson:"author"` // 作者 Md5 string `form:"md5" json:"md5"` // md5 EpisodeNumber int `json:"episodeNumber" bson:"episodeNumber"` // 第几集 EpisodeNumberName string `form:"episodeNumberName" json:"episodeNumberName"` // 章节名称 ListenPermission int `json:"listenPermission" bson:"listenPermission"` // 收听权限 0:会员 1:金币购买 2:免费 SeriesCover []string `form:"seriesCover" json:"seriesCover"` // 封面 Filename string `form:"filename" json:"filename"` // 文件名称 PlayTime uint `form:"playTime" json:"playTime"` // 时长 Cover string `form:"cover" json:"cover"` // 子集的封面 } func (p *WebCreateReq) Validate() error { if p.MediaType != mediamod.MediaTypeDrama { return nil } p.Title = strings.TrimSpace(p.Title) p.HorizontalCover = strings.TrimSpace(p.HorizontalCover) p.VerticalCover = strings.TrimSpace(p.VerticalCover) if p.Title == "" || p.HorizontalCover == "" || p.VerticalCover == "" { return errors.New("短剧标题、横版封面和竖版封面不能为空") } if p.TotalEpisode < 1 { return errors.New("短剧总集数必须大于0") } if p.UpdateStatus != mediamod.Update && p.UpdateStatus != mediamod.Finish { return errors.New("短剧更新状态只支持1或2") } if p.Status != 0 { return errors.New("短剧创建后请先添加有效首集再上架") } if p.Kind != mediamod.Theater { return errors.New("短剧kind必须为8") } if p.Permission != 1 { return errors.New("短剧主记录permission必须为1") } if p.SortCode < 0 { return errors.New("短剧推荐排序不能小于0") } return nil } // Create 发布数据 func (p *WebCreateReq) Create(manager string) (code stderr.Code, vid primitive.ObjectID) { now := time.Now() if p.MediaType == mediamod.MediaTypeVideo { vid, _ = primitive.ObjectIDFromHex(p.SourceID) } else { vid = primitive.NewObjectID() } if p.UpdateStatus == 0 { p.UpdateStatus = 2 } data := mediamod.Media{ SortCode: p.SortCode, Status: p.Status, Title: p.Title, HorizontalCover: p.HorizontalCover, VerticalCover: p.VerticalCover, Tags: p.Tags, Summary: p.Summary, TotalEpisode: p.TotalEpisode, UpdateStatus: p.UpdateStatus, MediaType: p.MediaType, MediaSubType: p.MediaSubType, Kind: p.Kind, PermissionIconHide: p.PermissionIconHide, Permission: p.Permission, Price: p.Price, Direction: p.Direction, Style: p.Style, FreeTime: p.FreeTime, FreeEpisode: p.FreeEpisode, Choice: p.Choice, ChoiceSort: p.ChoiceSort, CountComment: p.CountComment, CountPurchases: 0, CountBrowse: 0, CountCollect: 0, CountLike: 0, CountView: 0, Number: 0, IsDelete: false, UpdatedAct: manager, UpdateTime: now, CreatedAt: now, } if p.MediaType == mediamod.MediaTypeImage { data.DefaultContent = mediamod.DefaultContent{ Id: vid, Cover: p.Cover, Name: p.EpisodeNumberName, UrlSet: p.SeriesCover, } } resultID := vid err := webg.VideoDB.Trans(func(t *db.MongoTool) error { // 创建数据 mediaId, err := mediamod.Insert(t, data) if err != nil { log.Error(fmt.Sprintf("SubmitBase mediamod.InsertMedia err:%v", err)) return stderr.ErrDbInsertError } if p.MediaType == mediamod.MediaTypeDrama { resultID = mediaId return nil } mc := mediacontentmod.MediaContent{ ID: vid, MediaID: mediaId, Cover: p.Cover, MediaType: p.MediaType, EpisodeNumber: p.EpisodeNumber, Name: p.EpisodeNumberName, ListenPermission: p.ListenPermission, Price: p.Price, IsActive: p.IsActive, } // 视频 if p.MediaType == mediamod.MediaTypeVideo { mc.Md5 = p.Md5 mc.VideoUrl = p.SourceURL mc.Status = 0 } // 音频 if p.MediaType == mediamod.MediaTypeText { mc.Md5 = p.Md5 mc.AudioUrl = p.AudioUrl mc.Author = p.Author mc.Status = 1 } // 图集 if p.MediaType == mediamod.MediaTypeImage { mc.UrlSet = p.SeriesCover } // 媒体内容新增 if _, err := mediacontentmod.Insert(t, mc); err != nil { log.Error(fmt.Sprintf("SubmitBase mediacontentmod Insert err:%v", err)) return stderr.ErrDbInsertError } return nil }) if err != nil { log.Error(fmt.Sprintf("Media SubmitBase Trans fail error:%+v:", err)) return stderr.Failure, vid } return stderr.Success, resultID } // 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) _, err := vidmod.ModifyMany(ids, *delSetDoc) if err != nil { return stderr.ErrDbUpdateError, nil } return stderr.Success, nil } // SendBaseInfo2AWs 上传基本文件信息到aws func SendBaseInfo2AWs(req mediamod.SendBaseInfo2AWsReq) (code stderr.Code) { tags := []string{} if len(req.Tags) > 0 { for _, t := range req.Tags { tags = append(tags, t.Hex()) } } info := vidmod.BaseVid4Aws{ ID: req.SourceID, Tags: tags, CheckSum: req.Md5, Title: req.Title, Actors: []string{}, AddedTime: time.Now().Format("2006-01-02"), PlayTime: uint64(req.PlayTime), Size: req.MediaSize, 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.Any("err", 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 } type WebUpdateReq struct { ID primitive.ObjectID `json:"id" binding:"required"` SortCode *int `json:"sortCode"` // 排序 MediaSubType *mediamod.MediaSubType `json:"mediaSubType" form:"mediaSubType"` // 子类型 ,暂时只有小说使用 0-默认文本小说 1-有声小说 Status *int `json:"status"` // 是否开启 1:上架,2:下架 Title *string `json:"title"` // 标题 HorizontalCover *string `json:"horizontalCover"` // 横版封面 VerticalCover *string `json:"verticalCover"` // 竖版封面 Tags *[]primitive.ObjectID `json:"tags"` // 标签 Summary *string `json:"summary"` // 简介 TotalEpisode *int `json:"totalEpisode"` // 总集数 UpdateStatus *int `json:"updateStatus" bson:"updateStatus"` // 更新状态 Kind *int `json:"kind"` // 种类(1、哩番,2、3D,3、同人动画,4、单行本,5、连载漫画,6、韩漫) Style *int `json:"style" form:"style"` // 对应五种风格背景颜色 PermissionIconHide *bool `json:"permissionIconHide"` // 售卖类型标识隐藏 Permission *int `json:"permission"` // 收听权限 0:会员 1:金币购买 2:免费 Price *int64 `json:"price"` // 价格 Direction *int64 `json:"direction"` // 排版方向 0横 1竖(冗余字段) SellType *int `json:"sellType"` // 商品售卖类型(0单集售卖 1整部售卖) FreeTime *int `json:"freeTime"` // 免费试看时长 单位 s FreeEpisode *int `json:"freeEpisode" form:"freeEpisode"` // 免费集数(前几集免费) SectionSort *int `json:"sectionSort" bson:"sectionSort"` // 专题排序 Choice *primitive.ObjectID `json:"choice"` // 精选 ChoiceSort *int `json:"choiceSort"` // 精选专题排序 IsActive *bool `json:"isActive"` // 是否激活 IsRemove *bool `json:"isRemove"` // 是否移出专题 CountComment *int64 `json:"countComment"` // 评论数 CountPurchases *int64 `json:"countPurchases"` // 购买数 CountBrowse *int64 `json:"countBrowse"` // 浏览数 CountCollect *int64 `json:"countCollect"` // 收藏数 CountLike *int64 `json:"countLike"` // 喜欢数 CountView *int64 `json:"countView"` // 展现数 Number *int64 `json:"number"` // 编号 } func (p *WebUpdateReq) mergedDramaMedia(current mediamod.Media) mediamod.Media { next := current if p.SortCode != nil { next.SortCode = *p.SortCode } if p.Status != nil { next.Status = *p.Status } if p.Title != nil { next.Title = strings.TrimSpace(*p.Title) } if p.HorizontalCover != nil { next.HorizontalCover = strings.TrimSpace(*p.HorizontalCover) } if p.VerticalCover != nil { next.VerticalCover = strings.TrimSpace(*p.VerticalCover) } if p.TotalEpisode != nil { next.TotalEpisode = *p.TotalEpisode } if p.UpdateStatus != nil { next.UpdateStatus = *p.UpdateStatus } if p.Kind != nil { next.Kind = *p.Kind } if p.Permission != nil { next.Permission = *p.Permission } return next } func validateDramaMediaFields(media mediamod.Media, requireHorizontalCover bool) error { if strings.TrimSpace(media.Title) == "" || strings.TrimSpace(media.VerticalCover) == "" { return errors.New("短剧标题和竖版封面不能为空") } if requireHorizontalCover && strings.TrimSpace(media.HorizontalCover) == "" { return errors.New("短剧横版封面不能为空") } if media.TotalEpisode < 1 { return errors.New("短剧总集数必须大于0") } if media.UpdateStatus != mediamod.Update && media.UpdateStatus != mediamod.Finish { return errors.New("短剧更新状态只支持1或2") } if media.Status != 0 && media.Status != 1 { return errors.New("短剧状态只支持0或1") } if media.Kind != mediamod.Theater { return errors.New("短剧kind必须为8") } if media.Permission != 1 { return errors.New("短剧主记录permission必须为1") } if media.SortCode < 0 { return errors.New("短剧推荐排序不能小于0") } return nil } func validateDramaMediaForPublish(media mediamod.Media, first mediacontentmod.MediaContent) error { if err := validateDramaMediaFields(media, true); err != nil { return err } return validateDramaFirstEpisodeForPublish(media, first) } func validateDramaFirstEpisodeForPublish(media mediamod.Media, first mediacontentmod.MediaContent) error { if first.ID.IsZero() || first.MediaID != media.ID || first.MediaType != mediamod.MediaTypeDrama || first.EpisodeNumber != 1 || first.IsDelete { return errors.New("短剧第1集不存在,不能上架整部短剧") } if !first.IsActive { return errors.New("短剧第1集未上架,不能上架整部短剧") } if first.Status != 1 { return errors.New("短剧第1集仍在转码,不能上架整部短剧") } if strings.TrimSpace(first.VideoUrl) == "" && strings.TrimSpace(first.H265Url) == "" { return errors.New("短剧第1集没有播放资源,不能上架整部短剧") } return nil } func (p *WebUpdateReq) validateDramaUpdate(current mediamod.Media) error { if current.MediaType != mediamod.MediaTypeDrama { return nil } next := p.mergedDramaMedia(current) if err := validateDramaMediaFields(next, false); err != nil { return err } if next.Status != 1 { return nil } first, err := mediacontentmod.GetInfoByCond(bson.M{ "mediaId": current.ID, "mediaType": mediamod.MediaTypeDrama, "episodeNumber": 1, "isDelete": false, }) if err != nil { return errors.New("短剧第1集不存在,不能上架整部短剧") } return validateDramaFirstEpisodeForPublish(next, first) } // 钻石榜 const ( diamondRanking = "DiamondRanking:%v" ) // Update 更新数据 func (p *WebUpdateReq) Update(manager string) error { media, err := mediamod.GetInfo(p.ID) if err != nil { return err } if err = p.validateDramaUpdate(media); err != nil { return err } data := make(map[string]interface{}) if p.MediaSubType != nil { data["mediaSubType"] = *p.MediaSubType } if p.SortCode != nil { data["sortCode"] = *p.SortCode } if p.Status != nil { data["status"] = *p.Status if media.Status != 1 && *p.Status == 1 { data["latestPublishedAt"] = time.Now() } } if p.UpdateStatus != nil { data["updateStatus"] = *p.UpdateStatus if *p.UpdateStatus == 0 { data["updateStatus"] = 2 } } if p.Title != nil { data["title"] = strings.TrimSpace(*p.Title) } if p.HorizontalCover != nil { data["horizontalCover"] = strings.TrimSpace(*p.HorizontalCover) } if p.VerticalCover != nil { data["verticalCover"] = strings.TrimSpace(*p.VerticalCover) } if p.Tags != nil { data["tags"] = *p.Tags } if p.SectionSort != nil { data["sectionSort"] = *p.SectionSort } if p.Summary != nil { data["summary"] = *p.Summary } if p.TotalEpisode != nil { data["totalEpisode"] = *p.TotalEpisode } if p.Kind != nil { data["kind"] = *p.Kind } if p.PermissionIconHide != nil { data["permissionIconHide"] = *p.PermissionIconHide } if p.Style != nil { data["style"] = *p.Style } if p.Permission != nil { data["permission"] = *p.Permission } if p.Price != nil { data["price"] = *p.Price } if p.Direction != nil { data["direction"] = *p.Direction } if p.SellType != nil { data["sellType"] = *p.SellType } if p.FreeTime != nil { data["freeTime"] = *p.FreeTime } if p.FreeEpisode != nil { data["freeEpisode"] = *p.FreeEpisode } if p.Choice != nil { data["choice"] = *p.Choice } if p.ChoiceSort != nil { data["choiceSort"] = *p.ChoiceSort } if p.CountComment != nil { data["countComment"] = *p.CountComment } if p.CountPurchases != nil { data["countPurchases"] = *p.CountPurchases } if p.CountBrowse != nil { data["countBrowse"] = *p.CountBrowse } if p.CountCollect != nil { data["countCollect"] = *p.CountCollect } if p.CountLike != nil { data["countLike"] = *p.CountLike } if p.CountView != nil { data["countView"] = *p.CountView } if p.Number != nil { data["number"] = *p.Number } if p.IsRemove != nil { data["mId"] = primitive.ObjectID{} data["sId"] = primitive.ObjectID{} } data["updatedAct"] = manager data["updateTime"] = time.Now() if _, err = mediamod.UpdateByID(nil, p.ID, data); err != nil { return err } if p.Status != nil && *p.Status != 1 { // 将它从排行榜上移除 topasist.Remove(topser.TypeMedia(media.MediaType), media.ID.Hex()) key := fmt.Sprintf(diamondRanking, media.MediaType) redis.Handler.ZRem(key, media.ID.Hex()) } return nil } type WebBatchUpdateReq struct { IDS []string `json:"ids" binding:"required"` // IDS SID *primitive.ObjectID `json:"sId" bson:"sId,omitempty"` // 专题ID IsActive *bool `json:"isActive" bson:"isActive,omitempty"` // 上下架 Price *int64 `json:"price" bson:"price"` // 价格 Permission *int `json:"permission" bson:"permission"` // 收听权限 0:会员 1:金币购买 2:免费 Tags *[]primitive.ObjectID `json:"tags"` // 标签 FreeEpisode *int `json:"freeEpisode" form:"freeEpisode"` // 免费集数(前几集免费) } func (p *WebBatchUpdateReq) Filter() []primitive.ObjectID { ids, _ := p.ValidatedIDs() return ids } func (p *WebBatchUpdateReq) ValidatedIDs() ([]primitive.ObjectID, error) { if len(p.IDS) == 0 || len(p.IDS) > 100 { return nil, errors.New("ids数量必须在1到100之间") } ids := make([]primitive.ObjectID, 0, len(p.IDS)) seen := make(map[primitive.ObjectID]struct{}, len(p.IDS)) for _, raw := range p.IDS { id, err := primitive.ObjectIDFromHex(raw) if err != nil || id.IsZero() { return nil, errors.New("ids包含无效的媒体ID") } if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} ids = append(ids, id) } return ids, nil } func loadBatchMedia(ids []primitive.ObjectID) ([]*mediamod.Media, error) { list, err := mediamod.QueryAllList(bson.M{ "_id": bson.M{"$in": ids}, "isDelete": false, }, options.Find().SetLimit(int64(len(ids)))) if err != nil { return nil, err } if len(list) != len(ids) { return nil, errors.New("部分媒体不存在或已删除") } return list, nil } func validateDramaBatchPublish(medias []*mediamod.Media) error { dramaIDs := make([]primitive.ObjectID, 0, len(medias)) dramaByID := make(map[primitive.ObjectID]mediamod.Media) for _, media := range medias { if media.MediaType != mediamod.MediaTypeDrama { continue } dramaIDs = append(dramaIDs, media.ID) dramaByID[media.ID] = *media } if len(dramaIDs) == 0 { return nil } firstList, err := mediacontentmod.QueryAllList(bson.M{ "mediaId": bson.M{"$in": dramaIDs}, "mediaType": mediamod.MediaTypeDrama, "episodeNumber": 1, "isDelete": false, }, options.Find().SetLimit(int64(len(dramaIDs)))) if err != nil { return err } firstByMediaID := make(map[primitive.ObjectID]mediacontentmod.MediaContent, len(firstList)) for _, first := range firstList { firstByMediaID[first.MediaID] = *first } for _, id := range dramaIDs { media := dramaByID[id] media.Status = 1 if err := validateDramaMediaForPublish(media, firstByMediaID[id]); err != nil { return fmt.Errorf("短剧%s:%w", media.Title, err) } } return nil } func (p *WebBatchUpdateReq) validateDramaBatchFields(medias []*mediamod.Media) error { if p.Price == nil && p.Permission == nil && p.FreeEpisode == nil { return nil } for _, media := range medias { if media.MediaType == mediamod.MediaTypeDrama { return errors.New("短剧价格、权限和免费集数必须在剧集管理中配置") } } return nil } // Update 更新数据 func (p *WebBatchUpdateReq) Update(manager string) error { ids, err := p.ValidatedIDs() if err != nil { return err } medias, err := loadBatchMedia(ids) if err != nil { return err } if err = p.validateDramaBatchFields(medias); err != nil { return err } if p.IsActive != nil && *p.IsActive { if err = validateDramaBatchPublish(medias); err != nil { return err } } nonDramaIDs := make([]primitive.ObjectID, 0, len(medias)) for _, media := range medias { if media.MediaType != mediamod.MediaTypeDrama { nonDramaIDs = append(nonDramaIDs, media.ID) } } data := make(map[string]interface{}) if p.SID != nil { section, err := modulesectionmod.GetBySectionByID(*p.SID) if err != nil { return err } if section.ID.IsZero() { return errors.New("section is null") } moduleConf, err := moduleconfmod.FindOneById(section.SubModuleID) if err != nil { return err } if moduleConf.ID.IsZero() { return errors.New("module conf is null") } data["mId"] = section.SubModuleID data["sId"] = section.ID data["sectionName"] = section.SectionName data["moduleName"] = moduleConf.ModuleName } if p.Price != nil { data["price"] = *p.Price } if p.Permission != nil { data["permission"] = *p.Permission } if p.Tags != nil { data["tags"] = *p.Tags } if p.FreeEpisode != nil { data["freeEpisode"] = *p.FreeEpisode } data["updatedAct"] = manager data["updateTime"] = time.Now() err = webg.VideoDB.Trans(func(t *db.MongoTool) error { if p.IsActive != nil { // 短剧的剧集上下架由 media_content/batch/update 单独控制; // 其他媒体保持历史整部上下架行为。 if len(nonDramaIDs) > 0 { filter := bson.M{"mediaId": bson.M{"$in": nonDramaIDs}} updateData := make(map[string]interface{}) updateData["isActive"] = *p.IsActive updateData["updatedAct"] = manager updateData["updateTime"] = time.Now() _, err := mediacontentmod.UpdateByCond(t, filter, updateData) if err != nil { log.Error(fmt.Sprintf("mediacontentmod UpdateByCond error:%+v:", err)) return err } } if *p.IsActive { data["status"] = 1 data["latestPublishedAt"] = time.Now() } else { data["status"] = 0 } } // 更新数据 if _, err := mediamod.UpdateByIDS(t, ids, data); err != nil { log.Error(fmt.Sprintf("mediamod UpdateByIDS error:%+v:", err)) return err } return nil }) if err != nil { log.Error(fmt.Sprintf("Media update Trans fail error:%+v:", err)) return err } if p.IsActive != nil && *p.IsActive == false { // 批量下架 common.Go(func() { for _, id := range p.IDS { objId, _ := primitive.ObjectIDFromHex(id) media, err := mediamod.GetInfo(objId) if err != nil { continue } // 将它从排行榜上移除 topasist.Remove(topser.TypeMedia(media.MediaType), media.ID.Hex()) key := fmt.Sprintf(diamondRanking, media.MediaType) redis.Handler.ZRem(key, media.ID.Hex()) } }) } return nil } type WebDeleteReq struct { ID primitive.ObjectID `json:"id" binding:"required"` } // Delete 删除数据 func (p *WebDeleteReq) Delete() error { media, err := mediamod.GetInfo(p.ID) if err != nil { return err } err = webg.VideoDB.Trans(func(t *db.MongoTool) error { if err := mediamod.DeleteByID(t, p.ID); err != nil { return err } if media.MediaType == mediamod.MediaTypeDrama { return mediacontentmod.DeleteByMediaId(t, p.ID) } return nil }) if err != nil { return err } // 将它从排行榜上移除 topasist.Remove(topser.TypeMedia(media.MediaType), media.ID.Hex()) key := fmt.Sprintf(diamondRanking, media.MediaType) redis.Handler.ZRem(key, media.ID.Hex()) return nil } type WebBatchDeleteReq struct { Ids []primitive.ObjectID `json:"ids" binding:"required"` } // Delete 删除数据 func (p *WebBatchDeleteReq) Delete() error { if len(p.Ids) == 0 || len(p.Ids) > 100 { return errors.New("ids数量必须在1到100之间") } medias := make([]mediamod.Media, 0, len(p.Ids)) for _, id := range p.Ids { media, err := mediamod.GetInfo(id) if err != nil { return err } medias = append(medias, media) } err := webg.VideoDB.Trans(func(t *db.MongoTool) error { for _, media := range medias { if err := mediamod.DeleteByID(t, media.ID); err != nil { return err } if media.MediaType == mediamod.MediaTypeDrama { if err := mediacontentmod.DeleteByMediaId(t, media.ID); err != nil { return err } } } return nil }) if err != nil { return err } for _, media := range medias { // 将它从排行榜上移除 topasist.Remove(topser.TypeMedia(media.MediaType), media.ID.Hex()) key := fmt.Sprintf(diamondRanking, media.MediaType) redis.Handler.ZRem(key, media.ID.Hex()) } return nil }