package mediaService import ( "91porn-server/common/log" "91porn-server/common/mediaCenter" sli "91porn-server/common/slice" "91porn-server/models/v/mediacontentmod" "91porn-server/models/v/mediamod" "91porn-server/models/v/moduleconfmod" "91porn-server/models/v/modulesectionmod" "91porn-server/models/v/sensitivewordmod" "fmt" "strings" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) // UseMediaData 同步acg动漫 func (m *mediaSync) UseMediaData(admin string, id uint, dimension string, dimensionType, kind int, sectionId primitive.ObjectID) (err error) { // 加锁,防止多个同步出错 // 判断当前是否有正在同步的任务,有的话就不执行 key := fmt.Sprintf("MediaSync:%v", id) has, err := m.rd.SetNX(key, 1, time.Minute*10) if err != nil { log.Error("SyncAll setNx fail", log.E(err)) return } if !has { log.Info("【ACG订阅】该任务已经在进行", log.Any("task", key)) return } // 释放锁 defer m.rd.Del(key) log.Info("【ACG订阅】开始", log.Any("admin", admin), log.Any("mediaCenterId", id), log.Any("dimension", dimension), log.Any("dimensionType", dimensionType), log.Any("kind", kind), log.Any("sectionId", sectionId), ) var media mediamod.Media var module moduleconfmod.ModuleConf var section modulesectionmod.Section // 获取专题 if !sectionId.IsZero() { section, _ = modulesectionmod.GetBySectionID(sectionId) module, _ = moduleconfmod.GetByID(section.SubModuleID) } // 同步数据 data, err := m.Ms.MediaSync(&mediaCenter.MediaSyncReq{ Id: id, SyncTime: 0, }) if err != nil { log.Error("【ACG订阅】 call mediaSync Fail", log.Any("id", id), log.E(err)) return err } if data.IsDelete { // 数据已经删除,不需要同步 log.Info("【ACG订阅】 数据在媒资库已经删除,不需要同步", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId)) return } log.Info("【ACG订阅】获取到media数据", log.Any("admin", admin), log.Any("mediaCenterId", id), log.Any("mediaData", data.Media), ) media, err = m.OnUseMediaData(admin, module, section, dimension, dimensionType, kind, data.Media) if err != nil { log.Error("【ACG订阅】 OnUseMediaData Fail", log.Any("id", id), log.E(err)) return err } log.Info("【ACG订阅】处理media数据成功", log.Any("admin", admin), log.Any("mediaCenterId", id), log.Any("media objId", media.ID), ) if len(data.MediaContentList) == 0 { // 已经没有需要同步的数据了,直接返回 return } newSyncTime, isAdd, err := m.OnUseMediaContentData(admin, media, data.MediaContentList) if err != nil { log.Error("【ACG订阅】 OnUseMediaContentData Fail", log.Any("id", id), log.E(err)) return err } err = OnMediaContentChange(media.ID, media.MediaType, isAdd) if err != nil { log.Error("【ACG订阅】 OnMediaContentChange Fail", log.Any("id", id), log.E(err)) return err } m.OnSyncTimeChange(media.ID, newSyncTime) log.Info("【ACG订阅】完成", log.Any("admin", admin), log.Any("mediaCenterId", id), log.Any("dimension", dimension), log.Any("dimensionType", dimensionType), log.Any("kind", kind), log.Any("sectionId", sectionId), ) return nil } func OnMediaContentChange(mediaId primitive.ObjectID, mediaType string, isAdd bool) (err error) { // 获取当前最小的章节,重新update defaultContent := mediamod.DefaultContent{} if mediaType == mediamod.MediaTypeImage { contentList, _, _, err := mediacontentmod.GetList(bson.M{"mediaId": mediaId, "isActive": true, "isDelete": false}, 0, 1, bson.D{{Key: "episodeNumber", Value: 1}, {Key: "createdAt", Value: 1}}) if err != nil { return err } if len(contentList) > 0 { defaultContent.Id = contentList[0].ID defaultContent.Name = contentList[0].Name defaultContent.UrlSet = contentList[0].UrlSet defaultContent.Cover = contentList[0].Cover } } // 子集总数 count, err := mediacontentmod.QueryAllCount(bson.M{"mediaId": mediaId, "isActive": true, "isDelete": false}) if err != nil { return err } // 获取子集免费章节总数 freeCount, err := mediacontentmod.QueryAllCount(bson.M{"mediaId": mediaId, "listenPermission": 2, "isActive": true, "isDelete": false}) if err != nil { return err } contentsPrice, err := mediacontentmod.QueryContentsPrice(mediaId) if err != nil { return } updateData := bson.M{ "currentEpisode": count, "contentsPrice": contentsPrice, "freeEpisode": freeCount, "updateTime": time.Now(), "defaultContent": defaultContent, // 默认展示的子集内容,冗余一个字段,减少一次查询 } if isAdd { now := time.Now() updateData["contentUpdateTime"] = now updateData["latestPublishedAt"] = now } //修改当前父级的记录 _, err = mediamod.UpdateByID(nil, mediaId, updateData) if err != nil { return err } return nil } func (m *mediaSync) OnUseMediaData(admin string, module moduleconfmod.ModuleConf, section modulesectionmod.Section, dimension string, dimensionType, kind int, mediaData *mediaCenter.Media) (media mediamod.Media, err error) { if mediaData == nil { return } dimension, dimensionType, kind = normalizeImportedTagClassification(mediaData.MediaType, dimension, dimensionType, kind) // 查询是否存在 media, err = mediamod.GetInfoByMediaCenterId(mediaData.ID) if err != nil && err != mediamod.MediaNotFound { return } var exists bool if err == nil { exists = true } else if err == mediamod.MediaNotFound && mediaData.DataVersion == 0 { exists = false } else if err == mediamod.MediaNotFound && mediaData.DataVersion > 0 { // 如果不存在,则需要判断是否之前的存在 // 通过标题获取数据,看看是否存在 oldMedia, err := mediamod.GetInfoByTitle(mediaData.Title) if err != nil && err != mediamod.MediaNotFound { return media, err } if err == nil { // 如果之前就存在 media = oldMedia exists = true } } else { // 数据库查询错误,直接返回 return } if !exists { // 直接写入 media = mediamod.Media{ Title: mediaData.Title, HorizontalCover: mediaData.HorizontalCover, VerticalCover: mediaData.VerticalCover, Summary: mediaData.Summary, TotalEpisode: int(mediaData.TotalEpisode), // 总集数 UpdateStatus: mediaData.UpdateStatus, // 更新状态 PermissionIconHide: false, // 售卖类型标识隐藏 Permission: 0, // 收听权限 0:会员 1:金币购买 2:免费 Direction: 0, // 排版方向 0横 1竖(冗余字段) FreeTime: 0, // 免费试看时长 单位 s Anchor: mediaData.Anchor, // 作者 UpdatedAct: admin, // 操作用户账号 CreatedAt: time.Now(), // 文档创建时间 UpdateTime: time.Now(), // 文档更新时间 MediaCenterId: mediaData.ID, // 在媒资库中的唯一标识 Status: 1, // 默认上架 MID: module.ID, // 模块ID ModuleName: module.ModuleName, // 模块名称 SID: section.ID, SectionName: section.SectionName, } if kind > 0 { media.Kind = kind } switch mediaData.MediaType { case 1: // 动漫 media.MediaType = "video" case 2: // 漫画 media.MediaType = "image" case 3: // 文本小说 media.MediaType = "text" case 4: // 有声小说 media.MediaType = "text" media.MediaSubType = 1 case 5: // 短剧 media.MediaType = mediamod.MediaTypeDrama media.MediaSubType = 0 media.Kind = mediamod.Theater media.Permission = 1 media.Price = 0 media.Status = 0 } if media.UpdateStatus == 0 { // 直接是已完结 media.UpdateStatus = 2 } if strings.TrimSpace(mediaData.TagsText) != "" && kind > 0 && dimension != "" && dimensionType > 0 { tagList := strings.Split(mediaData.TagsText, ",") tagIds := make([]primitive.ObjectID, 0, len(tagList)) for _, tagName := range tagList { tid, suc := HandleTag(tagName, dimension, dimensionType, kind) if !suc { continue } tagIds = append(tagIds, tid) } media.Tags = tagIds } // 敏感词命中:强制下架(Status=0),命中详情写 log,便于管理员从后台日志复查 // 此接口为异步 worker 写库,无法回传 handler,故只 log + 强制状态 terms := sensitivewordmod.LoadEnabledTerms() titleHits := sensitivewordmod.MatchHits(media.Title, terms) summaryHits := sensitivewordmod.MatchHits(media.Summary, terms) if len(titleHits) > 0 || len(summaryHits) > 0 { media.Status = 0 log.Warn("OnUseMediaData sensitive hit, forced offline", log.Any("mediaCenterId", mediaData.ID), log.Any("title", media.Title), log.Any("detail", sensitivewordmod.FormatHitDetail(titleHits, summaryHits, nil))) } objId, err := mediamod.Insert(nil, media) if err != nil { return media, err } media.ID = objId return media, nil } //修改数据(不修改这个价格了,避免跟运营自己修改的价格冲突,只做部分内容的修改) updateData := make(map[string]interface{}) updateData["title"] = mediaData.Title updateData["horizontalCover"] = mediaData.HorizontalCover updateData["verticalCover"] = mediaData.VerticalCover updateData["summary"] = mediaData.Summary updateData["totalEpisode"] = int(mediaData.TotalEpisode) updateData["updateStatus"] = mediaData.UpdateStatus if media.UpdateStatus == 0 { // 直接是已完结 updateData["updateStatus"] = 2 } updateData["anchor"] = mediaData.Anchor updateData["mediaCenterId"] = mediaData.ID updateData["updateTime"] = time.Now() if kind > 0 { updateData["kind"] = kind } if strings.TrimSpace(mediaData.TagsText) != "" && kind > 0 && dimension != "" && dimensionType > 0 { tagList := strings.Split(mediaData.TagsText, ",") tagIds := prepareImportedTagIDs(mediaData.MediaType, media.Tags) for _, tagName := range tagList { tid, suc := HandleTag(tagName, dimension, dimensionType, kind) if !suc { continue } tagIds = append(tagIds, tid) } // 去重 updateData["tags"] = sli.RemoveRepObjectID(tagIds) } if !section.ID.IsZero() { updateData["mId"] = module.ID updateData["moduleName"] = module.ModuleName updateData["sectionName"] = section.SectionName updateData["sId"] = section.ID } _, err = mediamod.UpdateByID(nil, media.ID, updateData) if err != nil { return media, err } return } func normalizeImportedTagClassification(mediaType int, dimension string, dimensionType, kind int) (string, int, int) { if mediaType == 5 { return "剧场", 5, mediamod.Theater } return dimension, dimensionType, kind } func prepareImportedTagIDs(mediaType int, current []primitive.ObjectID) []primitive.ObjectID { if mediaType == 5 { return make([]primitive.ObjectID, 0, len(current)) } return current } func (m *mediaSync) OnUseMediaContentData(admin string, media mediamod.Media, mediaContentDataList []mediaCenter.MediaContent) (int64, bool, error) { syncTime := media.SyncTime if len(mediaContentDataList) == 0 { return syncTime, false, nil } isAdd := false updatedIDs := make([]primitive.ObjectID, 0, len(mediaContentDataList)) log.Info("【ACG订阅】获取到需要同步的子集", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集数量", len(mediaContentDataList))) for _, mediaContentData := range mediaContentDataList { h265URL := normalizeH265URL(mediaContentData.H265Url) // 更改下一次同步的时间点 if mediaContentData.UpdateTime > syncTime { syncTime = mediaContentData.UpdateTime } log.Info("【ACG订阅】同步子集", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID)) // 判断是否已经存在该记录 mediaContent, err := mediacontentmod.GetInfoByHashId(media.ID, mediaContentData.HashId) if err != nil && err != mediacontentmod.MediaContentNotFound { log.Error("【ACG订阅】同步子集失败", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID), log.Any("err", err)) return 0, false, err } var exists bool if err == nil { exists = true } else if err == mediacontentmod.MediaContentNotFound && mediaContentData.DataVersion == 0 { exists = false } else if err == mediacontentmod.MediaContentNotFound && mediaContentData.DataVersion > 0 { // 如果不存在,则需要判断是否之前的存在 // 通过标题获取数据,看看是否存在 oldMediaContent, err := mediacontentmod.GetInfoByMediaIdAndName(media.ID, mediaContentData.Title) if err != nil && err != mediacontentmod.MediaContentNotFound { log.Error("【ACG订阅】同步子集失败", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID), log.Any("err", err)) return 0, false, err } if err == nil { // 如果之前就存在 mediaContent = oldMediaContent exists = true } } else { // 数据库查询错误,直接返回 log.Error("【ACG订阅】同步子集失败", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID), log.Any("err", err)) return 0, false, err } if !exists { isAdd = true // 不存在则入库 mediaContent = mediacontentmod.MediaContent{ MediaID: media.ID, MediaType: media.MediaType, EpisodeNumber: mediaContentData.EpisodeNumber, ListenPermission: 0, // 收听权限 0:会员 1:金币购买 2:免费 Name: mediaContentData.Title, // 章节名 Text: mediaContentData.Text, // 内容(小说专用) Author: media.Anchor, // 作者 Height: int(mediaContentData.Height), // 高 Weight: int(mediaContentData.Width), // 宽 MediaSize: int64(mediaContentData.MediaSize), // 资源大小 PlayTime: mediaContentData.PlayTime, // 影片长度 Status: 1, // 状态 0:转码中 1:转码成功 2:转码失败, 视频资源使用 IsActive: false, // 默认不上架 UpdatedAct: admin, // 操作用户账号 CreatedAt: time.Now(), // 文档创建时间 UpdateTime: time.Now(), // 文档更新时间 HashId: mediaContentData.HashId, // 在媒资库中的唯一标识 VideoUrl: mediaContentData.M3u8Url, H265Url: h265URL, AudioUrl: mediaContentData.AudioUrl, } if media.MediaType == mediamod.MediaTypeDrama { mediaContent.Price = int64(mediaContentData.SaleCoin) mediaContent.ListenPermission = 1 if mediaContentData.SaleCoin == 0 { mediaContent.ListenPermission = 2 } } if strings.TrimSpace(mediaContentData.Urls) != "" { mediaContent.UrlSet = strings.Split(mediaContentData.Urls, ",") } else { mediaContent.UrlSet = []string{} } insertedID, insertErr := mediacontentmod.Insert(nil, mediaContent) err = insertErr if err != nil { log.Error("OnUseMediaContentData mediacontentmod.Insert fail", log.Any("mediaContent", mediaContent), log.E(err)) log.Error("【ACG订阅】同步子集失败", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID), log.Any("err", err)) return 0, false, err } updatedIDs = append(updatedIDs, insertedID) } else { // 存在则修改 updateData := make(map[string]interface{}) updateData["episodeNumber"] = mediaContentData.EpisodeNumber updateData["name"] = mediaContentData.Title updateData["text"] = mediaContentData.Text if strings.TrimSpace(mediaContentData.Urls) != "" { updateData["urlSet"] = strings.Split(mediaContentData.Urls, ",") } else { updateData["urlSet"] = []string{} } updateData["weight"] = int(mediaContentData.Width) updateData["height"] = int(mediaContentData.Height) updateData["ratio"] = mediaContentData.Ratio updateData["mediaSize"] = int64(mediaContentData.MediaSize) updateData["playTime"] = mediaContentData.PlayTime updateData["status"] = 1 updateData["hashId"] = mediaContentData.HashId updateData["videoUrl"] = mediaContentData.M3u8Url if shouldUpdateH265URL(mediaContent.H265Url, h265URL) { updateData["h265Url"] = h265URL } // 其他的只有有声小说有这个地址 updateData["audioUrl"] = mediaContentData.AudioUrl if media.MediaType == mediamod.MediaTypeDrama { updateData["price"] = int64(mediaContentData.SaleCoin) updateData["listenPermission"] = 1 if mediaContentData.SaleCoin == 0 { updateData["listenPermission"] = 2 } } _, err = mediacontentmod.UpdateByID(nil, mediaContent.ID, updateData) if err != nil { log.Error("OnUseMediaContentData mediacontentmod.UpdateByID fail", log.Any("updateData", updateData), log.E(err)) log.Error("【ACG订阅】同步子集失败", log.Any("mediaId", media.ID), log.Any("mediaCenterId", media.MediaCenterId), log.Any("子集ID", mediaContentData.ID), log.Any("err", err)) return 0, false, err } updatedIDs = append(updatedIDs, mediaContent.ID) } } return syncTime, isAdd, nil }