1814 lines
60 KiB
Go
1814 lines
60 KiB
Go
package srv_playlet
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
"videoweb/pub/common/enum/redisconst"
|
|
"videoweb/pub/common/log"
|
|
"videoweb/pub/common/playletcache"
|
|
"videoweb/pub/common/response"
|
|
"videoweb/pub/common/slugx"
|
|
"videoweb/pub/common/srvdef"
|
|
"videoweb/pub/common/utils"
|
|
"videoweb/pub/models"
|
|
"videoweb/pub/models/commentlikemod"
|
|
"videoweb/pub/models/commentmod"
|
|
"videoweb/pub/models/contenttagmod"
|
|
"videoweb/pub/models/mwacc"
|
|
"videoweb/pub/models/playletcategorymod"
|
|
"videoweb/pub/models/playletchaptermod"
|
|
"videoweb/pub/models/playletfavoritemod"
|
|
"videoweb/pub/models/playletlikemod"
|
|
"videoweb/pub/models/playletmod"
|
|
"videoweb/pub/models/playletthememod"
|
|
"videoweb/pub/mongos"
|
|
"videoweb/pub/pubdef"
|
|
"videoweb/pub/redis"
|
|
"videoweb/web/api/proto"
|
|
"videoweb/web/service/srv_contenttag"
|
|
"videoweb/web/service/srv_mediaresource"
|
|
"videoweb/web/service/srv_search"
|
|
"videoweb/web/service/srv_seopush"
|
|
"videoweb/web/skd/playletwatermark"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
var playletWebProjection = bson.D{
|
|
{Key: playletmod.FN.Playlet.Id, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Status, Value: 1},
|
|
{Key: playletmod.FN.Playlet.IsFeatured, Value: 1},
|
|
{Key: playletmod.FN.Playlet.IsOriginal, Value: 1},
|
|
{Key: playletmod.FN.Playlet.ShowOriginal, Value: 1},
|
|
{Key: playletmod.FN.Playlet.IsRecommend, Value: 1},
|
|
{Key: playletmod.FN.Playlet.ShowOnBanner, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Banner, Value: 1},
|
|
{Key: playletmod.FN.Playlet.ReviewAt, Value: 1},
|
|
{Key: playletmod.FN.Playlet.OnlineAt, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Title, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Slug, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Desc, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Anchor, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Cover, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CompressedCover, Value: 1},
|
|
{Key: playletmod.FN.Playlet.Preview, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CategoryIds, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CategoryBinds, Value: 1},
|
|
{Key: playletmod.FN.Playlet.ThemeIds, Value: 1},
|
|
{Key: playletmod.FN.Playlet.TagIds, Value: 1},
|
|
{Key: playletmod.FN.Playlet.ContentType, Value: 1},
|
|
{Key: playletmod.FN.Playlet.PayType, Value: 1},
|
|
{Key: playletmod.FN.Playlet.SingleCoins, Value: 1},
|
|
{Key: playletmod.FN.Playlet.TotalEpisode, Value: 1},
|
|
{Key: playletmod.FN.Playlet.FreeEpisodeCount, Value: 1},
|
|
{Key: playletmod.FN.Playlet.UpdateStatus, Value: 1},
|
|
{Key: playletmod.FN.Playlet.TopRank, Value: 1},
|
|
{Key: playletmod.FN.Playlet.RecommendRank, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountComment, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountBuy, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountBrowse, Value: 1},
|
|
{Key: playletmod.FN.Playlet.VirtualCountBrowse, Value: 1},
|
|
{Key: playletmod.FN.Playlet.HeatValue, Value: 1},
|
|
{Key: playletmod.FN.Playlet.VirtualHeatValue, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountCollect, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountLike, Value: 1},
|
|
{Key: playletmod.FN.Playlet.VirtualCountLike, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CountDisLike, Value: 1},
|
|
{Key: playletmod.FN.Playlet.SeoInfo.Title, Value: 1},
|
|
{Key: playletmod.FN.Playlet.SeoInfo.Description, Value: 1},
|
|
{Key: playletmod.FN.Playlet.SeoInfo.Keyworld, Value: 1},
|
|
{Key: playletmod.FN.Playlet.OperatorId, Value: 1},
|
|
{Key: playletmod.FN.Playlet.OperatorName, Value: 1},
|
|
{Key: playletmod.FN.Playlet.CreatedAt, Value: 1},
|
|
{Key: playletmod.FN.Playlet.UpdatedAt, Value: 1},
|
|
}
|
|
|
|
// ListPlaylet 分页查询短剧列表
|
|
func ListPlaylet(ctx context.Context, req *proto.PlayletListReq) (*proto.PlayletListPageResp, *srvdef.SrvErr) {
|
|
if req == nil {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "", "")
|
|
}
|
|
filter := buildPlayletListFilter(req)
|
|
pageNum, pageSize := normalizePage(req.PageNum, req.PageSize)
|
|
totalCount, err := playletmod.CountPlaylet(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp := &proto.PlayletListPageResp{
|
|
BasePageResp: response.BasePageResp{
|
|
TotalCount: totalCount,
|
|
PageSize: int(pageSize),
|
|
PageNum: int(pageNum),
|
|
List: make([]*proto.PlayletListResp, 0),
|
|
},
|
|
}
|
|
if totalCount == 0 {
|
|
return resp, nil
|
|
}
|
|
var list playletmod.PlayletList
|
|
if req.CategoryId != nil && *req.CategoryId > 0 {
|
|
list, err = playletmod.FindManyPlayletByCategoryRank(ctx, filter, *req.CategoryId, (pageNum-1)*pageSize, pageSize, playletmod.CategoryRankAdminSort(), playletWebProjection)
|
|
} else {
|
|
list, err = playletmod.FindManyPlaylet(ctx, filter, options.Find().
|
|
SetProjection(playletWebProjection).
|
|
SetSort(bson.D{
|
|
{Key: playletmod.FN.Playlet.CreatedAt, Value: -1},
|
|
{Key: playletmod.FN.Playlet.Id, Value: -1},
|
|
}).
|
|
SetSkip((pageNum-1)*pageSize).
|
|
SetLimit(pageSize))
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
categoryNameMap, err := buildCategoryNameMap(ctx, list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
themeNameMap, err := buildThemeNameMap(ctx, list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tagNameMap, err := buildTagNameMap(ctx, list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]*proto.PlayletListResp, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
webItem := playletToWebResp(item, categoryNameMap, themeNameMap, tagNameMap)
|
|
if req.CategoryId != nil {
|
|
webItem.CategoryRank = playletmod.CategoryRankOf(playletmod.EffectiveCategoryBinds(item), *req.CategoryId)
|
|
}
|
|
items = append(items, webItem)
|
|
}
|
|
resp.List = items
|
|
return resp, nil
|
|
}
|
|
|
|
// AddPlaylet 新增短剧
|
|
func AddPlaylet(ctx context.Context, req *proto.PlayletAddReq, operatorId int64) (*proto.PlayletAddResp, *srvdef.SrvErr) {
|
|
if req == nil {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
title := strings.TrimSpace(req.Title)
|
|
if title == "" {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "短剧标题不能为空", "")
|
|
}
|
|
categoryBinds, err := resolveAddCategoryBinds(req.CategoryBinds, req.CategoryIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
categoryIds := playletmod.CategoryIdsFromBinds(categoryBinds)
|
|
if err := validatePlayletCategoryIds(ctx, categoryIds); err != nil {
|
|
return nil, err
|
|
}
|
|
themeIds := dedupePositiveInt64Ids(req.ThemeIds)
|
|
if len(themeIds) == 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "题材ID不能为空", "")
|
|
}
|
|
if err := validatePlayletThemeIds(ctx, themeIds); err != nil {
|
|
return nil, err
|
|
}
|
|
tagIds, err := srv_contenttag.NormalizeContentTagIds(req.TagIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return nil, err
|
|
}
|
|
if !isValidPlayletStatus(req.Status) {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "上下架状态无效", "")
|
|
}
|
|
if !isValidPlayletContentType(req.ContentType) {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "内容类型无效", "")
|
|
}
|
|
payType, singleCoins, err := normalizePlayletPaySetting(req.PayType, &req.SingleCoins, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
freeEpisodeCount := int64(2)
|
|
if req.FreeEpisodeCount != nil {
|
|
if !validOfficialFreeEpisodeCount(*req.FreeEpisodeCount) {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "官网开放集数无效", "")
|
|
}
|
|
freeEpisodeCount = *req.FreeEpisodeCount
|
|
}
|
|
banner := strings.TrimSpace(req.Banner)
|
|
if req.ShowOnBanner && banner == "" {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "显示首页banner时banner图不能为空", "")
|
|
}
|
|
now := time.Now()
|
|
operatorId, operatorName := resolvePlayletOperator(ctx, operatorId)
|
|
doc := &playletmod.Playlet{
|
|
Status: req.Status,
|
|
IsFeatured: req.IsFeatured,
|
|
IsOriginal: true, // 管理后台新增的短剧都是原创
|
|
ShowOriginal: req.ShowOriginal,
|
|
IsRecommend: req.IsRecommend,
|
|
ShowOnBanner: req.ShowOnBanner,
|
|
Banner: banner,
|
|
ReviewAt: now,
|
|
OnlineAt: playletOnlineAt(req.Status, now),
|
|
CategoryIds: categoryIds,
|
|
CategoryBinds: categoryBinds,
|
|
ThemeIds: themeIds,
|
|
Title: title,
|
|
Slug: req.Slug,
|
|
Desc: strings.TrimSpace(req.Desc),
|
|
Anchor: strings.TrimSpace(req.Anchor),
|
|
Cover: strings.TrimSpace(req.Cover),
|
|
Preview: strings.TrimSpace(req.Preview),
|
|
TagIds: tagIds,
|
|
ContentType: req.ContentType,
|
|
PayType: payType,
|
|
SingleCoins: singleCoins,
|
|
TotalEpisode: req.TotalEpisode,
|
|
FreeEpisodeCount: freeEpisodeCount,
|
|
TopRank: req.TopRank,
|
|
RecommendRank: req.RecommendRank,
|
|
SeoInfo: normalizeSeoValue(req.SeoInfo),
|
|
OperatorId: operatorId,
|
|
OperatorName: operatorName,
|
|
}
|
|
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(&doc.Slug, utils.SlugSourceFromKeywordsOrTitle(doc.SeoInfo.Keyworld, title), exists); serr != nil {
|
|
return nil, serr
|
|
}
|
|
if err := playletmod.InsertOnePlaylet(ctx, doc); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := srv_contenttag.SyncContentTagBindCount(ctx, nil, tagIds, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return nil, err
|
|
}
|
|
go srv_mediaresource.FillPlayletCompressedCover(context.Background(), doc)
|
|
clearAppPlayletHomeCache()
|
|
if req.ShowOriginal {
|
|
playletcache.ClearRankCache()
|
|
}
|
|
srv_seopush.NotifyPlayletAfterWrite(ctx, doc.Id, nil, doc.Status, doc.OnlineAt)
|
|
if doc.Status == playletmod.StatusUp {
|
|
asyncSyncPlayletESByStatus([]int64{doc.Id}, doc.Status, "短剧新增")
|
|
}
|
|
return &proto.PlayletAddResp{Id: doc.Id}, nil
|
|
}
|
|
|
|
// UpdatePlaylet 更新短剧
|
|
func UpdatePlaylet(ctx context.Context, req *proto.PlayletUpdateReq, operatorId int64) *srvdef.SrvErr {
|
|
if req == nil || req.Id <= 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID无效", "")
|
|
}
|
|
current, err := playletmod.FindOnePlaylet(ctx, bson.M{
|
|
playletmod.FN.Playlet.Id: req.Id,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
setDoc := bson.M{playletmod.FN.Playlet.UpdatedAt: now}
|
|
if req.Status != nil {
|
|
if !isValidPlayletStatus(*req.Status) {
|
|
return srvdef.New(srvdef.ErrParamError, "上下架状态无效", "")
|
|
}
|
|
setDoc[playletmod.FN.Playlet.Status] = *req.Status
|
|
setDoc[playletmod.FN.Playlet.ReviewAt] = now
|
|
if *req.Status == playletmod.StatusUp {
|
|
setDoc[playletmod.FN.Playlet.OnlineAt] = now
|
|
setDoc[playletmod.FN.Playlet.IndexNow] = int64(0)
|
|
}
|
|
}
|
|
if req.FreeEpisodeCount != nil {
|
|
if !validOfficialFreeEpisodeCount(*req.FreeEpisodeCount) {
|
|
return srvdef.New(srvdef.ErrParamError, "官网开放集数无效", "")
|
|
}
|
|
setDoc[playletmod.FN.Playlet.FreeEpisodeCount] = *req.FreeEpisodeCount
|
|
}
|
|
if req.IsFeatured != nil {
|
|
setDoc[playletmod.FN.Playlet.IsFeatured] = *req.IsFeatured
|
|
}
|
|
if req.ShowOriginal != nil {
|
|
setDoc[playletmod.FN.Playlet.ShowOriginal] = *req.ShowOriginal
|
|
}
|
|
if req.IsRecommend != nil {
|
|
setDoc[playletmod.FN.Playlet.IsRecommend] = *req.IsRecommend
|
|
}
|
|
if req.ShowOnBanner != nil {
|
|
setDoc[playletmod.FN.Playlet.ShowOnBanner] = *req.ShowOnBanner
|
|
}
|
|
if req.Banner != nil {
|
|
setDoc[playletmod.FN.Playlet.Banner] = strings.TrimSpace(*req.Banner)
|
|
}
|
|
if req.CategoryBinds != nil || req.CategoryIds != nil {
|
|
categoryBinds, err := resolveCategoryBinds(req.CategoryBinds, req.CategoryIds, playletmod.EffectiveCategoryBinds(current))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
categoryIds := playletmod.CategoryIdsFromBinds(categoryBinds)
|
|
if err := validatePlayletCategoryIds(ctx, categoryIds); err != nil {
|
|
return err
|
|
}
|
|
setDoc[playletmod.FN.Playlet.CategoryIds] = categoryIds
|
|
setDoc[playletmod.FN.Playlet.CategoryBinds] = categoryBinds
|
|
}
|
|
if req.ThemeIds != nil {
|
|
themeIds := dedupePositiveInt64Ids(*req.ThemeIds)
|
|
if len(themeIds) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "题材ID不能为空", "")
|
|
}
|
|
if err := validatePlayletThemeIds(ctx, themeIds); err != nil {
|
|
return err
|
|
}
|
|
setDoc[playletmod.FN.Playlet.ThemeIds] = themeIds
|
|
}
|
|
if req.Title != nil {
|
|
title := strings.TrimSpace(*req.Title)
|
|
if title == "" {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧标题不能为空", "")
|
|
}
|
|
setDoc[playletmod.FN.Playlet.Title] = title
|
|
}
|
|
if req.Desc != nil {
|
|
setDoc[playletmod.FN.Playlet.Desc] = strings.TrimSpace(*req.Desc)
|
|
}
|
|
if req.Anchor != nil {
|
|
setDoc[playletmod.FN.Playlet.Anchor] = strings.TrimSpace(*req.Anchor)
|
|
}
|
|
coverChanged := false
|
|
if req.Cover != nil {
|
|
newCover := strings.TrimSpace(*req.Cover)
|
|
setDoc[playletmod.FN.Playlet.Cover] = newCover
|
|
if strings.TrimSpace(current.Cover) != newCover {
|
|
setDoc[playletmod.FN.Playlet.CompressedCover] = ""
|
|
coverChanged = true
|
|
}
|
|
}
|
|
if req.Preview != nil {
|
|
setDoc[playletmod.FN.Playlet.Preview] = strings.TrimSpace(*req.Preview)
|
|
}
|
|
var oldTagIds []int64
|
|
var newTagIds []int64
|
|
if req.TagIds != nil {
|
|
tagIds, err := srv_contenttag.NormalizeContentTagIds(*req.TagIds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return err
|
|
}
|
|
oldTagIds = append([]int64{}, current.TagIds...)
|
|
newTagIds = tagIds
|
|
setDoc[playletmod.FN.Playlet.TagIds] = tagIds
|
|
}
|
|
if req.ContentType != nil {
|
|
if !isValidPlayletContentType(*req.ContentType) {
|
|
return srvdef.New(srvdef.ErrParamError, "内容类型无效", "")
|
|
}
|
|
setDoc[playletmod.FN.Playlet.ContentType] = *req.ContentType
|
|
}
|
|
if req.PayType != nil {
|
|
payType, singleCoins, err := normalizePlayletPaySetting(*req.PayType, req.SingleCoins, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setDoc[playletmod.FN.Playlet.PayType] = payType
|
|
setDoc[playletmod.FN.Playlet.SingleCoins] = singleCoins
|
|
} else if req.SingleCoins != nil {
|
|
if *req.SingleCoins < 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "单集金币不能小于0", "")
|
|
}
|
|
setDoc[playletmod.FN.Playlet.SingleCoins] = *req.SingleCoins
|
|
}
|
|
if req.TotalEpisode != nil {
|
|
setDoc[playletmod.FN.Playlet.TotalEpisode] = *req.TotalEpisode
|
|
}
|
|
if req.TopRank != nil {
|
|
setDoc[playletmod.FN.Playlet.TopRank] = *req.TopRank
|
|
}
|
|
if req.RecommendRank != nil {
|
|
setDoc[playletmod.FN.Playlet.RecommendRank] = *req.RecommendRank
|
|
}
|
|
if req.SeoInfo != nil {
|
|
setDoc["seoInfo"] = normalizeSeoValue(*req.SeoInfo)
|
|
}
|
|
title := current.Title
|
|
if req.Title != nil {
|
|
title = strings.TrimSpace(*req.Title)
|
|
}
|
|
keywords := current.SeoInfo.Keyworld
|
|
if req.SeoInfo != nil {
|
|
keywords = normalizeSeoValue(*req.SeoInfo).Keyworld
|
|
}
|
|
exists := func(s string) bool {
|
|
item, _ := playletmod.FindOnePlaylet(ctx, slugx.Filter(playletmod.FN.Playlet.Slug, playletmod.FN.Playlet.Id, s, req.Id))
|
|
return item != nil
|
|
}
|
|
slug, changed, serr := slugx.PrepareOnUpdate(current.Slug, req.Slug,
|
|
utils.SlugSourceFromKeywordsOrTitle(keywords, title), exists)
|
|
if serr != nil {
|
|
return serr
|
|
}
|
|
if changed {
|
|
setDoc[playletmod.FN.Playlet.Slug] = slug
|
|
}
|
|
if len(setDoc) <= 1 {
|
|
return srvdef.New(srvdef.ErrParamError, "没有可更新字段", "")
|
|
}
|
|
operatorId, operatorName := resolvePlayletOperator(ctx, operatorId)
|
|
setDoc[playletmod.FN.Playlet.OperatorId] = operatorId
|
|
setDoc[playletmod.FN.Playlet.OperatorName] = operatorName
|
|
if err := playletmod.UpdateOnePlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: req.Id},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if req.TagIds != nil {
|
|
if err := srv_contenttag.SyncContentTagBindCount(ctx, oldTagIds, newTagIds, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if coverChanged {
|
|
go func(playletId int64, cover string) {
|
|
srv_mediaresource.FillPlayletCompressedCover(context.Background(), &playletmod.Playlet{
|
|
Id: playletId,
|
|
Cover: cover,
|
|
})
|
|
}(req.Id, strings.TrimSpace(*req.Cover))
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
if req.ShowOriginal != nil {
|
|
playletcache.ClearRankCache()
|
|
}
|
|
status := current.Status
|
|
onlineAt := current.OnlineAt
|
|
if req.Status != nil {
|
|
status = *req.Status
|
|
if status == playletmod.StatusUp {
|
|
onlineAt = now
|
|
}
|
|
}
|
|
prevStatus := current.Status
|
|
srv_seopush.NotifyPlayletAfterWrite(ctx, req.Id, &prevStatus, status, onlineAt)
|
|
asyncSyncPlayletESByLatestStatus([]int64{req.Id}, "短剧更新")
|
|
return nil
|
|
}
|
|
|
|
// DelPlaylet 删除短剧、剧集及关联的用户数据
|
|
func DelPlaylet(ctx context.Context, req *proto.PlayletDelReq) *srvdef.SrvErr {
|
|
if req == nil || len(req.Ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
for _, id := range req.Ids {
|
|
if id <= 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID无效", "")
|
|
}
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
playletFilter := bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}}
|
|
chapterFilter := bson.M{playletchaptermod.FN.PlayletChapter.PlayletId: bson.M{"$in": ids}}
|
|
var serviceErr *srvdef.SrvErr
|
|
var chapterIDs []int64
|
|
txnErr := mongos.Shop.Transaction(ctx, func(sctx mongo.SessionContext) error {
|
|
serviceErr = nil
|
|
playlets, err := playletmod.FindManyPlaylet(sctx, playletFilter, options.Find().SetProjection(bson.M{
|
|
playletmod.FN.Playlet.Id: 1,
|
|
playletmod.FN.Playlet.TagIds: 1,
|
|
}))
|
|
if err != nil {
|
|
serviceErr = err
|
|
return err
|
|
}
|
|
changes := make([]srv_contenttag.ContentTagBindChange, 0, len(playlets))
|
|
for _, playlet := range playlets {
|
|
if playlet != nil && len(playlet.TagIds) > 0 {
|
|
changes = append(changes, srv_contenttag.ContentTagBindChange{OldTagIds: playlet.TagIds})
|
|
}
|
|
}
|
|
if len(changes) > 0 {
|
|
if err := srv_contenttag.SyncContentTagBindCountBatch(sctx, changes, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
serviceErr = err
|
|
return err
|
|
}
|
|
}
|
|
chapters, err := playletchaptermod.FindManyPlayletChapter(sctx, chapterFilter, options.Find().SetProjection(bson.M{
|
|
playletchaptermod.FN.PlayletChapter.Id: 1,
|
|
}))
|
|
if err != nil {
|
|
serviceErr = err
|
|
return err
|
|
}
|
|
commentFilters := bson.A{bson.M{
|
|
commentmod.FN.Comment.ObjectType: pubdef.CommentObjectTypePlaylet,
|
|
commentmod.FN.Comment.ObjectId: bson.M{"$in": ids},
|
|
}}
|
|
chapterIDs = make([]int64, 0, len(chapters))
|
|
for _, chapter := range chapters {
|
|
if chapter != nil {
|
|
chapterIDs = append(chapterIDs, chapter.Id)
|
|
}
|
|
}
|
|
if len(chapterIDs) > 0 {
|
|
commentFilters = append(commentFilters, bson.M{
|
|
commentmod.FN.Comment.ObjectType: pubdef.CommentObjectTypePlayletChapter,
|
|
commentmod.FN.Comment.ObjectId: bson.M{"$in": chapterIDs},
|
|
})
|
|
}
|
|
commentFilter := bson.M{"$or": commentFilters}
|
|
comments, err := commentmod.FindManyComment(sctx, commentFilter, options.Find().SetProjection(bson.M{
|
|
commentmod.FN.Comment.Id: 1,
|
|
}))
|
|
if err != nil {
|
|
serviceErr = err
|
|
return err
|
|
}
|
|
commentIDs := make([]int64, 0, len(comments))
|
|
for _, comment := range comments {
|
|
if comment != nil {
|
|
commentIDs = append(commentIDs, comment.Id)
|
|
}
|
|
}
|
|
if err := commentlikemod.DeleteByCommentIds(sctx, commentIDs); err != nil {
|
|
serviceErr = err
|
|
return err
|
|
}
|
|
deletions := []struct {
|
|
collection string
|
|
filter bson.M
|
|
}{
|
|
{commentmod.TN.Comment, commentFilter},
|
|
{playletlikemod.TN.PlayletLike, bson.M{playletlikemod.FN.PlayletLike.PlayletID: bson.M{"$in": ids}}},
|
|
{playletfavoritemod.CollectionName, bson.M{playletfavoritemod.FieldPlayletID: bson.M{"$in": ids}}},
|
|
{models.WatchHistoryCollectionName, bson.M{models.WatchHistoryDataType: models.WatchHistoryTypePlaylet, models.WatchHistoryContentID: bson.M{"$in": ids}}},
|
|
{playletchaptermod.TN.PlayletChapter, chapterFilter},
|
|
{playletmod.TN.Playlet, playletFilter},
|
|
}
|
|
for _, deletion := range deletions {
|
|
if _, err := mongos.Shop.Coll(deletion.collection).DeleteMany(sctx, deletion.filter); err != nil {
|
|
log.Warn("删除短剧关联数据失败", log.Any("collection", deletion.collection), log.E(err))
|
|
serviceErr = srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if txnErr != nil {
|
|
if serviceErr != nil {
|
|
return serviceErr
|
|
}
|
|
log.Warn("删除短剧事务失败", log.E(txnErr))
|
|
return srvdef.New(srvdef.ErrDbDeleteError, "", "")
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
playletcache.ClearRankCache()
|
|
if redis.Client() != nil {
|
|
for _, id := range ids {
|
|
if err := redis.Del(redisconst.CommentListCacheKey(id, pubdef.CommentObjectTypePlaylet)); err != nil {
|
|
log.Warn("清理短剧评论缓存失败", log.Any("playletId", id), log.E(err))
|
|
}
|
|
}
|
|
for _, id := range chapterIDs {
|
|
if err := redis.Del(redisconst.CommentListCacheKey(id, pubdef.CommentObjectTypePlayletChapter)); err != nil {
|
|
log.Warn("清理剧集评论缓存失败", log.Any("chapterId", id), log.E(err))
|
|
}
|
|
}
|
|
}
|
|
srv_seopush.MarkSitemapDirty(ctx)
|
|
for _, id := range ids {
|
|
srv_seopush.EnqueueDeleted(ctx, srv_seopush.KindPlaylet, id)
|
|
}
|
|
if err := srv_search.SyncPlayletStatusToES(ctx, ids, playletmod.StatusDown); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletStatus 单/批量设置短剧上下架
|
|
func SetPlayletStatus(ctx context.Context, req *proto.PlayletStatusSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
if !isValidPlayletStatus(req.Status) {
|
|
return srvdef.New(srvdef.ErrParamError, "上下架状态无效", "")
|
|
}
|
|
now := time.Now()
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.Status: req.Status,
|
|
playletmod.FN.Playlet.ReviewAt: now,
|
|
playletmod.FN.Playlet.UpdatedAt: now,
|
|
}
|
|
if req.Status == playletmod.StatusUp {
|
|
setDoc[playletmod.FN.Playlet.OnlineAt] = now
|
|
setDoc[playletmod.FN.Playlet.IndexNow] = int64(0)
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
srv_seopush.NotifyPlayletAfterStatus(ctx, ids, req.Status, now)
|
|
asyncSyncPlayletESByStatus(ids, req.Status, "短剧上下架")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletRecommend 单/批量设置短剧是否推荐
|
|
func SetPlayletRecommend(ctx context.Context, req *proto.PlayletRecommendSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.IsRecommend: req.IsRecommend,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧推荐状态变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletFeatured 单/批量设置短剧是否爆剧
|
|
func SetPlayletFeatured(ctx context.Context, req *proto.PlayletFeaturedSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.IsFeatured: req.IsFeatured,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧爆剧状态变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletBanner 单/批量设置短剧是否显示在首页banner
|
|
func SetPlayletBanner(ctx context.Context, req *proto.PlayletBannerSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.ShowOnBanner: req.ShowOnBanner,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧banner状态变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletTag 单/批量设置短剧标签
|
|
func SetPlayletTag(ctx context.Context, req *proto.PlayletTagSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
if req.TagIds == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "标签ID不能为空", "")
|
|
}
|
|
tagIds, err := srv_contenttag.NormalizeContentTagIds(*req.TagIds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return err
|
|
}
|
|
list, err := playletmod.FindManyPlaylet(ctx, bson.M{
|
|
playletmod.FN.Playlet.Id: bson.M{"$in": ids},
|
|
}, options.Find().SetProjection(bson.D{
|
|
{Key: playletmod.FN.Playlet.Id, Value: 1},
|
|
{Key: playletmod.FN.Playlet.TagIds, Value: 1},
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.TagIds: tagIds,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
changes := make([]srv_contenttag.ContentTagBindChange, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
changes = append(changes, srv_contenttag.ContentTagBindChange{
|
|
OldTagIds: append([]int64{}, item.TagIds...),
|
|
NewTagIds: append([]int64{}, tagIds...),
|
|
})
|
|
}
|
|
if err := srv_contenttag.SyncContentTagBindCountBatch(ctx, changes, contenttagmod.ContentTagTypePlaylet); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧标签变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletCategory 单/批量设置短剧分类
|
|
func SetPlayletCategory(ctx context.Context, req *proto.PlayletCategorySetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
if req.CategoryIds == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "分类ID不能为空", "")
|
|
}
|
|
categoryIds, err := normalizePlayletCategoryIds(*req.CategoryIds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validatePlayletCategoryIds(ctx, categoryIds); err != nil {
|
|
return err
|
|
}
|
|
list, err := playletmod.FindManyPlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, item := range list {
|
|
binds := playletmod.MergeCategoryBindsByIds(playletmod.EffectiveCategoryBinds(item), categoryIds)
|
|
if err := playletmod.UpdateOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: item.Id}, bson.M{"$set": bson.M{
|
|
playletmod.FN.Playlet.CategoryIds: categoryIds,
|
|
playletmod.FN.Playlet.CategoryBinds: binds,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧分类变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletTheme 单/批量设置短剧题材
|
|
func SetPlayletTheme(ctx context.Context, req *proto.PlayletThemeSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
themeIds := dedupePositiveInt64Ids(req.ThemeIds)
|
|
if len(themeIds) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "题材ID不能为空", "")
|
|
}
|
|
if err := validatePlayletThemeIds(ctx, themeIds); err != nil {
|
|
return err
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.ThemeIds: themeIds,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧题材变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletVirtualLike 单/批量设置短剧虚拟点赞数
|
|
func SetPlayletVirtualLike(ctx context.Context, req *proto.PlayletVirtualLikeSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
if req.MinVirtualCountLike < 0 || req.MaxVirtualCountLike < req.MinVirtualCountLike {
|
|
return srvdef.New(srvdef.ErrParamError, "虚拟点赞数范围无效", "")
|
|
}
|
|
if req.MaxVirtualCountLike-req.MinVirtualCountLike < int64(len(ids))-1 {
|
|
return srvdef.New(srvdef.ErrParamError, "虚拟点赞数范围不足,无法保证每条短剧数值不同", "")
|
|
}
|
|
values := randomUniqueVirtualLikeValues(req.MinVirtualCountLike, req.MaxVirtualCountLike, len(ids))
|
|
now := time.Now()
|
|
docs := make([]mongo.WriteModel, 0, len(ids))
|
|
for index, id := range ids {
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.VirtualCountLike: values[index],
|
|
playletmod.FN.Playlet.UpdatedAt: now,
|
|
}
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{playletmod.FN.Playlet.Id: id}).
|
|
SetUpdate(bson.M{"$set": setDoc}))
|
|
}
|
|
if err := playletmod.BulkWritePlaylet(ctx, docs); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, "短剧虚拟点赞数变更")
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletVirtualBrowse 单/批量设置短剧虚拟观看数
|
|
func SetPlayletVirtualBrowse(ctx context.Context, req *proto.PlayletVirtualBrowseSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
return setPlayletVirtualMetric(ctx, req.Ids, req.MinVirtualCountBrowse, req.MaxVirtualCountBrowse,
|
|
playletmod.FN.Playlet.VirtualCountBrowse, "短剧虚拟观看数变更")
|
|
}
|
|
|
|
// SetPlayletVirtualHeat 单/批量设置短剧虚拟热度
|
|
func SetPlayletVirtualHeat(ctx context.Context, req *proto.PlayletVirtualHeatSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
return setPlayletVirtualMetric(ctx, req.Ids, req.MinVirtualHeatValue, req.MaxVirtualHeatValue,
|
|
playletmod.FN.Playlet.VirtualHeatValue, "短剧虚拟热度变更")
|
|
}
|
|
|
|
// setPlayletVirtualMetric 按区间为每部短剧设置不同虚拟数值
|
|
func setPlayletVirtualMetric(ctx context.Context, rawIds []int64, min, max int64, field, reason string) *srvdef.SrvErr {
|
|
ids := dedupePositiveInt64Ids(rawIds)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
if min < 0 || max < min || max-min < int64(len(ids))-1 {
|
|
return srvdef.New(srvdef.ErrParamError, "虚拟数值区间无效或不足", "")
|
|
}
|
|
values := randomUniqueVirtualLikeValues(min, max, len(ids))
|
|
now := time.Now()
|
|
docs := make([]mongo.WriteModel, 0, len(ids))
|
|
for index, id := range ids {
|
|
docs = append(docs, mongo.NewUpdateOneModel().
|
|
SetFilter(bson.M{playletmod.FN.Playlet.Id: id}).
|
|
SetUpdate(bson.M{"$set": bson.M{field: values[index], playletmod.FN.Playlet.UpdatedAt: now}}))
|
|
}
|
|
if err := playletmod.BulkWritePlaylet(ctx, docs); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
playletcache.ClearRankCache()
|
|
asyncSyncPlayletESByLatestStatus(ids, reason)
|
|
return nil
|
|
}
|
|
|
|
// SetPlayletPayType 单/批量设置短剧默认支付方式
|
|
func SetPlayletPayType(ctx context.Context, req *proto.PlayletPayTypeSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
payType, singleCoins, err := normalizePlayletPaySetting(req.PayType, req.SingleCoins, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setDoc := bson.M{
|
|
playletmod.FN.Playlet.PayType: payType,
|
|
playletmod.FN.Playlet.SingleCoins: singleCoins,
|
|
playletmod.FN.Playlet.UpdatedAt: time.Now(),
|
|
}
|
|
if err := playletmod.UpdateManyPlaylet(ctx,
|
|
bson.M{playletmod.FN.Playlet.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
clearAppPlayletHomeCache()
|
|
return nil
|
|
}
|
|
|
|
// ResetPlayletWatermark 按短剧ID批量重置其下全部剧集水印,等待定时任务重新加水印。
|
|
// 已有水印地址不清理,新水印完成回写时直接覆盖。
|
|
// 原创短剧保留 mediaSourceId / videoUrl / mediaTaskId,直接进入已登记后按原片重跑水印。
|
|
// 非原创清空 media 关联并回到未处理,由 SyncMediaFile 重新登记。
|
|
func ResetPlayletWatermark(ctx context.Context, req *proto.PlayletWatermarkResetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "短剧ID不能为空", "")
|
|
}
|
|
originalIds, otherIds, err := splitPlayletIdsByOriginal(ctx, ids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
if resetErr := resetOriginalChaptersWatermark(ctx, originalIds, now); resetErr != nil {
|
|
return resetErr
|
|
}
|
|
return resetChaptersWatermark(ctx, otherIds, playletchaptermod.WatermarkStatusPending, now)
|
|
}
|
|
|
|
func splitPlayletIdsByOriginal(ctx context.Context, ids []int64) (originalIds, otherIds []int64, err *srvdef.SrvErr) {
|
|
originalList, err := playletmod.FindManyPlaylet(ctx, bson.M{
|
|
playletmod.FN.Playlet.Id: bson.M{"$in": ids},
|
|
playletmod.FN.Playlet.IsOriginal: true,
|
|
}, options.Find().SetProjection(bson.M{playletmod.FN.Playlet.Id: 1}))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
originalSet := make(map[int64]struct{}, len(originalList))
|
|
for _, item := range originalList {
|
|
if item == nil || item.Id <= 0 {
|
|
continue
|
|
}
|
|
originalSet[item.Id] = struct{}{}
|
|
originalIds = append(originalIds, item.Id)
|
|
}
|
|
for _, id := range ids {
|
|
if _, ok := originalSet[id]; !ok {
|
|
otherIds = append(otherIds, id)
|
|
}
|
|
}
|
|
return originalIds, otherIds, nil
|
|
}
|
|
|
|
func resetOriginalChaptersWatermark(ctx context.Context, playletIds []int64, now time.Time) *srvdef.SrvErr {
|
|
if len(playletIds) == 0 {
|
|
return nil
|
|
}
|
|
// 原创剧集保留 mediaSourceId / videoUrl / mediaTaskId,以及已有水印地址。
|
|
// 新水印完成回写时会直接覆盖 WatermarkVideoUrl。
|
|
err := playletchaptermod.UpdateManyPlayletChapter(ctx,
|
|
bson.M{playletchaptermod.FN.PlayletChapter.PlayletId: bson.M{"$in": playletIds}},
|
|
bson.M{"$set": bson.M{
|
|
playletchaptermod.FN.PlayletChapter.WatermarkStatus: playletchaptermod.WatermarkStatusRegistered,
|
|
playletchaptermod.FN.PlayletChapter.UpdatedAt: now,
|
|
}},
|
|
)
|
|
if err != nil && err.Code == srvdef.ErrNoData {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func resetChaptersWatermark(ctx context.Context, playletIds []int64, status playletchaptermod.WatermarkStatus, now time.Time) *srvdef.SrvErr {
|
|
if len(playletIds) == 0 {
|
|
return nil
|
|
}
|
|
err := playletchaptermod.UpdateManyPlayletChapter(ctx,
|
|
bson.M{playletchaptermod.FN.PlayletChapter.PlayletId: bson.M{"$in": playletIds}},
|
|
bson.M{"$set": bson.M{
|
|
playletchaptermod.FN.PlayletChapter.WatermarkStatus: status,
|
|
playletchaptermod.FN.PlayletChapter.MediaSourceId: "",
|
|
playletchaptermod.FN.PlayletChapter.MediaTaskId: "",
|
|
playletchaptermod.FN.PlayletChapter.UpdatedAt: now,
|
|
}},
|
|
)
|
|
if err != nil && err.Code == srvdef.ErrNoData {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ListPlayletChapter 分页查询短剧剧集列表
|
|
func ListPlayletChapter(ctx context.Context, req *proto.PlayletChapterListReq) (*proto.PlayletChapterListPageResp, *srvdef.SrvErr) {
|
|
if req == nil {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
filter := bson.M{}
|
|
if req.Id != nil && *req.Id > 0 {
|
|
filter[playletchaptermod.FN.PlayletChapter.Id] = *req.Id
|
|
}
|
|
if req.PlayletId != nil && *req.PlayletId > 0 {
|
|
filter[playletchaptermod.FN.PlayletChapter.PlayletId] = *req.PlayletId
|
|
}
|
|
if req.WatermarkStatus != nil {
|
|
filter[playletchaptermod.FN.PlayletChapter.WatermarkStatus] = *req.WatermarkStatus
|
|
}
|
|
pageNum, pageSize := normalizePage(req.PageNum, req.PageSize)
|
|
totalCount, err := playletchaptermod.CountPlayletChapter(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp := &proto.PlayletChapterListPageResp{
|
|
BasePageResp: response.BasePageResp{
|
|
TotalCount: totalCount,
|
|
PageSize: int(pageSize),
|
|
PageNum: int(pageNum),
|
|
List: make([]*proto.PlayletChapterListResp, 0),
|
|
},
|
|
}
|
|
if totalCount == 0 {
|
|
return resp, nil
|
|
}
|
|
list, err := playletchaptermod.FindManyPlayletChapter(ctx, filter, options.Find().
|
|
SetSort(bson.D{
|
|
{Key: playletchaptermod.FN.PlayletChapter.CurrentEpisode, Value: 1},
|
|
{Key: playletchaptermod.FN.PlayletChapter.Id, Value: 1},
|
|
}).
|
|
SetSkip((pageNum-1)*pageSize).
|
|
SetLimit(pageSize))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]*proto.PlayletChapterListResp, 0, len(list))
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
items = append(items, chapterToWebResp(item))
|
|
}
|
|
resp.List = items
|
|
return resp, nil
|
|
}
|
|
|
|
// AddPlayletChapter 新增短剧剧集
|
|
func AddPlayletChapter(ctx context.Context, req *proto.PlayletChapterAddReq) (*proto.PlayletChapterAddResp, *srvdef.SrvErr) {
|
|
if req == nil || req.PlayletId <= 0 || req.CurrentEpisode <= 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "playletId/currentEpisode无效", "")
|
|
}
|
|
if !isValidPlayletChapterPayType(req.PayType) {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "支付方式无效", "")
|
|
}
|
|
if req.PayType == playletchaptermod.PayTypeCoin && req.SingleCoins <= 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "金币支付时单集金币必须大于0", "")
|
|
}
|
|
singleCoins := req.SingleCoins
|
|
if req.PayType != playletchaptermod.PayTypeCoin {
|
|
singleCoins = 0
|
|
}
|
|
playlet, err := playletmod.FindOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: req.PlayletId})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if playlet == nil || !playlet.IsOriginal {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "只有原创短剧支持新增剧集", "")
|
|
}
|
|
doc := &playletchaptermod.PlayletChapter{
|
|
PlayletId: req.PlayletId,
|
|
CurrentEpisode: req.CurrentEpisode,
|
|
Title: strings.TrimSpace(req.Title),
|
|
Md5: strings.TrimSpace(req.Md5),
|
|
Hash: strings.TrimSpace(req.Hash),
|
|
Height: req.Height,
|
|
Width: req.Width,
|
|
Ratio: req.Ratio,
|
|
VideoUrl: strings.TrimSpace(req.VideoUrl),
|
|
MediaSize: req.MediaSize,
|
|
MediaTime: req.MediaTime,
|
|
PayType: req.PayType,
|
|
SingleCoins: singleCoins,
|
|
}
|
|
mediaSourceId := strings.TrimSpace(req.MediaSourceId)
|
|
if playlet.IsOriginal && (doc.VideoUrl != "" || mediaSourceId != "") {
|
|
doc.WatermarkStatus = playletchaptermod.WatermarkStatusRegistered
|
|
if mediaSourceId != "" {
|
|
// 后台上传正片已在 media 创建原片,直接沿用该 ID,避免再 SyncMediaFile 重复处理。
|
|
doc.MediaSourceId = mediaSourceId
|
|
} else {
|
|
sourceID, regErr := playletwatermark.RegisterChapterSource(doc)
|
|
if regErr != nil {
|
|
log.Error("AddPlayletChapter RegisterChapterSource 失败",
|
|
log.Any("playletId", req.PlayletId),
|
|
log.Any("videoUrl", doc.VideoUrl),
|
|
log.E(regErr))
|
|
} else {
|
|
doc.MediaSourceId = sourceID
|
|
}
|
|
}
|
|
}
|
|
if err := playletchaptermod.InsertOnePlayletChapter(ctx, doc); err != nil {
|
|
return nil, err
|
|
}
|
|
return &proto.PlayletChapterAddResp{Id: doc.Id}, nil
|
|
}
|
|
|
|
// DelPlayletChapter 单/批量删除原创短剧剧集。
|
|
func DelPlayletChapter(ctx context.Context, req *proto.PlayletChapterDelReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "剧集ID不能为空", "")
|
|
}
|
|
filter := bson.M{playletchaptermod.FN.PlayletChapter.Id: bson.M{"$in": ids}}
|
|
chapters, findErr := playletchaptermod.FindManyPlayletChapter(ctx, filter)
|
|
if findErr != nil {
|
|
return findErr
|
|
}
|
|
if len(chapters) == 0 {
|
|
return srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
playletIds := make([]int64, 0, len(chapters))
|
|
seenPlaylet := make(map[int64]struct{}, len(chapters))
|
|
for _, item := range chapters {
|
|
if item == nil || item.PlayletId <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seenPlaylet[item.PlayletId]; ok {
|
|
continue
|
|
}
|
|
seenPlaylet[item.PlayletId] = struct{}{}
|
|
playletIds = append(playletIds, item.PlayletId)
|
|
}
|
|
if len(playletIds) > 0 {
|
|
playlets, playletErr := playletmod.FindManyPlaylet(ctx, bson.M{
|
|
playletmod.FN.Playlet.Id: bson.M{"$in": playletIds},
|
|
}, options.Find().SetProjection(bson.M{
|
|
playletmod.FN.Playlet.Id: 1,
|
|
playletmod.FN.Playlet.IsOriginal: 1,
|
|
}))
|
|
if playletErr != nil {
|
|
return playletErr
|
|
}
|
|
for _, item := range playlets {
|
|
if item == nil || !item.IsOriginal {
|
|
return srvdef.New(srvdef.ErrParamError, "只有原创短剧支持删除剧集", "")
|
|
}
|
|
}
|
|
}
|
|
count, err := playletchaptermod.DelManyPlayletChapter(ctx, filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return srvdef.New(srvdef.ErrNoData, "", "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdatePlayletChapter 编辑短剧剧集
|
|
func UpdatePlayletChapter(ctx context.Context, req *proto.PlayletChapterUpdateReq) *srvdef.SrvErr {
|
|
if req == nil || req.Id <= 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "id无效", "")
|
|
}
|
|
setDoc := bson.M{playletchaptermod.FN.PlayletChapter.UpdatedAt: time.Now()}
|
|
if req.CurrentEpisode != nil {
|
|
if *req.CurrentEpisode <= 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "集数无效", "")
|
|
}
|
|
setDoc[playletchaptermod.FN.PlayletChapter.CurrentEpisode] = *req.CurrentEpisode
|
|
}
|
|
if req.Title != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Title] = strings.TrimSpace(*req.Title)
|
|
}
|
|
if req.Md5 != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Md5] = strings.TrimSpace(*req.Md5)
|
|
}
|
|
if req.Hash != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Hash] = strings.TrimSpace(*req.Hash)
|
|
}
|
|
if req.Height != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Height] = *req.Height
|
|
}
|
|
if req.Width != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Width] = *req.Width
|
|
}
|
|
if req.Ratio != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.Ratio] = *req.Ratio
|
|
}
|
|
if req.VideoUrl != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.VideoUrl] = strings.TrimSpace(*req.VideoUrl)
|
|
}
|
|
if req.MediaSize != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.MediaSize] = *req.MediaSize
|
|
}
|
|
if req.MediaTime != nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.MediaTime] = *req.MediaTime
|
|
}
|
|
if req.PayType != nil {
|
|
if !isValidPlayletChapterPayType(*req.PayType) {
|
|
return srvdef.New(srvdef.ErrParamError, "支付方式无效", "")
|
|
}
|
|
setDoc[playletchaptermod.FN.PlayletChapter.PayType] = *req.PayType
|
|
if *req.PayType != playletchaptermod.PayTypeCoin && req.SingleCoins == nil {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.SingleCoins] = int64(0)
|
|
}
|
|
}
|
|
if req.SingleCoins != nil {
|
|
if *req.SingleCoins < 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "单集金币不能小于0", "")
|
|
}
|
|
setDoc[playletchaptermod.FN.PlayletChapter.SingleCoins] = *req.SingleCoins
|
|
}
|
|
if len(setDoc) <= 1 {
|
|
return srvdef.New(srvdef.ErrParamError, "没有可更新字段", "")
|
|
}
|
|
return playletchaptermod.UpdateOnePlayletChapter(ctx,
|
|
bson.M{playletchaptermod.FN.PlayletChapter.Id: req.Id},
|
|
bson.M{"$set": setDoc},
|
|
)
|
|
}
|
|
|
|
// SetPlayletChapterPayType 单/批量设置短剧剧集支付方式
|
|
func SetPlayletChapterPayType(ctx context.Context, req *proto.PlayletChapterPayTypeSetReq) *srvdef.SrvErr {
|
|
if req == nil {
|
|
return srvdef.New(srvdef.ErrParamError, "参数不能为空", "")
|
|
}
|
|
ids := dedupePositiveInt64Ids(req.Ids)
|
|
if len(ids) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "剧集ID不能为空", "")
|
|
}
|
|
if !isValidPlayletChapterPayType(req.PayType) {
|
|
return srvdef.New(srvdef.ErrParamError, "支付方式无效", "")
|
|
}
|
|
setDoc := bson.M{
|
|
playletchaptermod.FN.PlayletChapter.PayType: req.PayType,
|
|
playletchaptermod.FN.PlayletChapter.UpdatedAt: time.Now(),
|
|
}
|
|
if req.PayType == playletchaptermod.PayTypeCoin {
|
|
if req.SingleCoins == nil || *req.SingleCoins <= 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "金币支付时单集金币必须大于0", "")
|
|
}
|
|
setDoc[playletchaptermod.FN.PlayletChapter.SingleCoins] = *req.SingleCoins
|
|
} else {
|
|
setDoc[playletchaptermod.FN.PlayletChapter.SingleCoins] = int64(0)
|
|
}
|
|
return playletchaptermod.UpdateManyPlayletChapter(ctx,
|
|
bson.M{playletchaptermod.FN.PlayletChapter.Id: bson.M{"$in": ids}},
|
|
bson.M{"$set": setDoc},
|
|
)
|
|
}
|
|
|
|
func buildPlayletListFilter(req *proto.PlayletListReq) bson.M {
|
|
filter := bson.M{}
|
|
if req.Id != nil && *req.Id > 0 {
|
|
filter[playletmod.FN.Playlet.Id] = *req.Id
|
|
}
|
|
if req.CategoryId != nil && *req.CategoryId > 0 {
|
|
filter[playletmod.FN.Playlet.CategoryIds] = bson.M{"$in": []int64{*req.CategoryId}}
|
|
}
|
|
if req.TagId != nil && *req.TagId > 0 {
|
|
filter[playletmod.FN.Playlet.TagIds] = bson.M{"$in": []int64{*req.TagId}}
|
|
}
|
|
if req.ThemeId != nil && *req.ThemeId > 0 {
|
|
filter[playletmod.FN.Playlet.ThemeIds] = bson.M{"$in": []int64{*req.ThemeId}}
|
|
}
|
|
if req.ContentType != nil {
|
|
filter[playletmod.FN.Playlet.ContentType] = *req.ContentType
|
|
}
|
|
if req.PayType != nil {
|
|
filter[playletmod.FN.Playlet.PayType] = *req.PayType
|
|
}
|
|
if req.Status != nil {
|
|
filter[playletmod.FN.Playlet.Status] = *req.Status
|
|
}
|
|
if req.IsFeatured != nil {
|
|
filter[playletmod.FN.Playlet.IsFeatured] = *req.IsFeatured
|
|
}
|
|
if req.IsOriginal != nil {
|
|
if *req.IsOriginal {
|
|
filter[playletmod.FN.Playlet.IsOriginal] = true
|
|
} else {
|
|
// 历史入库数据可能没有该字段,按非原创处理
|
|
filter[playletmod.FN.Playlet.IsOriginal] = bson.M{"$ne": true}
|
|
}
|
|
}
|
|
if req.ShowOriginal != nil {
|
|
if *req.ShowOriginal {
|
|
filter[playletmod.FN.Playlet.ShowOriginal] = true
|
|
} else {
|
|
filter[playletmod.FN.Playlet.ShowOriginal] = bson.M{"$ne": true}
|
|
}
|
|
}
|
|
if req.IsRecommend != nil {
|
|
filter[playletmod.FN.Playlet.IsRecommend] = *req.IsRecommend
|
|
}
|
|
if req.ShowOnBanner != nil {
|
|
filter[playletmod.FN.Playlet.ShowOnBanner] = *req.ShowOnBanner
|
|
}
|
|
if title := strings.TrimSpace(req.Title); title != "" {
|
|
filter[playletmod.FN.Playlet.Title] = bson.M{"$regex": regexp.QuoteMeta(title)}
|
|
}
|
|
if operatorName := strings.TrimSpace(req.OperatorName); operatorName != "" {
|
|
filter[playletmod.FN.Playlet.OperatorName] = bson.M{"$regex": regexp.QuoteMeta(operatorName)}
|
|
}
|
|
return filter
|
|
}
|
|
|
|
func playletToWebResp(item *playletmod.Playlet, categoryNameMap map[int64]string, themeNameMap map[int64]string, tagNameMap map[int64]string) *proto.PlayletListResp {
|
|
return &proto.PlayletListResp{
|
|
Id: item.Id,
|
|
Status: item.Status,
|
|
IsFeatured: item.IsFeatured,
|
|
IsOriginal: item.IsOriginal,
|
|
ShowOriginal: item.ShowOriginal,
|
|
MediaBusiness: item.MediaBusiness,
|
|
IsRecommend: item.IsRecommend,
|
|
ShowOnBanner: item.ShowOnBanner,
|
|
Banner: item.Banner,
|
|
ReviewAt: item.ReviewAt,
|
|
OnlineAt: item.OnlineAt,
|
|
Title: item.Title,
|
|
Slug: item.Slug,
|
|
Desc: item.Desc,
|
|
Anchor: item.Anchor,
|
|
Cover: item.Cover,
|
|
CompressedCover: item.CompressedCover,
|
|
Preview: item.Preview,
|
|
CategoryIds: item.CategoryIds,
|
|
CategoryBinds: toProtoCategoryBinds(playletmod.EffectiveCategoryBinds(item)),
|
|
CategoryNames: categoryNamesFromMap(item.CategoryIds, categoryNameMap),
|
|
ThemeIds: item.ThemeIds,
|
|
ThemeNames: themeNamesFromMap(item.ThemeIds, themeNameMap),
|
|
TagIds: item.TagIds,
|
|
TagNames: tagNamesFromMap(item.TagIds, tagNameMap),
|
|
OldTags: normalizePlayletOldTags(item.OldTags),
|
|
ContentType: item.ContentType,
|
|
PayType: item.PayType,
|
|
SingleCoins: item.SingleCoins,
|
|
TotalEpisode: item.TotalEpisode,
|
|
FreeEpisodeCount: item.EffectiveFreeEpisodeCount(),
|
|
UpdateStatus: item.UpdateStatus,
|
|
TopRank: item.TopRank,
|
|
RecommendRank: item.RecommendRank,
|
|
CountComment: item.CountComment,
|
|
CountBuy: item.CountBuy,
|
|
CountBrowse: item.CountBrowse,
|
|
VirtualCountBrowse: item.VirtualCountBrowse,
|
|
DisplayCountBrowse: item.CountBrowse + item.VirtualCountBrowse,
|
|
HeatValue: item.HeatValue,
|
|
VirtualHeatValue: item.VirtualHeatValue,
|
|
DisplayHeatValue: item.HeatValue + item.VirtualHeatValue,
|
|
CountCollect: item.CountCollect,
|
|
CountLike: item.CountLike,
|
|
VirtualCountLike: item.VirtualCountLike,
|
|
DisplayCountLike: playletDisplayCountLike(item),
|
|
CountDisLike: item.CountDisLike,
|
|
SeoInfo: normalizeSeoValue(item.SeoInfo),
|
|
OperatorId: item.OperatorId,
|
|
OperatorName: item.OperatorName,
|
|
CreatedAt: item.CreatedAt,
|
|
UpdatedAt: item.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func validOfficialFreeEpisodeCount(count int64) bool {
|
|
return count == -1 || count >= 1 && count <= 1000
|
|
}
|
|
|
|
func playletDisplayCountLike(item *playletmod.Playlet) int64 {
|
|
if item == nil {
|
|
return 0
|
|
}
|
|
return item.CountLike + item.VirtualCountLike
|
|
}
|
|
|
|
func chapterToWebResp(item *playletchaptermod.PlayletChapter) *proto.PlayletChapterListResp {
|
|
if item == nil {
|
|
return nil
|
|
}
|
|
return &proto.PlayletChapterListResp{
|
|
Id: item.Id,
|
|
PlayletId: item.PlayletId,
|
|
MediaContentId: item.MediaContentId,
|
|
CurrentEpisode: item.CurrentEpisode,
|
|
Title: item.Title,
|
|
Md5: item.Md5,
|
|
Hash: item.Hash,
|
|
Height: item.Height,
|
|
Width: item.Width,
|
|
Ratio: item.Ratio,
|
|
VideoUrl: item.VideoUrl,
|
|
WatermarkVideoUrl: item.WatermarkVideoUrl,
|
|
MediaSize: item.MediaSize,
|
|
MediaTime: item.MediaTime,
|
|
CountComment: item.CountComment,
|
|
CountBuy: item.CountBuy,
|
|
CountBrowse: item.CountBrowse,
|
|
CountCollect: item.CountCollect,
|
|
CountLike: item.CountLike,
|
|
CountDisLike: item.CountDisLike,
|
|
PayType: item.PayType,
|
|
SingleCoins: item.SingleCoins,
|
|
WatermarkStatus: item.WatermarkStatus,
|
|
MediaSourceId: item.MediaSourceId,
|
|
MediaTaskId: item.MediaTaskId,
|
|
CreatedAt: item.CreatedAt,
|
|
UpdatedAt: item.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func buildCategoryNameMap(ctx context.Context, list playletmod.PlayletList) (map[int64]string, *srvdef.SrvErr) {
|
|
categoryIds := make([]int64, 0)
|
|
seen := make(map[int64]struct{})
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
for _, categoryId := range item.CategoryIds {
|
|
if categoryId <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[categoryId]; ok {
|
|
continue
|
|
}
|
|
seen[categoryId] = struct{}{}
|
|
categoryIds = append(categoryIds, categoryId)
|
|
}
|
|
}
|
|
result := make(map[int64]string, len(categoryIds))
|
|
if len(categoryIds) == 0 {
|
|
return result, nil
|
|
}
|
|
categories, err := playletcategorymod.FindManyPlayletCategory(ctx, bson.M{
|
|
playletcategorymod.FN.PlayletCategory.Id: bson.M{"$in": categoryIds},
|
|
}, options.Find().SetProjection(bson.M{
|
|
playletcategorymod.FN.PlayletCategory.Id: 1,
|
|
playletcategorymod.FN.PlayletCategory.Name: 1,
|
|
}))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, category := range categories {
|
|
if category != nil {
|
|
result[category.Id] = category.Name
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func categoryNamesFromMap(categoryIds []int64, categoryNameMap map[int64]string) []string {
|
|
result := make([]string, 0, len(categoryIds))
|
|
for _, categoryId := range categoryIds {
|
|
if name := categoryNameMap[categoryId]; name != "" {
|
|
result = append(result, name)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func buildThemeNameMap(ctx context.Context, list playletmod.PlayletList) (map[int64]string, *srvdef.SrvErr) {
|
|
themeIds := make([]int64, 0)
|
|
seen := make(map[int64]struct{})
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
for _, themeId := range item.ThemeIds {
|
|
if themeId <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[themeId]; ok {
|
|
continue
|
|
}
|
|
seen[themeId] = struct{}{}
|
|
themeIds = append(themeIds, themeId)
|
|
}
|
|
}
|
|
result := make(map[int64]string, len(themeIds))
|
|
if len(themeIds) == 0 {
|
|
return result, nil
|
|
}
|
|
themes, err := playletthememod.FindManyPlayletTheme(ctx, bson.M{
|
|
playletthememod.FN.PlayletTheme.Id: bson.M{"$in": themeIds},
|
|
}, options.Find().SetProjection(bson.M{
|
|
playletthememod.FN.PlayletTheme.Id: 1,
|
|
playletthememod.FN.PlayletTheme.Name: 1,
|
|
}))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, theme := range themes {
|
|
if theme != nil {
|
|
result[theme.Id] = theme.Name
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func themeNamesFromMap(themeIds []int64, themeNameMap map[int64]string) []string {
|
|
result := make([]string, 0, len(themeIds))
|
|
for _, themeId := range themeIds {
|
|
if name := themeNameMap[themeId]; name != "" {
|
|
result = append(result, name)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func buildTagNameMap(ctx context.Context, list playletmod.PlayletList) (map[int64]string, *srvdef.SrvErr) {
|
|
tagIds := make([]int64, 0)
|
|
seen := make(map[int64]struct{})
|
|
for _, item := range list {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
for _, tagId := range item.TagIds {
|
|
if tagId <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[tagId]; ok {
|
|
continue
|
|
}
|
|
seen[tagId] = struct{}{}
|
|
tagIds = append(tagIds, tagId)
|
|
}
|
|
}
|
|
result := make(map[int64]string, len(tagIds))
|
|
if len(tagIds) == 0 {
|
|
return result, nil
|
|
}
|
|
tags, err := contenttagmod.GetMapTags(ctx, tagIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tag := range tags {
|
|
if tag != nil {
|
|
result[tag.Id] = tag.Name
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func tagNamesFromMap(tagIds []int64, tagNameMap map[int64]string) []string {
|
|
result := make([]string, 0, len(tagIds))
|
|
for _, tagId := range tagIds {
|
|
if name := tagNameMap[tagId]; name != "" {
|
|
result = append(result, name)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalizePlayletOldTags(oldTags []string) []string {
|
|
if oldTags == nil {
|
|
return make([]string, 0)
|
|
}
|
|
return oldTags
|
|
}
|
|
|
|
func dedupePositiveInt64Ids(ids []int64) []int64 {
|
|
result := make([]int64, 0, len(ids))
|
|
seen := make(map[int64]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
|
|
}
|
|
|
|
func isValidPlayletStatus(status playletmod.Status) bool {
|
|
return status == playletmod.StatusDown || status == playletmod.StatusUp
|
|
}
|
|
|
|
func isValidPlayletPayType(payType playletmod.PayType) bool {
|
|
return payType == playletmod.PayTypeFree ||
|
|
payType == playletmod.PayTypeCoin ||
|
|
payType == playletmod.PayTypeVip
|
|
}
|
|
|
|
func isValidPlayletChapterPayType(payType playletchaptermod.PayType) bool {
|
|
return payType == playletchaptermod.PayTypeFree ||
|
|
payType == playletchaptermod.PayTypeCoin ||
|
|
payType == playletchaptermod.PayTypeVip
|
|
}
|
|
|
|
func isValidPlayletContentType(contentType playletmod.ContentType) bool {
|
|
return contentType == playletmod.ContentTypeTheater ||
|
|
contentType == playletmod.ContentTypeAlbum ||
|
|
contentType == playletmod.ContentTypeComic ||
|
|
contentType == playletmod.ContentTypeAnime ||
|
|
contentType == playletmod.ContentTypeManga
|
|
}
|
|
|
|
func normalizePlayletPaySetting(payType playletmod.PayType, singleCoins *int64, requireCoinAmount bool) (playletmod.PayType, int64, *srvdef.SrvErr) {
|
|
if !isValidPlayletPayType(payType) {
|
|
return 0, 0, srvdef.New(srvdef.ErrParamError, "支付方式无效", "")
|
|
}
|
|
if payType == playletmod.PayTypeCoin {
|
|
if requireCoinAmount && (singleCoins == nil || *singleCoins <= 0) {
|
|
return 0, 0, srvdef.New(srvdef.ErrParamError, "金币支付时单集金币必须大于0", "")
|
|
}
|
|
if singleCoins == nil {
|
|
return payType, 0, nil
|
|
}
|
|
return payType, *singleCoins, nil
|
|
}
|
|
return payType, 0, nil
|
|
}
|
|
|
|
func normalizePlayletTagIds(tagIds []int64) ([]int64, *srvdef.SrvErr) {
|
|
result := make([]int64, 0, len(tagIds))
|
|
seen := make(map[int64]struct{}, len(tagIds))
|
|
for _, tagId := range tagIds {
|
|
if tagId <= 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "标签ID无效", "")
|
|
}
|
|
if _, ok := seen[tagId]; ok {
|
|
continue
|
|
}
|
|
seen[tagId] = struct{}{}
|
|
result = append(result, tagId)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func validatePlayletTagIds(ctx context.Context, tagIds []int64) *srvdef.SrvErr {
|
|
if len(tagIds) == 0 {
|
|
return nil
|
|
}
|
|
return srv_contenttag.ValidateContentTags(ctx, tagIds, contenttagmod.ContentTagTypePlaylet)
|
|
}
|
|
|
|
// resolveAddCategoryBinds 新增短剧时根据分类绑定或分类ID生成排序配置
|
|
func resolveAddCategoryBinds(binds []proto.PlayletCategoryBind, ids []int64) ([]playletmod.PlayletCategoryBind, *srvdef.SrvErr) {
|
|
if len(binds) > 0 {
|
|
return resolveCategoryBinds(&binds, nil, nil)
|
|
}
|
|
return resolveCategoryBinds(nil, &ids, nil)
|
|
}
|
|
|
|
// resolveCategoryBinds 保留旧分类排序,新增分类默认排序为0
|
|
func resolveCategoryBinds(binds *[]proto.PlayletCategoryBind, ids *[]int64, old []playletmod.PlayletCategoryBind) ([]playletmod.PlayletCategoryBind, *srvdef.SrvErr) {
|
|
if binds != nil {
|
|
result := make([]playletmod.PlayletCategoryBind, 0, len(*binds))
|
|
seen := make(map[int64]bool, len(*binds))
|
|
for _, bind := range *binds {
|
|
if bind.Id <= 0 || seen[bind.Id] {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "分类ID无效或重复", "")
|
|
}
|
|
seen[bind.Id] = true
|
|
result = append(result, playletmod.PlayletCategoryBind{Id: bind.Id, Rank: bind.Rank})
|
|
}
|
|
return result, nil
|
|
}
|
|
if ids != nil {
|
|
categoryIds, err := normalizePlayletCategoryIds(*ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return playletmod.MergeCategoryBindsByIds(old, categoryIds), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func toProtoCategoryBinds(binds []playletmod.PlayletCategoryBind) []proto.PlayletCategoryBind {
|
|
result := make([]proto.PlayletCategoryBind, 0, len(binds))
|
|
for _, bind := range binds {
|
|
result = append(result, proto.PlayletCategoryBind{Id: bind.Id, Rank: bind.Rank})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalizePlayletCategoryIds(categoryIds []int64) ([]int64, *srvdef.SrvErr) {
|
|
result := make([]int64, 0, len(categoryIds))
|
|
seen := make(map[int64]struct{}, len(categoryIds))
|
|
for _, categoryId := range categoryIds {
|
|
if categoryId <= 0 {
|
|
return nil, srvdef.New(srvdef.ErrParamError, "分类ID无效", "")
|
|
}
|
|
if _, ok := seen[categoryId]; ok {
|
|
continue
|
|
}
|
|
seen[categoryId] = struct{}{}
|
|
result = append(result, categoryId)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func validatePlayletCategoryIds(ctx context.Context, categoryIds []int64) *srvdef.SrvErr {
|
|
if len(categoryIds) == 0 {
|
|
return nil
|
|
}
|
|
count, err := playletcategorymod.CountPlayletCategory(ctx, bson.M{
|
|
playletcategorymod.FN.PlayletCategory.Id: bson.M{"$in": categoryIds},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(categoryIds)) {
|
|
return srvdef.New(srvdef.ErrParamError, "分类不存在", "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validatePlayletThemeIds(ctx context.Context, themeIds []int64) *srvdef.SrvErr {
|
|
if len(themeIds) == 0 {
|
|
return srvdef.New(srvdef.ErrParamError, "题材ID不能为空", "")
|
|
}
|
|
count, err := playletthememod.CountPlayletTheme(ctx, bson.M{
|
|
playletthememod.FN.PlayletTheme.Id: bson.M{"$in": themeIds},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count != int64(len(themeIds)) {
|
|
return srvdef.New(srvdef.ErrParamError, "题材不存在", "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func playletOnlineAt(status playletmod.Status, now time.Time) time.Time {
|
|
if status == playletmod.StatusUp {
|
|
return now
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
func resolvePlayletOperator(ctx context.Context, operatorId int64) (int64, string) {
|
|
if operatorId <= 0 {
|
|
return 0, ""
|
|
}
|
|
acc, err := mwacc.FindOneAccByFilter(ctx, bson.M{
|
|
mwacc.FN.MwAcc.Id: operatorId,
|
|
}, 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 operatorId, ""
|
|
}
|
|
return acc.Id, acc.UserName
|
|
}
|
|
|
|
func asyncSyncPlayletESByStatus(ids []int64, status playletmod.Status, action string) {
|
|
syncIds := append([]int64(nil), ids...)
|
|
go func() {
|
|
if err1 := srv_search.SyncPlayletStatusToES(context.Background(), syncIds, status); err1 != nil {
|
|
log.Error(action+"同步ES失败", log.Any("短剧ID", syncIds), log.Any("状态", status), log.E(err1))
|
|
}
|
|
}()
|
|
}
|
|
|
|
func asyncSyncPlayletESByLatestStatus(ids []int64, action string) {
|
|
syncIds := append([]int64(nil), ids...)
|
|
go func() {
|
|
if err1 := srv_search.SyncPlayletsToESByLatestStatus(context.Background(), syncIds); err1 != nil {
|
|
log.Error(action+"同步ES失败", log.Any("短剧ID", syncIds), log.E(err1))
|
|
}
|
|
}()
|
|
}
|
|
|
|
func randomUniqueVirtualLikeValues(min int64, max int64, count int) []int64 {
|
|
if count <= 0 {
|
|
return nil
|
|
}
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
result := make([]int64, 0, count)
|
|
selected := make(map[int64]struct{}, count)
|
|
start := max - int64(count) + 1
|
|
for offset := int64(0); offset < int64(count); offset++ {
|
|
current := start + offset
|
|
picked := min + rng.Int63n(current-min+1)
|
|
if _, ok := selected[picked]; ok {
|
|
picked = current
|
|
}
|
|
selected[picked] = struct{}{}
|
|
result = append(result, picked)
|
|
}
|
|
return result
|
|
}
|