503 lines
18 KiB
Go
503 lines
18 KiB
Go
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
|
|
}
|