679 lines
22 KiB
Go
679 lines
22 KiB
Go
package srv_mediaresource
|
||
|
||
import (
|
||
"context"
|
||
"math/rand"
|
||
"strings"
|
||
"time"
|
||
"videoweb/pub/common/log"
|
||
"videoweb/pub/common/slugx"
|
||
"videoweb/pub/common/srvdef"
|
||
"videoweb/pub/common/utils"
|
||
"videoweb/pub/models/mwacc"
|
||
"videoweb/pub/models/playletchaptermod"
|
||
"videoweb/pub/models/playletmod"
|
||
"videoweb/web/api/proto"
|
||
"videoweb/web/middleware/auth"
|
||
"videoweb/web/webg"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/mongo/options"
|
||
)
|
||
|
||
const (
|
||
defaultResourceSyncType = 1
|
||
defaultResourcePageNum = 1
|
||
defaultResourcePageSize = 20
|
||
maxResourcePageSize = 100
|
||
maxResourceImportIds = 100
|
||
playletResourceImportMsg = "同步任务已提交,后台正在处理"
|
||
playletHeatMinValue = 1000
|
||
playletHeatMaxValue = 5000
|
||
|
||
playletResourceMediaTypeAnime = 1 // 动漫
|
||
playletResourceMediaTypeComic = 4 // 漫画短剧
|
||
playletResourceMediaTypeShort = 5 // 短剧
|
||
playletResourceMediaTypeNormal = 6 // 普通短剧
|
||
)
|
||
|
||
// ListPlayletResource 分页查询短剧媒资列表
|
||
func ListPlayletResource(ctx context.Context, req *proto.PlayletResourceListReq) (*proto.PlayletResourceListResp, *srvdef.SrvErr) {
|
||
if req == nil {
|
||
return nil, srvdef.New(srvdef.ErrParamError, "", "")
|
||
}
|
||
syncType := defaultResourceSyncType
|
||
if req.SyncType != nil {
|
||
syncType = *req.SyncType
|
||
}
|
||
pageNumber := req.PageNumber
|
||
if pageNumber <= 0 {
|
||
pageNumber = defaultResourcePageNum
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = defaultResourcePageSize
|
||
}
|
||
if pageSize > maxResourcePageSize {
|
||
pageSize = maxResourcePageSize
|
||
}
|
||
mediaType, err1 := resolvePlayletResourceMediaType(req.MediaType)
|
||
if err1 != nil {
|
||
return nil, err1
|
||
}
|
||
ms := newPlayletMediaCenterFromConfig()
|
||
data, err := ms.mediaList(&playletMediaListReq{
|
||
MediaType: mediaType,
|
||
Title: req.Title,
|
||
Tag: req.Tag,
|
||
SyncType: syncType,
|
||
Page: uint(pageNumber),
|
||
Size: uint(pageSize),
|
||
})
|
||
if err != nil {
|
||
return nil, srvdef.New(srvdef.Failure, err.Error(), "")
|
||
}
|
||
if err := fillPlayletResourceAddStatus(ctx, data.Data); err != nil {
|
||
return nil, err
|
||
}
|
||
return &proto.PlayletResourceListResp{
|
||
List: data.Data,
|
||
Total: data.Pagination.Count,
|
||
}, nil
|
||
}
|
||
|
||
// ListPlayletResourceChapters 查询短剧媒资剧集信息
|
||
func ListPlayletResourceChapters(ctx context.Context, req *proto.PlayletResourceChapterListReq) (*proto.PlayletResourceChapterListResp, *srvdef.SrvErr) {
|
||
if req == nil || req.Id == 0 {
|
||
return nil, srvdef.New(srvdef.ErrParamError, "媒资ID不能为空", "")
|
||
}
|
||
ms := newPlayletMediaCenterFromConfig()
|
||
result, err := ms.mediaSync(&playletMediaSyncReq{
|
||
Id: req.Id,
|
||
SyncTime: 0,
|
||
})
|
||
if err != nil {
|
||
return nil, srvdef.New(srvdef.Failure, err.Error(), "")
|
||
}
|
||
if result.IsDelete || result.Media == nil {
|
||
return nil, srvdef.New(srvdef.ErrNoData, "媒资不存在或已删除", "")
|
||
}
|
||
list := make([]*proto.PlayletResourceChapterItem, 0, len(result.MediaContentList))
|
||
for _, item := range result.MediaContentList {
|
||
videoUrl := strings.TrimSpace(item.M3u8Url)
|
||
if videoUrl == "" {
|
||
videoUrl = strings.TrimSpace(item.Urls)
|
||
}
|
||
list = append(list, &proto.PlayletResourceChapterItem{
|
||
Id: item.ID,
|
||
EpisodeNumber: item.EpisodeNumber,
|
||
Title: strings.TrimSpace(item.Title),
|
||
Cover: item.Cover,
|
||
VideoUrl: videoUrl,
|
||
Height: item.Height,
|
||
Width: item.Width,
|
||
MediaSize: item.MediaSize,
|
||
PlayTime: item.PlayTime,
|
||
SaleCoin: item.SaleCoin,
|
||
HashId: item.HashId,
|
||
CreateTime: item.CreateTime,
|
||
UpdateTime: item.UpdateTime,
|
||
})
|
||
}
|
||
return &proto.PlayletResourceChapterListResp{
|
||
MediaId: req.Id,
|
||
Title: result.Media.Title,
|
||
List: list,
|
||
Total: len(list),
|
||
}, nil
|
||
}
|
||
|
||
// resolvePlayletResourceMediaType 校验后台显式传入的媒资类型
|
||
func resolvePlayletResourceMediaType(mediaType *int) (int, *srvdef.SrvErr) {
|
||
if mediaType == nil {
|
||
return 0, srvdef.New(srvdef.ErrParamError, "媒体类型不能为空", "媒体类型不能为空")
|
||
}
|
||
if !validPlayletResourceMediaType(*mediaType) {
|
||
return 0, srvdef.New(srvdef.ErrParamError, "媒体类型无效,仅支持1-动漫、2-漫画、4-漫画短剧、5-短剧、6-普通短剧", "媒体类型无效,仅支持1-动漫、2-漫画、4-漫画短剧、5-短剧、6-普通短剧")
|
||
}
|
||
return *mediaType, nil
|
||
}
|
||
|
||
// validPlayletResourceMediaType 媒资列表当前允许的媒体类型
|
||
func validPlayletResourceMediaType(mediaType int) bool {
|
||
switch mediaType {
|
||
case playletResourceMediaTypeAnime, playletResourceMediaTypeComic, playletResourceMediaTypeShort, playletResourceMediaTypeNormal:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// validPlayletImportMediaType 允许入库的媒资类型
|
||
func validPlayletImportMediaType(mediaType int) bool {
|
||
switch mediaType {
|
||
case playletResourceMediaTypeAnime, playletResourceMediaTypeComic, playletResourceMediaTypeShort, playletResourceMediaTypeNormal:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// contentTypeFromMediaType 媒资中心 media_type 映射为本项目 contentType
|
||
func contentTypeFromMediaType(mediaType int) playletmod.ContentType {
|
||
switch mediaType {
|
||
case playletResourceMediaTypeAnime:
|
||
return playletmod.ContentTypeAnime
|
||
case playletResourceMediaTypeComic:
|
||
return playletmod.ContentTypeComic
|
||
case playletResourceMediaTypeNormal:
|
||
return playletmod.ContentTypeAlbum
|
||
default:
|
||
return playletmod.ContentTypeTheater
|
||
}
|
||
}
|
||
|
||
// fillPlayletResourceAddStatus 回填媒资列表是否已加入后台短剧库
|
||
func fillPlayletResourceAddStatus(ctx context.Context, list []*proto.PlayletResourceListItem) *srvdef.SrvErr {
|
||
mediaIds := make([]uint, 0, len(list))
|
||
seen := make(map[uint]struct{}, len(list))
|
||
for _, item := range list {
|
||
if item == nil || item.ID == 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[item.ID]; ok {
|
||
continue
|
||
}
|
||
seen[item.ID] = struct{}{}
|
||
mediaIds = append(mediaIds, item.ID)
|
||
}
|
||
if len(mediaIds) == 0 {
|
||
return nil
|
||
}
|
||
|
||
playletList, err := playletmod.FindManyPlaylet(ctx, bson.M{
|
||
playletmod.FN.Playlet.MediaCenterId: bson.M{"$in": mediaIds},
|
||
})
|
||
if err != nil {
|
||
log.Error("查询短剧媒资加入状态失败", log.Any("mediaIds", mediaIds), log.Any("err", err))
|
||
return err
|
||
}
|
||
addedMap := make(map[uint]bool, len(playletList))
|
||
for _, item := range playletList {
|
||
if item == nil || item.MediaCenterId == 0 {
|
||
continue
|
||
}
|
||
addedMap[item.MediaCenterId] = true
|
||
}
|
||
for _, item := range list {
|
||
if item != nil {
|
||
item.IsAdd = addedMap[item.ID]
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ImportPlayletResource 提交短剧媒资入库任务
|
||
func ImportPlayletResource(ctx context.Context, req *proto.PlayletResourceUseReq) (string, *srvdef.SrvErr) {
|
||
ids, err := validatePlayletResourceImportReq(req)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
operatorId, operatorName := resolveImportOperator(ctx)
|
||
if err := pushPlayletResourceImportJob(ctx, ids, operatorId, operatorName); err != nil {
|
||
return "", err
|
||
}
|
||
return playletResourceImportMsg, nil
|
||
}
|
||
|
||
// importPlayletResourceBatch 批量处理短剧媒资入库任务
|
||
func importPlayletResourceBatch(ctx context.Context, ids []uint, operatorId int64, operatorName string) *proto.PlayletResourceUseResp {
|
||
resp := &proto.PlayletResourceUseResp{
|
||
Success: make([]*proto.PlayletResourceUseSuccess, 0, len(ids)),
|
||
Failed: make([]*proto.PlayletResourceUseFailed, 0),
|
||
}
|
||
importedMap, err := findImportedPlayletResourceMap(ctx, ids)
|
||
if err != nil {
|
||
for _, id := range ids {
|
||
resp.Failed = append(resp.Failed, &proto.PlayletResourceUseFailed{
|
||
MediaId: id,
|
||
Reason: "查询导入状态失败",
|
||
})
|
||
}
|
||
return resp
|
||
}
|
||
ms := newPlayletMediaCenterFromConfig()
|
||
for _, id := range ids {
|
||
if importedMap[id] {
|
||
resp.Failed = append(resp.Failed, &proto.PlayletResourceUseFailed{
|
||
MediaId: id,
|
||
Reason: "该媒资已导入",
|
||
})
|
||
continue
|
||
}
|
||
success, reason := importSinglePlayletResource(ctx, ms, id, operatorId, operatorName)
|
||
if reason != "" {
|
||
resp.Failed = append(resp.Failed, &proto.PlayletResourceUseFailed{
|
||
MediaId: id,
|
||
Reason: reason,
|
||
})
|
||
continue
|
||
}
|
||
resp.Success = append(resp.Success, success)
|
||
}
|
||
return resp
|
||
}
|
||
|
||
// findImportedPlayletResourceMap 批量查询已经导入的媒资ID
|
||
func findImportedPlayletResourceMap(ctx context.Context, ids []uint) (map[uint]bool, *srvdef.SrvErr) {
|
||
result := make(map[uint]bool, len(ids))
|
||
if len(ids) == 0 {
|
||
return result, nil
|
||
}
|
||
playletList, err := playletmod.FindManyPlaylet(ctx, bson.M{
|
||
playletmod.FN.Playlet.MediaCenterId: bson.M{"$in": ids},
|
||
})
|
||
if err != nil {
|
||
log.Error("批量查询短剧媒资导入状态失败", log.Any("mediaIds", ids), log.Any("err", err))
|
||
return nil, err
|
||
}
|
||
for _, item := range playletList {
|
||
if item == nil || item.MediaCenterId == 0 {
|
||
continue
|
||
}
|
||
result[item.MediaCenterId] = true
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func newPlayletMediaCenterFromConfig() *playletMediaCenterService {
|
||
return newPlayletMediaCenterService(playletMediaCenterConfig{
|
||
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
|
||
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
|
||
Secret: webg.Conf.MediaResourceDBCfg.Secret,
|
||
})
|
||
}
|
||
|
||
// validatePlayletResourceImportReq 校验短剧媒资入库请求
|
||
func validatePlayletResourceImportReq(req *proto.PlayletResourceUseReq) ([]uint, *srvdef.SrvErr) {
|
||
if req == nil {
|
||
return nil, srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
||
}
|
||
ids := dedupePlayletResourceIds(req.Ids)
|
||
if len(ids) == 0 {
|
||
return nil, srvdef.New(srvdef.ErrParamError, "媒资ID不能为空", "")
|
||
}
|
||
if len(ids) > maxResourceImportIds {
|
||
return nil, srvdef.New(srvdef.ErrParamError, "单次最多导入100条媒资", "")
|
||
}
|
||
return ids, nil
|
||
}
|
||
|
||
// dedupePlayletResourceIds 去重并过滤无效媒资ID
|
||
func dedupePlayletResourceIds(ids []uint) []uint {
|
||
result := make([]uint, 0, len(ids))
|
||
seen := make(map[uint]struct{}, len(ids))
|
||
for _, id := range ids {
|
||
if id == 0 {
|
||
continue
|
||
}
|
||
if _, ok := seen[id]; ok {
|
||
continue
|
||
}
|
||
seen[id] = struct{}{}
|
||
result = append(result, id)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// importSinglePlayletResource 导入单条短剧媒资
|
||
func importSinglePlayletResource(ctx context.Context, ms *playletMediaCenterService, mediaId uint, operatorId int64, operatorName string) (*proto.PlayletResourceUseSuccess, string) {
|
||
locked, err := lockPlayletResourceImportMedia(mediaId)
|
||
if err != nil {
|
||
log.Error("获取短剧媒资导入锁失败", log.Any("mediaId", mediaId), log.E(err))
|
||
return nil, "获取导入锁失败"
|
||
}
|
||
if !locked {
|
||
return nil, "该媒资正在导入"
|
||
}
|
||
defer unlockPlayletResourceImportMedia(mediaId)
|
||
|
||
if reason := checkPlayletResourceImported(ctx, mediaId); reason != "" {
|
||
return nil, reason
|
||
}
|
||
result, err := ms.mediaSync(&playletMediaSyncReq{
|
||
Id: mediaId,
|
||
SyncTime: 0,
|
||
})
|
||
if err != nil {
|
||
log.Error("同步短剧媒资失败", log.Any("mediaId", mediaId), log.E(err))
|
||
return nil, "同步媒资失败"
|
||
}
|
||
if result.IsDelete || result.Media == nil {
|
||
return nil, "媒资不存在或已删除"
|
||
}
|
||
if !validPlayletImportMediaType(result.Media.MediaType) {
|
||
return nil, "该媒资类型不支持导入短剧"
|
||
}
|
||
if len(result.MediaContentList) == 0 {
|
||
return nil, "媒资章节为空"
|
||
}
|
||
|
||
playlet := buildPlayletFromMedia(result.Media, operatorId, operatorName)
|
||
playlet.MediaSyncTime = maxPlayletMediaContentUpdateTime(0, result.MediaContentList)
|
||
if playlet.Title == "" {
|
||
return nil, "媒资标题为空"
|
||
}
|
||
exists := func(s string) bool {
|
||
item, _ := playletmod.FindOnePlaylet(ctx, slugx.Filter(playletmod.FN.Playlet.Slug, playletmod.FN.Playlet.Id, s, 0))
|
||
return item != nil
|
||
}
|
||
if serr := slugx.Ensure(&playlet.Slug, utils.SlugSourceFromKeywordsOrTitle(nil, playlet.Title), exists); serr != nil {
|
||
if serr.Tips != "" {
|
||
return nil, serr.Tips
|
||
}
|
||
return nil, "slug生成失败"
|
||
}
|
||
// 原图继续写入 Cover;压缩成功才写 CompressedCover,失败保持空。
|
||
playlet.CompressedCover = compressPlayletCoverOrEmpty(playlet.Cover)
|
||
chapters := buildPlayletChaptersFromMedia(result.MediaContentList)
|
||
if len(chapters) == 0 {
|
||
return nil, "媒资章节为空"
|
||
}
|
||
if err1 := playletmod.InsertOnePlaylet(ctx, playlet); err1 != nil {
|
||
log.Error("短剧媒资主表入库失败", log.Any("mediaId", mediaId), log.Any("err", err1))
|
||
return nil, "主资源入库失败"
|
||
}
|
||
for _, chapter := range chapters {
|
||
chapter.PlayletId = playlet.Id
|
||
}
|
||
if err1 := playletchaptermod.InsertManyPlayletChapter(ctx, chapters); err1 != nil {
|
||
log.Error("短剧媒资章节批量入库失败", log.Any("mediaId", mediaId), log.Any("playletId", playlet.Id), log.Any("chapterCount", len(chapters)), log.Any("err", err1))
|
||
rollbackImportedPlayletResource(ctx, playlet.Id, collectPlayletChapterIds(chapters))
|
||
return nil, "章节入库失败"
|
||
}
|
||
return &proto.PlayletResourceUseSuccess{
|
||
MediaId: mediaId,
|
||
PlayletId: playlet.Id,
|
||
}, ""
|
||
}
|
||
|
||
// collectPlayletChapterIds 收集章节ID
|
||
func collectPlayletChapterIds(chapters playletchaptermod.PlayletChapterList) []int64 {
|
||
ids := make([]int64, 0, len(chapters))
|
||
for _, chapter := range chapters {
|
||
if chapter == nil || chapter.Id == 0 {
|
||
continue
|
||
}
|
||
ids = append(ids, chapter.Id)
|
||
}
|
||
return ids
|
||
}
|
||
|
||
// checkPlayletResourceImported 检查媒资是否已经导入到本项目短剧表
|
||
func checkPlayletResourceImported(ctx context.Context, mediaId uint) string {
|
||
_, err := playletmod.FindOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.MediaCenterId: mediaId})
|
||
if err == nil {
|
||
return "该媒资已导入"
|
||
}
|
||
if err.Code != srvdef.ErrNoData {
|
||
log.Error("查询短剧媒资导入状态失败", log.Any("mediaId", mediaId), log.Any("err", err))
|
||
return "查询导入状态失败"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// buildPlayletFromMedia 将媒资中心主资源转换为本项目 playlet 主表模型
|
||
func buildPlayletFromMedia(media *proto.PlayletResourceListItem, operatorId int64, operatorName string) *playletmod.Playlet {
|
||
cover := strings.TrimSpace(media.VerticalCover)
|
||
if cover == "" {
|
||
cover = strings.TrimSpace(media.HorizontalCover)
|
||
}
|
||
return &playletmod.Playlet{
|
||
MediaCenterId: uint(media.ID),
|
||
MediaCreatedAt: mediaResourceTime(media.CreateTime),
|
||
MediaUpdatedAt: mediaResourceTime(media.UpdateTime),
|
||
Title: strings.TrimSpace(media.Title),
|
||
Desc: strings.TrimSpace(media.Summary),
|
||
Anchor: strings.TrimSpace(media.Anchor),
|
||
Cover: cover,
|
||
OldTags: oldTagNamesFromPlayletTagsText(media.TagsText),
|
||
MediaBusiness: playletMediaBusiness(media),
|
||
ContentType: contentTypeFromMediaType(media.MediaType),
|
||
TotalEpisode: int64(media.TotalEpisode),
|
||
UpdateStatus: normalizePlayletUpdateStatus(media.UpdateStatus),
|
||
HeatValue: randomPlayletHeatValue(),
|
||
Status: playletmod.StatusDown,
|
||
OperatorId: operatorId,
|
||
OperatorName: operatorName,
|
||
}
|
||
}
|
||
|
||
// playletMediaBusinessCGDJ 赤果短剧、CGDJ 标题前缀对应的媒资业务来源。
|
||
const playletMediaBusinessCGDJ = "CGDJ"
|
||
|
||
// playletMediaBusinessRule 把媒资标题前缀映射成要写入的业务来源。先匹配先生效。
|
||
type playletMediaBusinessRule struct {
|
||
Prefix string
|
||
Business string
|
||
}
|
||
|
||
// playletMediaBusinessRules 多个前缀可以对应同一个业务来源。
|
||
// 新增来源时在这里追加;同一来源的不同叫法共用同一个 Business。
|
||
var playletMediaBusinessRules = []playletMediaBusinessRule{
|
||
//{Prefix: "赤果短剧", Business: playletMediaBusinessCGDJ},
|
||
{Prefix: "CGDJ", Business: playletMediaBusinessCGDJ},
|
||
}
|
||
|
||
func playletMediaBusiness(media *proto.PlayletResourceListItem) string {
|
||
if media == nil || media.MediaType != playletResourceMediaTypeShort {
|
||
return ""
|
||
}
|
||
title := strings.ToUpper(strings.TrimSpace(media.Title))
|
||
for _, rule := range playletMediaBusinessRules {
|
||
prefix := strings.ToUpper(strings.TrimSpace(rule.Prefix))
|
||
business := strings.TrimSpace(rule.Business)
|
||
if prefix == "" || business == "" {
|
||
continue
|
||
}
|
||
if strings.HasPrefix(title, prefix) {
|
||
return business
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// playletMediaBusinessSearchTitles 返回回填时用来搜索媒资库的标题前缀。
|
||
func playletMediaBusinessSearchTitles() []string {
|
||
seen := make(map[string]struct{}, len(playletMediaBusinessRules))
|
||
titles := make([]string, 0, len(playletMediaBusinessRules))
|
||
for _, rule := range playletMediaBusinessRules {
|
||
prefix := strings.TrimSpace(rule.Prefix)
|
||
if prefix == "" {
|
||
continue
|
||
}
|
||
key := strings.ToUpper(prefix)
|
||
if _, ok := seen[key]; ok {
|
||
continue
|
||
}
|
||
seen[key] = struct{}{}
|
||
titles = append(titles, prefix)
|
||
}
|
||
return titles
|
||
}
|
||
|
||
func oldTagNamesFromPlayletTagsText(tagsText string) []string {
|
||
fields := strings.FieldsFunc(strings.TrimSpace(tagsText), func(r rune) bool {
|
||
switch r {
|
||
case ',', ',', '、', ';', ';', '/', '|':
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
})
|
||
names := make([]string, 0, len(fields))
|
||
seen := make(map[string]struct{}, len(fields))
|
||
for _, field := range fields {
|
||
name := strings.TrimSpace(field)
|
||
if name == "" {
|
||
continue
|
||
}
|
||
if _, ok := seen[name]; ok {
|
||
continue
|
||
}
|
||
seen[name] = struct{}{}
|
||
names = append(names, name)
|
||
}
|
||
return names
|
||
}
|
||
|
||
func randomPlayletHeatValue() int64 {
|
||
return int64(rand.Intn(playletHeatMaxValue-playletHeatMinValue+1) + playletHeatMinValue)
|
||
}
|
||
|
||
// mediaResourceTime 转换媒资中心时间戳
|
||
func mediaResourceTime(ts int64) time.Time {
|
||
if ts <= 0 {
|
||
return time.Time{}
|
||
}
|
||
if ts > 1e12 {
|
||
return time.UnixMilli(ts)
|
||
}
|
||
return time.Unix(ts, 0)
|
||
}
|
||
|
||
// buildPlayletChaptersFromMedia 将媒资中心章节资源转换为本项目 playlet_chapter 模型
|
||
func buildPlayletChaptersFromMedia(mediaContentList []playletMediaContent) playletchaptermod.PlayletChapterList {
|
||
chapters := make(playletchaptermod.PlayletChapterList, 0, len(mediaContentList))
|
||
seenMediaContentIds := make(map[uint]struct{}, len(mediaContentList))
|
||
seenEpisodes := make(map[int]struct{}, len(mediaContentList))
|
||
seenHashIds := make(map[string]struct{}, len(mediaContentList))
|
||
seenVideoUrls := make(map[string]struct{}, len(mediaContentList))
|
||
for _, item := range mediaContentList {
|
||
videoUrl := strings.TrimSpace(item.M3u8Url)
|
||
if videoUrl == "" {
|
||
videoUrl = strings.TrimSpace(item.Urls)
|
||
}
|
||
if videoUrl == "" {
|
||
continue
|
||
}
|
||
if isDuplicatePlayletMediaChapter(item, videoUrl, seenMediaContentIds, seenEpisodes, seenHashIds, seenVideoUrls) {
|
||
continue
|
||
}
|
||
episodeNumber := int64(item.EpisodeNumber)
|
||
if episodeNumber <= 0 {
|
||
episodeNumber = int64(len(chapters) + 1)
|
||
}
|
||
height := int64(item.Height)
|
||
width := int64(item.Width)
|
||
ratio := item.Ratio
|
||
if ratio <= 0 && height > 0 {
|
||
ratio = float64(width) / float64(height)
|
||
}
|
||
chapters = append(chapters, &playletchaptermod.PlayletChapter{
|
||
MediaContentId: uint(item.ID),
|
||
CurrentEpisode: episodeNumber,
|
||
Title: strings.TrimSpace(item.Title),
|
||
Hash: item.HashId,
|
||
Height: height,
|
||
Width: width,
|
||
Ratio: ratio,
|
||
VideoUrl: videoUrl,
|
||
MediaSize: int64(item.MediaSize),
|
||
MediaTime: int64(item.PlayTime),
|
||
PayType: playletchaptermod.PayTypeFree,
|
||
SingleCoins: int64(item.SaleCoin),
|
||
WatermarkStatus: playletchaptermod.WatermarkStatusPending,
|
||
})
|
||
}
|
||
return chapters
|
||
}
|
||
|
||
// isDuplicatePlayletMediaChapter 判断媒资中心章节是否重复
|
||
func isDuplicatePlayletMediaChapter(item playletMediaContent, videoUrl string, seenMediaContentIds map[uint]struct{}, seenEpisodes map[int]struct{}, seenHashIds map[string]struct{}, seenVideoUrls map[string]struct{}) bool {
|
||
if item.ID > 0 {
|
||
if _, ok := seenMediaContentIds[item.ID]; ok {
|
||
log.Info("跳过重复短剧媒资章节", log.Any("mediaContentId", item.ID), log.Any("episodeNumber", item.EpisodeNumber))
|
||
return true
|
||
}
|
||
seenMediaContentIds[item.ID] = struct{}{}
|
||
return false
|
||
}
|
||
if item.EpisodeNumber > 0 {
|
||
if _, ok := seenEpisodes[item.EpisodeNumber]; ok {
|
||
log.Info("跳过重复短剧媒资章节", log.Any("episodeNumber", item.EpisodeNumber))
|
||
return true
|
||
}
|
||
seenEpisodes[item.EpisodeNumber] = struct{}{}
|
||
return false
|
||
}
|
||
hashId := strings.TrimSpace(item.HashId)
|
||
if hashId != "" {
|
||
if _, ok := seenHashIds[hashId]; ok {
|
||
log.Info("跳过重复短剧媒资章节", log.Any("hashId", hashId))
|
||
return true
|
||
}
|
||
seenHashIds[hashId] = struct{}{}
|
||
return false
|
||
}
|
||
if _, ok := seenVideoUrls[videoUrl]; ok {
|
||
log.Info("跳过重复短剧媒资章节", log.Any("videoUrl", videoUrl))
|
||
return true
|
||
}
|
||
seenVideoUrls[videoUrl] = struct{}{}
|
||
return false
|
||
}
|
||
|
||
// rollbackImportedPlayletResource 回滚单条媒资入库产生的数据
|
||
func rollbackImportedPlayletResource(ctx context.Context, playletId int64, chapterIds []int64) {
|
||
for _, chapterId := range chapterIds {
|
||
if _, err := playletchaptermod.DelOnePlayletChapter(ctx, bson.M{playletchaptermod.FN.PlayletChapter.Id: chapterId}); err != nil {
|
||
log.Error("回滚短剧媒资章节失败", log.Any("playletId", playletId), log.Any("chapterId", chapterId), log.Any("err", err))
|
||
}
|
||
}
|
||
if _, err := playletmod.DelOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: playletId}); err != nil {
|
||
log.Error("回滚短剧媒资主表失败", log.Any("playletId", playletId), log.Any("err", err))
|
||
}
|
||
}
|
||
|
||
// resolveImportOperator 从后台登录态解析当前操作人
|
||
func resolveImportOperator(ctx context.Context) (int64, string) {
|
||
c, ok := ctx.(*gin.Context)
|
||
if !ok || c == nil {
|
||
return 0, ""
|
||
}
|
||
accId := auth.GetAccount(c)
|
||
if accId <= 0 {
|
||
return 0, ""
|
||
}
|
||
acc, err := mwacc.FindOneAccByFilter(c, bson.M{
|
||
mwacc.FN.MwAcc.Id: accId,
|
||
}, options.FindOne().SetProjection(bson.D{
|
||
{Key: mwacc.FN.MwAcc.Id, Value: 1},
|
||
{Key: mwacc.FN.MwAcc.UserName, Value: 1},
|
||
}))
|
||
if err != nil || acc == nil {
|
||
return accId, ""
|
||
}
|
||
return acc.Id, acc.UserName
|
||
}
|
||
|
||
// maxPlayletMediaContentUpdateTime 记录本次同步返回章节中的最大更新时间
|
||
func maxPlayletMediaContentUpdateTime(syncTime int64, mediaContentList []playletMediaContent) int64 {
|
||
result := syncTime
|
||
for _, item := range mediaContentList {
|
||
if item.UpdateTime > result {
|
||
result = item.UpdateTime
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// normalizePlayletUpdateStatus 三方 update_status=0 在本项目按 2-已完结保存
|
||
func normalizePlayletUpdateStatus(updateStatus int) int {
|
||
if updateStatus == 0 {
|
||
return 2
|
||
}
|
||
return updateStatus
|
||
}
|