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