@@ -0,0 +1,524 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"91porn-server/models/commod"
|
||||
"91porn-server/models/v/locmod"
|
||||
"91porn-server/models/v/usermod"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
var TotalWatchCount uint64 = 5 //非vip用户每日总观看次数
|
||||
|
||||
const (
|
||||
SubmitVideoPlayTimeLimit = 10 //上传视频最短播放时间为10秒
|
||||
TodayRank = 0
|
||||
WeekRank = 1
|
||||
MonthRank = 2
|
||||
|
||||
TypeNewst = 0
|
||||
TypeHotest = 1
|
||||
TypeSameCity = 2
|
||||
TypePay = 3
|
||||
TypeVip = 4
|
||||
|
||||
CommonUp = 0
|
||||
MadouUp = 1
|
||||
|
||||
Version string = "1.0.0" // 帖子兼容老版本
|
||||
)
|
||||
|
||||
// BaseVid4Aws 上传到AWS的基础信息
|
||||
type BaseVid4Aws struct {
|
||||
ID string `json:"id"`
|
||||
CheckSum string `json:"checkSum"`
|
||||
Title string `json:"title"`
|
||||
Actors []string `json:"actors"`
|
||||
AddedTime string `json:"addedTime"`
|
||||
PlayTime uint64 `json:"playTime"`
|
||||
Tags []string `json:"tags"`
|
||||
Size int `json:"size"`
|
||||
Filename string `json:"filename"`
|
||||
Desc string `json:"desc"`
|
||||
Type string `json:"type"`
|
||||
Director string `json:"director"`
|
||||
Studio string `json:"studio"`
|
||||
Bango string `json:"bango"`
|
||||
Via string `json:"via"`
|
||||
}
|
||||
|
||||
// CoverInfo4Aws 上传到AWS的基础信息
|
||||
type CoverInfo4Aws struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Tags []string `json:"tags"`
|
||||
CoverImg string `json:"coverImg"`
|
||||
SeriesCover []string `json:"seriesCover"`
|
||||
Status string `json:"status"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Via string `json:"via"`
|
||||
Topic []string `json:"topic"`
|
||||
NewUpdateAt string `json:"newUpdateAt"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// LocVideoReq 同城视频请求
|
||||
type LocVideoReq struct {
|
||||
City string `form:"city" json:"city"`
|
||||
commod.Page
|
||||
}
|
||||
|
||||
// VideoReq 视频详情请求
|
||||
type VideoReq struct {
|
||||
VideoID string `form:"videoID" json:"videoID"`
|
||||
SearchAccessToken string `form:"searchAccessToken" json:"searchAccessToken"`
|
||||
}
|
||||
|
||||
// SubmitReq 视频发布请求
|
||||
type SubmitReq struct {
|
||||
UID uint64 `form:"uid" json:"uid"`
|
||||
NewsType string `form:"newsType" json:"newsType"`
|
||||
Title string `form:"title" json:"title"`
|
||||
Content string `form:"content" json:"content"`
|
||||
Tags []string `form:"tags" json:"tags" binding:"required"`
|
||||
PlayTime uint `form:"playTime" json:"playTime"`
|
||||
Cover string `form:"cover" json:"cover"`
|
||||
CoverThumb string `form:"coverThumb" json:"coverThumb"`
|
||||
SeriesCover []string `form:"seriesCover" json:"seriesCover"`
|
||||
Via string `form:"via" json:"via"`
|
||||
Coins int64 `form:"coins" json:"coins"`
|
||||
Size int `form:"size" json:"size"`
|
||||
Resolution string `form:"resolution" json:"resolution"`
|
||||
Ratio float64 `json:"ratio" bson:"ratio"` //宽高比
|
||||
MimeType string `form:"mimeType" json:"mimeType"`
|
||||
Location locmod.Location `form:"location" json:"location"`
|
||||
Actor string `form:"actor" json:"actor"`
|
||||
SourceID string `form:"sourceID" json:"sourceID"`
|
||||
SourceURL string `form:"sourceURL" json:"sourceURL"`
|
||||
MD5 string `form:"md5" json:"md5"`
|
||||
Filename string `form:"filename" json:"filename"`
|
||||
FreeTime int `form:"freeTime" json:"freeTime"`
|
||||
IsActivity bool `form:"isActivity" json:"isActivity"` // 是否是参赛作品
|
||||
}
|
||||
|
||||
// PlayReq 播放请求
|
||||
type PlayReq struct {
|
||||
VideoID string `form:"videoID" json:"videoID"`
|
||||
Longer int `form:"longer" json:"longer"`
|
||||
Progress int `form:"progress" json:"progress"`
|
||||
PlayWay int `form:"playWay" json:"playWay"`
|
||||
Via int `form:"via" json:"via"`
|
||||
TagID string `form:"tagID" json:"tagID"`
|
||||
//作者
|
||||
Publisher uint64 `form:"publisher" json:"publisher"`
|
||||
}
|
||||
|
||||
// LocReq 地址位置请求参数
|
||||
type LocReq struct {
|
||||
ID string `form:"id" json:"id"`
|
||||
}
|
||||
|
||||
// LocInfo 城市信息
|
||||
type LocInfo struct {
|
||||
ID primitive.ObjectID `json:"id"`
|
||||
//城市
|
||||
City string `json:"city"`
|
||||
//封面
|
||||
Cover string `json:"cover"`
|
||||
//访问人数
|
||||
Visit int `json:"visit"`
|
||||
//创建时间
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// LocResp 地址位置应答参数
|
||||
type LocResp struct {
|
||||
LocInfo
|
||||
}
|
||||
|
||||
// LocVideoResp 同城视频应答
|
||||
type LocVideoResp struct {
|
||||
List []*VideoInfo `json:"list"`
|
||||
HasNext bool `json:"hasNext"`
|
||||
}
|
||||
|
||||
// LocationResp 接受地理位置信息的结构
|
||||
type LocationResp struct {
|
||||
Code int `json:"code"`
|
||||
CountryName string `json:"country_name"`
|
||||
RegionName string `json:"region_name"`
|
||||
CityName string `json:"city_name"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
// TagInfo 标签信息
|
||||
type TagInfo struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"` //标签id
|
||||
Name string `json:"name" bson:"tagName"` //标签名字
|
||||
CoverImg string `json:"coverImg" bson:"coverImg"` //封面图片
|
||||
Description string `json:"description" bson:"description"` //文字说明
|
||||
PlayCount int64 `json:"playCount" bson:"tPlayCount"` //播放量
|
||||
HasCollected bool `json:"hasCollected" bson:"hasCollected"` //已收藏
|
||||
}
|
||||
|
||||
// VideoStatus 视频状态
|
||||
type VideoStatus struct {
|
||||
//已支付
|
||||
HasPaid bool `json:"hasPaid" bson:"hasPaid"`
|
||||
//每日视频状态信息
|
||||
TodayRank int `json:"todayRank" bson:"todayRank"`
|
||||
//每日播放次数
|
||||
TodayPlayCnt int `json:"todayPlayCnt" bson:"todayPlayCnt"`
|
||||
//已点赞
|
||||
HasLiked bool `json:"hasLiked" bson:"hasLiked"`
|
||||
//已收藏
|
||||
HasCollected bool `json:"hasCollected" bson:"hasCollected"`
|
||||
}
|
||||
|
||||
// UInfo 别名
|
||||
type UInfo = usermod.BaseInfoVip
|
||||
|
||||
// Publisher 发布者的信息
|
||||
type Publisher struct {
|
||||
UInfo
|
||||
//是否关注
|
||||
HasFollowed bool `json:"hasFollowed" bson:"hasFollowed"`
|
||||
}
|
||||
|
||||
// VideoBase 返回视频的基本信息
|
||||
type VideoBase struct {
|
||||
//id
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"`
|
||||
//帖子类型, SP,视频帖子,COVER
|
||||
NewsType string `json:"newsType" bson:"newsType"`
|
||||
//视频标题
|
||||
Title string `json:"title" bson:"title"`
|
||||
Content string `json:"content" bson:"content"` // 视频内容
|
||||
//视频标签
|
||||
Tags []TagInfo `json:"tags" bson:"tags"`
|
||||
//视频资源地址Path
|
||||
SourceURL string `json:"sourceURL" bson:"sourceURL"`
|
||||
// H.265 视频资源地址
|
||||
H265Url string `json:"h265Url" bson:"h265Url"`
|
||||
// 预览视频资源地址(并非所有视频都有预览)
|
||||
PreviewURL string `json:"previewURL" bson:"previewURL"`
|
||||
//广告跳转连接 目前只有广告帖子有用
|
||||
LinkUrl string `json:"linkUrl,omitempty" bson:"linkUrl,omitempty"`
|
||||
//影片长度
|
||||
PlayTime uint `json:"playTime" bson:"playTime"`
|
||||
//封面大图
|
||||
Cover string `json:"cover" bson:"cover"`
|
||||
//封⾯小图
|
||||
CoverThumb string `json:"coverThumb" bson:"coverThumb"`
|
||||
//帖子套图
|
||||
SeriesCover []string `json:"seriesCover" bson:"seriesCover"`
|
||||
//总播放量
|
||||
PlayCount int `json:"playCount" bson:"playCount"`
|
||||
//视频购买人数
|
||||
PurchaseCount int `json:"purchaseCount" bson:"purchaseCount"`
|
||||
//点赞数
|
||||
LikeCount int `json:"likeCount" bson:"likeCount"`
|
||||
//评论数
|
||||
CommentCount int `json:"commentCount" bson:"commentCount"`
|
||||
//分享数
|
||||
ShareCount int `json:"shareCount" bson:"shareCount"`
|
||||
//折扣后视频金币数(如果有的话)
|
||||
Coins int64 `json:"coins" bson:"coins"`
|
||||
//文件大小 byte
|
||||
Size int `json:"size" bson:"size"`
|
||||
//分辨率
|
||||
Resolution string `json:"resolution" bson:"resolution"`
|
||||
//宽高比
|
||||
Ratio float64 `json:"ratio" bson:"ratio"`
|
||||
//状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
||||
Status int `json:"status" bson:"status"`
|
||||
//视频未通过审核时的理由
|
||||
Reason string `json:"reason" bson:"reason"`
|
||||
//免费时长
|
||||
FreeTime int `json:"freeTime" bson:"freeTime"`
|
||||
//是否隐藏地址
|
||||
IsHideLocation bool `json:"isHideLocation" bson:"isHideLocation"`
|
||||
//免费专区
|
||||
FreeArea bool `json:"freeArea" bson:"freeArea"`
|
||||
//创建时间
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
//审核时间
|
||||
ReviewAt time.Time `json:"reviewAt" bson:"reviewAt,omitempty"`
|
||||
//是否置顶
|
||||
IsTopping bool `json:"isTopping" bson:"isTopping"`
|
||||
//力荐
|
||||
IsRecommend bool `json:"isRecommend" bson:"isRecommend"`
|
||||
//置精
|
||||
IsChoosen bool `json:"isChoosen" bson:"isChoosen"`
|
||||
//打赏金额
|
||||
Rewarded decimal.Decimal `json:"rewarded" bson:"rewarded"`
|
||||
//视频原金币数
|
||||
OriginCoins int64 `json:"originCoins" bson:"originCoins"`
|
||||
CollectCount int `json:"collectCount" bson:"collectCount"` // 收藏数
|
||||
PageViewCount int64 `json:"pageViewCount" bson:"pageViewCount"` // 视频页面展示次数
|
||||
SeedLinkUrl string `json:"seedLinkUrl" bson:"seedLinkUrl,omitempty"` //种子链接
|
||||
SeedSize uint64 `json:"seedSize" bson:"seedSize,omitempty"` // 种子影片大小 byte
|
||||
SeedPlayTime uint64 `json:"seedPlayTime" bson:"seedPlayTime,omitempty"` // 种子影片时长
|
||||
PreviewStart int `json:"previewStart" bson:"previewStart"` // 预览时间起始点
|
||||
RichText string `json:"richText" bson:"richText"` // 富文本内容
|
||||
TimeNodeList []TimeNode `json:"timeNodeList" bson:"timeNodeList"` // 时间节点
|
||||
DownloadAllow int `json:"downloadAllow" son:"downloadAllow"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
ShowType int `json:"showType" bson:"showType"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
LsjId string `json:"lsjId" bson:"lsjId"` // 老司机ID
|
||||
SearchAccessToken string `json:"searchAccessToken,omitempty" bson:"-"`
|
||||
ShowFreeTrialBadge bool `json:"showFreeTrialBadge" bson:"-"` // 是否展示免费试看角标
|
||||
FreeTrialRemaining uint64 `json:"freeTrialRemaining" bson:"-"` // 用户剩余免费观看次数
|
||||
CanUseFreeTrial bool `json:"canUseFreeTrial" bson:"-"` // 当前视频是否可使用免费观看次数
|
||||
}
|
||||
|
||||
// CommentInfo 评论信息
|
||||
type CommentInfo struct {
|
||||
//用户id
|
||||
UID uint64 `json:"uid"`
|
||||
//姓名
|
||||
Name string `json:"name"`
|
||||
//头像
|
||||
Portrait string `json:"portrait"`
|
||||
//评论id
|
||||
Cid string `json:"cid"`
|
||||
//评论内容
|
||||
Content string `json:"content"`
|
||||
///喜欢次数
|
||||
LikeCount int `json:"likeCount"`
|
||||
//是否作者
|
||||
IsAuthor bool `json:"isAuthor"`
|
||||
//创建时间
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// WatchModel 观看模型
|
||||
type WatchModel struct {
|
||||
///观看次数
|
||||
WatchCount uint64 `json:"watchCount"`
|
||||
//可观看
|
||||
IsWatch bool `json:"isWatch"`
|
||||
//是否免费
|
||||
IsFreeWatch bool `json:"isFreeWatch"`
|
||||
}
|
||||
|
||||
// WatchCountResp 用户免费观看次数响应。
|
||||
type WatchCountResp struct {
|
||||
IsCan bool `json:"isCan"`
|
||||
WatchCount uint64 `json:"watchCount"`
|
||||
TotalWatchCount uint64 `json:"totalWatchCount"`
|
||||
}
|
||||
|
||||
// WatchConsumeReq 消费免费观看视频次数请求。Vid用于兼容旧字段名。
|
||||
type WatchConsumeReq struct {
|
||||
VideoID string `json:"videoId"`
|
||||
Vid string `json:"vid"`
|
||||
}
|
||||
|
||||
func (r WatchConsumeReq) GetVideoID() string {
|
||||
if r.VideoID != "" {
|
||||
return r.VideoID
|
||||
}
|
||||
return r.Vid
|
||||
}
|
||||
|
||||
// WatchConsumeResp 消费免费观看视频次数响应。
|
||||
type WatchConsumeResp struct {
|
||||
IsCan bool `json:"isCan"`
|
||||
WatchCount uint64 `json:"watchCount"`
|
||||
TotalWatchCount uint64 `json:"totalWatchCount"`
|
||||
Consumed bool `json:"consumed"`
|
||||
}
|
||||
|
||||
// VideoInfo 返回的视频列表
|
||||
type VideoInfo struct {
|
||||
VideoBase
|
||||
UInfo Publisher `json:"publisher,omitempty"`
|
||||
Location LocInfo `json:"location,omitempty"`
|
||||
VidStatus VideoStatus `json:"vidStatus,omitempty"`
|
||||
Comment CommentInfo `json:"comment,omitempty"`
|
||||
Watch WatchModel `json:"watch,omitempty"`
|
||||
SortCode int `json:"-"` // 对专题内视频排序
|
||||
HappinessPlazaTop int32 `json:"happinessPlazaTop"` //是否是幸福广场置顶
|
||||
DiscountAreaPrice int64 `json:"discountAreaPrice"` //折扣专区展示价格
|
||||
VideoTypeId string `json:"videoTypeId"` //视频分类ID
|
||||
VideoTypeName string `json:"videoTypeName"` //视频分类名称
|
||||
}
|
||||
|
||||
// VideoInfoResp 返回的视频列表
|
||||
type VideoInfoResp struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"` // ID
|
||||
NewsType string `json:"newsType" bson:"newsType"` // 帖子类型, SP,视频帖子,COVER
|
||||
Title string `json:"title" bson:"title"` // 视频标题
|
||||
SourceURL string `json:"sourceURL" bson:"sourceURL"` // 视频资源地址Path
|
||||
H265Url string `json:"h265Url" bson:"h265Url"` // H.265 视频资源地址
|
||||
LinkUrl string `json:"linkUrl,omitempty" bson:"linkUrl,omitempty"` // 广告跳转连接 目前只有广告帖子有用
|
||||
PlayTime uint `json:"playTime" bson:"playTime"` // 影片长度
|
||||
Cover string `json:"cover" bson:"cover"` // 封面大图
|
||||
SeriesCover []string `json:"seriesCover" bson:"seriesCover"` // 帖子套图
|
||||
PlayCount int `json:"playCount" bson:"playCount"` // 总播放量
|
||||
LikeCount int `json:"likeCount" bson:"likeCount"` // 点赞数
|
||||
PageViewCount int64 `json:"pageViewCount" bson:"pageViewCount"` // 视频页面展示次数
|
||||
CommentCount int `json:"commentCount" bson:"commentCount"` // 评论数
|
||||
Coins int64 `json:"coins" bson:"coins"` // 折扣后视频金币数(如果有的话)
|
||||
Size int `json:"size" bson:"size"` // 文件大小 byte
|
||||
Resolution string `json:"resolution" bson:"resolution"` // 分辨率
|
||||
Ratio float64 `json:"ratio" bson:"ratio"` // 宽高比
|
||||
Status int `json:"status" bson:"status"` // 状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
||||
Reason string `json:"reason" bson:"reason"` // 视频未通过审核时的理由
|
||||
FreeTime int `json:"freeTime" bson:"freeTime"` // 免费时长
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间
|
||||
ReviewAt time.Time `json:"reviewAt" bson:"reviewAt,omitempty"` // 审核时间
|
||||
IsTopping bool `json:"isTopping" bson:"isTopping"` // 是否置顶
|
||||
IsPopping bool `json:"isPopping" bson:"isPopping"` // 是否推广
|
||||
IsRecommend bool `json:"isRecommend" bson:"isRecommend"` // 力荐
|
||||
IsChoosen bool `json:"isChoosen" bson:"isChoosen"` // 置精
|
||||
Rewarded decimal.Decimal `json:"rewarded" bson:"rewarded"` // 打赏金额
|
||||
OriginCoins int64 `json:"originCoins" bson:"originCoins"` // 视频原金币数
|
||||
TotalWorks int64 `json:"totalWorks" bson:"totalWorks"` // 总作品数
|
||||
Chosen bool `json:"chosen" bson:"chosen"` // 是否精选
|
||||
UInfo AppPublisherResp `json:"publisher,omitempty"` // 用户信息
|
||||
VidStatus VideoStatus `json:"vidStatus,omitempty"` // 视频状态
|
||||
Watch WatchModel `json:"watch,omitempty"` // 是否观看
|
||||
DownloadAllow int `json:"downloadAllow"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
ShowType int `json:"showType"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
FreeArea bool `json:"freeArea" bson:"freeArea"` // 免费专区
|
||||
SearchAccessToken string `json:"searchAccessToken,omitempty" bson:"-"`
|
||||
ShowFreeTrialBadge bool `json:"showFreeTrialBadge" bson:"-"` // 是否展示免费试看角标
|
||||
FreeTrialRemaining uint64 `json:"freeTrialRemaining" bson:"-"` // 用户剩余免费观看次数
|
||||
CanUseFreeTrial bool `json:"canUseFreeTrial" bson:"-"` // 当前视频是否可使用免费观看次数
|
||||
//视频标签
|
||||
Tags []TagInfo `json:"tags" bson:"tags"`
|
||||
}
|
||||
|
||||
// AppPublisherResp 发布者的信息
|
||||
type AppPublisherResp struct {
|
||||
UID uint64 `json:"uid" bson:"uid"` // 用户id
|
||||
Name string `json:"name" bson:"name"` // 姓名
|
||||
Portrait string `json:"portrait" bson:"portrait"` // 头像
|
||||
HasFollowed bool `json:"hasFollowed" bson:"hasFollowed"` // 是否关
|
||||
VipLevel int `json:"vipLevel" bson:"vipLevel"` // VIP等级
|
||||
VipExpireDate time.Time `json:"vipExpireDate,omitempty" bson:"vipExpireDate"` // VIP过期时间
|
||||
VipName string `json:"vipName" bson:"vipName"` // 用户VIP名称
|
||||
}
|
||||
|
||||
// RemoveVideoReq 删除自己的视频
|
||||
type RemoveVideoReq struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
|
||||
// NewsListReq 帖子请求
|
||||
type NewsListReq struct {
|
||||
Type int `form:"type" json:"type"` //请求类型,默认0,最新帖子;1,最热帖子;2,同城; 3,付费视频
|
||||
SubType int `form:"subType" json:"subType"` // type为0时, 0 默认; 1 只返回短视频 type为1时 1只返回麻豆
|
||||
Version string `form:"version" json:"version"` // 原创新版识别标志
|
||||
City string `form:"city" json:"city"` //城市
|
||||
ReqTime string `form:"reqDate" json:"reqDate"` //请求时间
|
||||
commod.Page
|
||||
}
|
||||
|
||||
// NewsListResp 帖子应答
|
||||
type NewsListResp struct {
|
||||
List []*VideoInfo `json:"list"`
|
||||
HasNext bool `json:"hasNext"`
|
||||
}
|
||||
type OriginalInfo struct {
|
||||
List [][]*VideoInfo `json:"list"`
|
||||
HasNext bool `json:"hasNext"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// UnlikeReq 不感兴趣
|
||||
type UnlikeReq struct {
|
||||
VideoID string `json:"videoID"`
|
||||
}
|
||||
|
||||
// ListType 帖子类型
|
||||
type ListType int
|
||||
|
||||
const (
|
||||
Video ListType = iota // 视频帖子 默认为全部视频
|
||||
LongVideo // 长视频
|
||||
ShortVideo // 短视频
|
||||
Image // 图片帖子
|
||||
)
|
||||
|
||||
// ModelEnum 模块枚举
|
||||
type ModelEnum int
|
||||
|
||||
const (
|
||||
LatestZone ModelEnum = iota + 1 // 最新专区/最新上架
|
||||
HottestZone // 最火专区/热门推荐
|
||||
OriginalZone // 原创专区
|
||||
GoldZone // 金币专区
|
||||
HappinessPlaza // 幸福广场
|
||||
MostLiked // 最多点赞
|
||||
MostPlay // 最多播放
|
||||
MYLickImage // 点赞图片作品
|
||||
MyImage // 我的---图片作品
|
||||
)
|
||||
|
||||
type PaymentEnum int
|
||||
|
||||
const (
|
||||
PaymentDefault PaymentEnum = iota // 默认为全部帖子
|
||||
PaymentVIP // 会员帖子
|
||||
PaymentGold // 金币帖子
|
||||
)
|
||||
|
||||
// AppListReq 帖子列表请求
|
||||
type AppListReq struct {
|
||||
Type ListType `json:"type" form:"type"` // 帖子类型
|
||||
Model ModelEnum `json:"model" form:"model"` // 模块
|
||||
Time time.Time `json:"time" form:"time"` // [最新专区、幸福广场]必传 值为第一次请求时间
|
||||
Tag string `json:"tag" form:"tag"` // 标签
|
||||
PaymentType PaymentEnum `json:"paymentType" form:"paymentType"` // 付费类型
|
||||
UID uint64 `json:"uid" form:"uid"` // 用户ID
|
||||
City string `form:"city" json:"city"`
|
||||
FilterType uint `form:"filterType" json:"filterType"` //过滤类型 1-推荐 2-最新
|
||||
commod.Page
|
||||
}
|
||||
|
||||
// WorkCount 用户视频次数
|
||||
type WorkCount struct {
|
||||
Uid uint64 `json:"_id" bson:"_id"` // 用户ID
|
||||
Count int64 `json:"count" bson:"count"` // 作品次数
|
||||
}
|
||||
|
||||
type LibraryReq struct {
|
||||
Canvas SortKey `json:"canvas" bson:"canvas"` // 视频分类
|
||||
OrderBy SortKey `json:"orderBy" bson:"orderBy"` // 视频排序
|
||||
Tags Tag `json:"tags" bson:"tags"` // 全部标签
|
||||
PaymentType SortKey `json:"paymentType" bson:"paymentType"` // 付费分类
|
||||
TimeType SortKey `json:"timeType" bson:"timeType"` // 时间排序
|
||||
}
|
||||
|
||||
type LibraryData struct {
|
||||
Canvas []SortKey `json:"canvas" bson:"canvas"` // 视频分类
|
||||
OrderBy []SortKey `json:"orderBy" bson:"orderBy"` // 视频排序
|
||||
VidTags []Tag `json:"vidTags" bson:"vidTags"` // 全部视频标签
|
||||
ACGTags []Tag `json:"acgTags" bson:"acgTags"` // 全部ACG标签
|
||||
PaymentType []SortKey `json:"paymentType" bson:"paymentType"` // 付费分类
|
||||
TimeType []SortKey `json:"timeType" bson:"timeType"` // 时间排序
|
||||
}
|
||||
|
||||
type SortKey struct {
|
||||
Key string `json:"key" bson:"key"` // 健值
|
||||
Name string `json:"name" bson:"name"` // 健名称
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
ID string `json:"id" bson:"id"` // 标签ID
|
||||
Name string `json:"name" bson:"name"` // 标签名称
|
||||
}
|
||||
|
||||
type ChangeVideoSectionReq struct {
|
||||
SectionID string `uri:"sectionID" binding:"required"` // 专题ID
|
||||
}
|
||||
|
||||
type HomeMostNewModuleVideoListReq struct {
|
||||
commod.Page
|
||||
SortType int `form:"sortType"` // 列表排序 1:最新 2:本周热门 4:本月最热 5:年度最热
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"91porn-server/common/timeutil/timerange"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func (receiver *AppListReq) LatestFilter() (primitive.M, error) {
|
||||
// 时间对齐5分钟
|
||||
recentMinute := timerange.RecentMinute(receiver.Time, 5)
|
||||
// 视频帖子并且已通过审核
|
||||
filter := bson.M{
|
||||
"newsType": "SP",
|
||||
"status": 1,
|
||||
"reviewAt": bson.M{"$lte": recentMinute},
|
||||
}
|
||||
if receiver.Tag != "" {
|
||||
tagID, err := primitive.ObjectIDFromHex(receiver.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter["tags"] = bson.M{"$elemMatch": bson.M{"$eq": tagID}}
|
||||
}
|
||||
if receiver.PaymentType == PaymentVIP {
|
||||
filter["coins"] = 0
|
||||
} else if receiver.PaymentType == PaymentGold {
|
||||
filter["coins"] = bson.M{"$gt": 0}
|
||||
}
|
||||
if receiver.Type == LongVideo {
|
||||
filter["playTime"] = bson.M{"$gte": 600}
|
||||
} else if receiver.Type == ShortVideo {
|
||||
filter["playTime"] = bson.M{"$lt": 600}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
// MostLikedFilter 点赞最多搜索条件
|
||||
func (receiver AppListReq) MostLikedFilter() (primitive.M, error) {
|
||||
// 视频帖子并且已通过审核
|
||||
filter := bson.M{
|
||||
"newsType": "SP",
|
||||
"status": 1,
|
||||
}
|
||||
if receiver.Tag != "" {
|
||||
tagID, err := primitive.ObjectIDFromHex(receiver.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter["tags"] = bson.M{"$elemMatch": bson.M{"$eq": tagID}}
|
||||
}
|
||||
if receiver.PaymentType == PaymentVIP {
|
||||
filter["coins"] = 0
|
||||
} else if receiver.PaymentType == PaymentGold {
|
||||
filter["coins"] = bson.M{"$gt": 0}
|
||||
}
|
||||
if receiver.Type == LongVideo {
|
||||
filter["playTime"] = bson.M{"$gte": 600}
|
||||
} else if receiver.Type == ShortVideo {
|
||||
filter["playTime"] = bson.M{"$lt": 600}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
// MostPlayFilter 播放最多搜索条件
|
||||
func (receiver *AppListReq) MostPlayFilter() (primitive.M, error) {
|
||||
// 视频帖子并且已通过审核
|
||||
filter := bson.M{
|
||||
"newsType": "SP",
|
||||
"status": 1,
|
||||
}
|
||||
if receiver.Tag != "" {
|
||||
tagID, err := primitive.ObjectIDFromHex(receiver.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter["tags"] = bson.M{"$elemMatch": bson.M{"$eq": tagID}}
|
||||
}
|
||||
if receiver.PaymentType == PaymentVIP {
|
||||
filter["coins"] = 0
|
||||
} else if receiver.PaymentType == PaymentGold {
|
||||
filter["coins"] = bson.M{"$gt": 0}
|
||||
}
|
||||
if receiver.Type == LongVideo {
|
||||
filter["playTime"] = bson.M{"$gte": 600}
|
||||
} else if receiver.Type == ShortVideo {
|
||||
filter["playTime"] = bson.M{"$lt": 600}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (receiver *AppListReq) HappinessPlazaFilter() (primitive.M, error) {
|
||||
// 时间对齐5分钟
|
||||
recentMinute := timerange.RecentMinute(receiver.Time, 5)
|
||||
// 视频帖子并且已通过审核
|
||||
filter := bson.M{
|
||||
"newsType": "COVER",
|
||||
"status": 1,
|
||||
"reviewAt": bson.M{"$lte": recentMinute},
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (receiver *AppListReq) Filter(uid uint64) (primitive.M, error) {
|
||||
// 视频帖子并且已通过审核
|
||||
filter := bson.M{
|
||||
"newsType": SP,
|
||||
"status": 1,
|
||||
}
|
||||
if receiver.Tag != "" {
|
||||
tagID, err := primitive.ObjectIDFromHex(receiver.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter["tags"] = tagID // bson.M{"$elemMatch": bson.M{"$eq": tagID}}
|
||||
}
|
||||
if receiver.PaymentType == PaymentVIP {
|
||||
filter["coins"] = 0
|
||||
} else if receiver.PaymentType == PaymentGold {
|
||||
filter["coins"] = bson.M{"$gt": 0}
|
||||
}
|
||||
switch receiver.Type {
|
||||
case LongVideo:
|
||||
filter["playTime"] = bson.M{"$gte": 600}
|
||||
case ShortVideo:
|
||||
filter["playTime"] = bson.M{"$lt": 600}
|
||||
case Image:
|
||||
filter["newsType"] = COVER
|
||||
}
|
||||
// 时间对齐5分钟
|
||||
recentMinute := timerange.RecentMinute(receiver.Time, 5)
|
||||
switch receiver.Model {
|
||||
case LatestZone:
|
||||
filter["reviewAt"] = bson.M{"$lte": recentMinute}
|
||||
case HappinessPlaza:
|
||||
filter["reviewAt"] = bson.M{"$lte": recentMinute}
|
||||
case MyImage:
|
||||
if receiver.UID != 0 {
|
||||
filter["publisherID"] = receiver.UID
|
||||
} else {
|
||||
filter["publisherID"] = uid
|
||||
delete(filter, "status")
|
||||
}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
// Options 分页及排序处理
|
||||
func (receiver *AppListReq) Options() *options.FindOptions {
|
||||
opts := options.Find() //如果是幸福广场,不进行分页
|
||||
if receiver.Model != HappinessPlaza { //否则进行分页
|
||||
opts.SetSkip(int64((receiver.PageNumber - 1) * receiver.PageSize)).SetLimit(int64(receiver.PageSize + 1))
|
||||
}
|
||||
var sort bson.D
|
||||
if receiver.FilterType == 1 {
|
||||
sort = bson.D{{Key: "recoWeigh", Value: -1}}
|
||||
opts.SetSort(sort)
|
||||
} else if receiver.FilterType == 2 {
|
||||
sort = bson.D{{Key: "createdAt", Value: -1}}
|
||||
opts.SetSort(sort)
|
||||
}
|
||||
switch receiver.Model {
|
||||
case LatestZone:
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "reviewAt", Value: -1}}))
|
||||
case HottestZone:
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "hot", Value: -1}, {Key: "reviewAt", Value: -1}}))
|
||||
case MostLiked:
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "likeCount", Value: -1}, {Key: "reviewAt", Value: -1}}))
|
||||
case MostPlay:
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "playCount", Value: -1}, {Key: "reviewAt", Value: -1}}))
|
||||
case MyImage:
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "createdAt", Value: -1}}))
|
||||
case HappinessPlaza:
|
||||
//opts.SetSort(bson.D{{"happinessPlazaTop", -1}, {"reviewAt", -1}})
|
||||
opts.SetSort(mergeSort(sort, bson.D{{Key: "reviewAt", Value: -1}})) //统一默认按照审核时间排序
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func mergeSort(m bson.D, mm bson.D) bson.D {
|
||||
if len(m) == 0 {
|
||||
return mm
|
||||
}
|
||||
if len(mm) == 0 {
|
||||
return m
|
||||
}
|
||||
for _, vv := range mm {
|
||||
for _, v := range m {
|
||||
if v.Key == vv.Key {
|
||||
continue
|
||||
}
|
||||
}
|
||||
m = append(m, vv)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// RedisKey 获取redis缓存key
|
||||
func (receiver *AppListReq) RedisKey(uid uint64) string {
|
||||
redisKey := "appListReqCache:status:1"
|
||||
if receiver.Type == Image {
|
||||
redisKey += ":newsType:" + COVER
|
||||
} else {
|
||||
redisKey += ":newsType:" + SP
|
||||
}
|
||||
if receiver.Tag != "" {
|
||||
tagID, err := primitive.ObjectIDFromHex(receiver.Tag)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
redisKey += ":tags:" + tagID.Hex()
|
||||
}
|
||||
if receiver.PaymentType == PaymentVIP {
|
||||
redisKey += ":coins:0"
|
||||
} else if receiver.PaymentType == PaymentGold {
|
||||
redisKey += ":coins:gt0"
|
||||
}
|
||||
if receiver.Type == LongVideo {
|
||||
redisKey += ":playTime:gte600"
|
||||
}
|
||||
if receiver.Type == ShortVideo {
|
||||
redisKey += ":playTime:lt600"
|
||||
}
|
||||
// 时间对齐5分钟
|
||||
recentMinute := timerange.RecentMinute(receiver.Time, 5)
|
||||
switch receiver.Model {
|
||||
case LatestZone:
|
||||
redisKey += ":reviewAt:lte" + recentMinute.Format("200601021504")
|
||||
case HappinessPlaza:
|
||||
redisKey += ":reviewAt:lte" + recentMinute.Format("200601021504")
|
||||
case MyImage:
|
||||
if receiver.UID != 0 {
|
||||
redisKey += ":publisherID:" + strconv.FormatUint(receiver.UID, 10)
|
||||
} else {
|
||||
redisKey += ":publisherID:" + strconv.FormatUint(receiver.UID, 10)
|
||||
redisKey += ":status:delete"
|
||||
}
|
||||
}
|
||||
if receiver.Model != HappinessPlaza { //分页信息
|
||||
redisKey += ":skip:" + strconv.FormatUint((receiver.PageNumber-1)*receiver.PageSize, 10) + ":limit:" + strconv.FormatUint((receiver.PageSize+1), 10)
|
||||
}
|
||||
switch receiver.Model {
|
||||
case LatestZone:
|
||||
redisKey += ":reviewAt:-1"
|
||||
case HottestZone:
|
||||
redisKey += ":hot:-1:reviewAt:-1"
|
||||
case MostLiked:
|
||||
redisKey += ":likeCount:-1:reviewAt:-1"
|
||||
case MostPlay:
|
||||
redisKey += ":playCount:-1:reviewAt:-1"
|
||||
case MyImage:
|
||||
redisKey += ":createdAt:-1"
|
||||
case HappinessPlaza:
|
||||
redisKey += ":reviewAt:-1"
|
||||
}
|
||||
redisKey += ":filterType:" + strconv.FormatInt(int64(receiver.FilterType), 10)
|
||||
return redisKey
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"91porn-server/app/appg"
|
||||
"91porn-server/common/constant/redisconst"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/redis"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
func getRedis() *redis.Client {
|
||||
return appg.Redis
|
||||
}
|
||||
|
||||
// GetByIDFromRedis 根据id获取一条记录
|
||||
func GetByIDFromRedis(id string) (vid VideoModel, err error) {
|
||||
redisKey := redisconst.DataCachKey(table, id)
|
||||
redisc := getRedis()
|
||||
if redisc == nil || !redisc.Exists(redisKey) {
|
||||
vid, err = GetVideoInfo(id)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetByID", table, "FindOne", err), log.Any("id", id))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if redisc == nil {
|
||||
return
|
||||
}
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = msgpack.Marshal(&vid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = redisc.Set(redisKey, string(jsonBytes), redisconst.DataCachExpire)
|
||||
return
|
||||
}
|
||||
str, err := redisc.Get(redisKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if str == nil {
|
||||
err = errors.New("redis key is null")
|
||||
return
|
||||
}
|
||||
err = msgpack.Unmarshal([]byte(*str), &vid)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetByID", table, "FindOne", err), log.Any("id", id))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetShareListFromRedis() ([]ShareInfo, error) {
|
||||
redisKey := redisconst.GetVideoShareListKey()
|
||||
redisc := getRedis()
|
||||
if redisc == nil || !redisc.Exists(redisKey) {
|
||||
status := CheckPass
|
||||
sis, err := getShareList(&status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if redisc == nil {
|
||||
return sis, err
|
||||
}
|
||||
var jsonBytes []byte
|
||||
jsonBytes, err = msgpack.Marshal(sis)
|
||||
if err != nil {
|
||||
return sis, nil
|
||||
}
|
||||
err = redisc.Set(redisKey, jsonBytes, redisconst.GetVideoShareListExpired())
|
||||
return sis, nil
|
||||
}
|
||||
str, err := redisc.Get(redisKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if str == nil {
|
||||
return nil, errors.New("redis key is null")
|
||||
}
|
||||
var sis []ShareInfo
|
||||
return sis, msgpack.Unmarshal([]byte(*str), &sis)
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"91porn-server/common/elastic"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models"
|
||||
)
|
||||
|
||||
var es *elastic.Client
|
||||
|
||||
const ESTable = models.ESInfoVideoTable
|
||||
|
||||
func InitESIndex() {
|
||||
es = elastic.Init()
|
||||
var setting = elastic.M{
|
||||
"settings": elastic.M{
|
||||
"number_of_shards": elastic.NumberOfShards,
|
||||
"number_of_replicas": elastic.NumberOfReplicas,
|
||||
"analysis": elastic.M{
|
||||
"analyzer": elastic.M{
|
||||
"ik": elastic.M{
|
||||
"tokenizer": elastic.AnalyzerIkSmart,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"mappings": elastic.M{
|
||||
"properties": elastic.M{
|
||||
"title": elastic.M{
|
||||
"type": "text",
|
||||
"analyzer": elastic.AnalyzerIkSmart,
|
||||
"search_analyzer": elastic.AnalyzerIkSmart,
|
||||
},
|
||||
"tagsName": elastic.M{
|
||||
"type": "text",
|
||||
"analyzer": elastic.AnalyzerIkSmart,
|
||||
"search_analyzer": elastic.AnalyzerIkSmart,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := es.CreateIndices(ESTable, setting); err != nil {
|
||||
panic(fmt.Sprintf("%s index indeices err ==>[%+v]", ESTable, err))
|
||||
}
|
||||
}
|
||||
|
||||
// 根据关键字搜索
|
||||
func Search(keywords string, from int64, size int64) (data []ESVideoSource, err error) {
|
||||
es = elastic.Init()
|
||||
query := elastic.M{
|
||||
"query": elastic.M{
|
||||
"bool": elastic.M{
|
||||
"must": elastic.A{
|
||||
{"multi_match": elastic.M{
|
||||
"query": keywords,
|
||||
"fields": []string{"title"}},
|
||||
},
|
||||
{"terms": elastic.M{"status": []int{1, 3}}},
|
||||
{"range": elastic.M{"playTime": elastic.M{"lt": 600}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"sort": elastic.A{{"hot": elastic.M{"order": "desc"}}},
|
||||
"from": from,
|
||||
"size": size,
|
||||
}
|
||||
if err = es.Search(ESTable, &data, query); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err),
|
||||
log.Any("keywords", keywords),
|
||||
log.Any("from", from),
|
||||
log.Any("size", size))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
func SearchByCondWithTotal(filter elastic.M) (data ESVideoSourceWithTotal, err error) {
|
||||
es = elastic.Init()
|
||||
if err = es.SearchWithTotal(ESTable, &data, filter); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err), log.Any("filter", filter))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SearchWithTotal(keywords string, from int64, size int64) (data ESVideoSourceWithTotal, err error) {
|
||||
es = elastic.Init()
|
||||
query := elastic.M{
|
||||
"query": elastic.M{
|
||||
"bool": elastic.M{
|
||||
"must": elastic.A{
|
||||
{"multi_match": elastic.M{
|
||||
"query": keywords,
|
||||
"fields": []string{"title"}},
|
||||
},
|
||||
{"terms": elastic.M{"status": []int{1, 3}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"sort": elastic.A{{"hot": elastic.M{"order": "desc"}}},
|
||||
"from": from,
|
||||
"size": size,
|
||||
}
|
||||
if err = es.SearchWithTotal(ESTable, &data, query); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err),
|
||||
log.Any("keywords", keywords),
|
||||
log.Any("from", from),
|
||||
log.Any("size", size))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DirectSearch 用户根据关键字搜索
|
||||
func DirectSearch(keywords string, vidType string, sortType int, from int64, size int64) (data []ESVideoSource, err error) {
|
||||
es = elastic.Init()
|
||||
query := elastic.M{
|
||||
"query": elastic.M{
|
||||
"bool": elastic.M{
|
||||
"must": elastic.A{
|
||||
{"multi_match": elastic.M{
|
||||
"query": keywords,
|
||||
"fields": []string{"title"}},
|
||||
},
|
||||
{"terms": elastic.M{"status": []int{1, 3}}},
|
||||
{"term": elastic.M{"newsType.keyword": vidType}},
|
||||
},
|
||||
},
|
||||
},
|
||||
// "sort": elastic.A{{"hot": elastic.M{"order": "desc"}}},
|
||||
"from": from,
|
||||
"size": size,
|
||||
}
|
||||
// sortType 1最多观看 2最新上架 3最多收藏
|
||||
switch sortType {
|
||||
case 1:
|
||||
query["sort"] = elastic.A{{"playCount": elastic.M{"order": "desc"}}}
|
||||
case 2:
|
||||
query["sort"] = elastic.A{{"createdAt": elastic.M{"order": "desc"}}}
|
||||
case 3:
|
||||
query["sort"] = elastic.A{{"collectCount": elastic.M{"order": "desc"}}}
|
||||
}
|
||||
if err = es.Search(ESTable, &data, query); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err),
|
||||
log.Any("keywords", keywords),
|
||||
log.Any("from", from),
|
||||
log.Any("size", size))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DeleteByCond(filter elastic.M) (err error) {
|
||||
es = elastic.Init()
|
||||
if err = es.BulkDelete(ESTable, filter); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err), log.Any("filter", filter))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWatchCountRespJSONIncludesTotal(t *testing.T) {
|
||||
data, err := json.Marshal(WatchCountResp{
|
||||
IsCan: true,
|
||||
WatchCount: 2,
|
||||
TotalWatchCount: 3,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), `"totalWatchCount":3`) {
|
||||
t.Fatalf("totalWatchCount missing from response: %s", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoInfoIncludesFreeTrialFieldsWhenFalse(t *testing.T) {
|
||||
for name, value := range map[string]interface{}{
|
||||
"VideoInfo": VideoInfo{},
|
||||
"VideoInfoResp": VideoInfoResp{},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, field := range []string{
|
||||
`"showFreeTrialBadge":false`,
|
||||
`"freeTrialRemaining":0`,
|
||||
`"canUseFreeTrial":false`,
|
||||
} {
|
||||
if !strings.Contains(string(data), field) {
|
||||
t.Fatalf("%s missing from response: %s", field, data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoInfoRespIncludesFreeArea(t *testing.T) {
|
||||
data, err := json.Marshal(VideoInfoResp{FreeArea: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), `"freeArea":true`) {
|
||||
t.Fatalf("freeArea missing from response: %s", data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func h265URLMissingFilter() bson.A {
|
||||
return bson.A{
|
||||
bson.M{"h265Url": ""},
|
||||
bson.M{"h265Url": nil},
|
||||
bson.M{"h265Url": bson.M{"$exists": false}},
|
||||
}
|
||||
}
|
||||
|
||||
func h265FailCountRetryableFilter() bson.A {
|
||||
return bson.A{
|
||||
bson.M{"h265FailCount": bson.M{"$exists": false}},
|
||||
bson.M{"h265FailCount": bson.M{"$lt": H265MaxFailCount}},
|
||||
}
|
||||
}
|
||||
|
||||
func h265QueueableStatusFilter() bson.A {
|
||||
return bson.A{
|
||||
bson.M{"h265Status": bson.M{"$exists": false}},
|
||||
bson.M{"h265Status": H265StatusNone},
|
||||
bson.M{"h265Status": H265StatusFailed, "$or": h265FailCountRetryableFilter()},
|
||||
}
|
||||
}
|
||||
|
||||
// QueueH265Transcode 将审核通过且尚无 H.265 地址的长视频加入等待队列。
|
||||
func QueueH265Transcode(ids []primitive.ObjectID) (int64, error) {
|
||||
if len(ids) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
now := time.Now()
|
||||
filter := bson.M{
|
||||
"_id": bson.M{"$in": ids},
|
||||
"status": CheckPass,
|
||||
"newsType": SP,
|
||||
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
||||
"$and": bson.A{
|
||||
bson.M{"$or": h265URLMissingFilter()},
|
||||
bson.M{"$or": h265QueueableStatusFilter()},
|
||||
},
|
||||
}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"h265Status": H265StatusQueued,
|
||||
"h265QueuedAt": now,
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$unset": bson.M{"h265PendingAt": ""},
|
||||
}
|
||||
result, err := coll(nil).UpdateMany(filter, update)
|
||||
if err != nil {
|
||||
log.Error("QueueH265Transcode UpdateMany failed", log.Any("ids", ids), log.E(err))
|
||||
return 0, err
|
||||
}
|
||||
return result.ModifiedCount, nil
|
||||
}
|
||||
|
||||
// CountH265Pending 统计已经提交云端、尚未取得结果的有效长视频。
|
||||
func CountH265Pending() (int64, error) {
|
||||
filter := bson.M{
|
||||
"h265Status": H265StatusPending,
|
||||
"status": CheckPass,
|
||||
"newsType": SP,
|
||||
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
||||
"$or": h265URLMissingFilter(),
|
||||
}
|
||||
count, err := coll(nil).Count(filter)
|
||||
if err != nil {
|
||||
log.Error("CountH265Pending Count failed", log.E(err))
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetQueuedH265Videos 获取等待提交或允许重试的 H.265 任务。
|
||||
func GetQueuedH265Videos(limit int64) ([]*VideoModel, error) {
|
||||
if limit <= 0 {
|
||||
return []*VideoModel{}, nil
|
||||
}
|
||||
filter := bson.M{
|
||||
"h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}},
|
||||
"status": CheckPass,
|
||||
"newsType": SP,
|
||||
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
||||
"$and": bson.A{
|
||||
bson.M{"$or": h265URLMissingFilter()},
|
||||
bson.M{"$or": h265FailCountRetryableFilter()},
|
||||
},
|
||||
}
|
||||
opts := options.Find().
|
||||
SetLimit(limit).
|
||||
SetSort(bson.D{{Key: "h265QueuedAt", Value: 1}, {Key: "reviewAt", Value: -1}})
|
||||
var out []*VideoModel
|
||||
if err := coll(nil).Find(&out, filter, opts); err != nil {
|
||||
log.Error("GetQueuedH265Videos Find failed", log.Any("filter", filter), log.E(err))
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetPendingH265Videos 获取已经提交云端、等待轮询结果的 H.265 任务。
|
||||
func GetPendingH265Videos(limit int64) ([]*VideoModel, error) {
|
||||
if limit <= 0 {
|
||||
return []*VideoModel{}, nil
|
||||
}
|
||||
filter := bson.M{
|
||||
"h265Status": H265StatusPending,
|
||||
"status": CheckPass,
|
||||
"newsType": SP,
|
||||
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
||||
"$or": h265URLMissingFilter(),
|
||||
}
|
||||
opts := options.Find().SetLimit(limit).SetSort(bson.D{{Key: "h265PendingAt", Value: 1}})
|
||||
var out []*VideoModel
|
||||
if err := coll(nil).Find(&out, filter, opts); err != nil {
|
||||
log.Error("GetPendingH265Videos Find failed", log.Any("filter", filter), log.E(err))
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ClaimH265Pending 原子地将一个等待任务标记为云端处理中,并返回本次
|
||||
// claim 的整秒时间。SKD 用该时间生成稳定的限时拉流 URL,重启后仍能算出
|
||||
// 与提交时相同的云端 file_id。
|
||||
func ClaimH265Pending(id primitive.ObjectID) (time.Time, bool, error) {
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
filter := bson.M{
|
||||
"_id": id,
|
||||
"h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}},
|
||||
"status": CheckPass,
|
||||
"newsType": SP,
|
||||
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
||||
"$and": bson.A{
|
||||
bson.M{"$or": h265URLMissingFilter()},
|
||||
bson.M{"$or": h265FailCountRetryableFilter()},
|
||||
},
|
||||
}
|
||||
result, err := coll(nil).UpdateOne(filter, bson.M{"$set": bson.M{
|
||||
"h265Status": H265StatusPending,
|
||||
"h265PendingAt": now,
|
||||
"updatedAt": now,
|
||||
}})
|
||||
if err != nil {
|
||||
log.Error("ClaimH265Pending UpdateOne failed", log.Any("id", id), log.E(err))
|
||||
return time.Time{}, false, err
|
||||
}
|
||||
if result.ModifiedCount == 0 {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
return now, true, nil
|
||||
}
|
||||
|
||||
// MarkH265Queued 将当前 pending 尝试重新放回等待队列。
|
||||
// sourceURL 与 pendingAt 共同标识一次云端尝试,避免旧实例把同视频的
|
||||
// 新尝试或已经成功回填的状态回退成 queued。
|
||||
func MarkH265Queued(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) {
|
||||
now := time.Now()
|
||||
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
||||
result, err := coll(nil).UpdateOne(filter, bson.M{
|
||||
"$set": bson.M{
|
||||
"h265Status": H265StatusQueued,
|
||||
"h265QueuedAt": now,
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$unset": bson.M{"h265PendingAt": ""},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("MarkH265Queued UpdateOne failed", log.Any("id", id), log.E(err))
|
||||
return false, err
|
||||
}
|
||||
return result.ModifiedCount > 0, nil
|
||||
}
|
||||
|
||||
// MarkH265Success 保存上游明确返回的可播放地址并将任务标记为成功。
|
||||
// 老司机重新导入属于权威回填,不受旧云转码任务状态限制。
|
||||
func MarkH265Success(id primitive.ObjectID, h265URL string) error {
|
||||
h265URL = strings.TrimSpace(h265URL)
|
||||
if h265URL == "" {
|
||||
return fmt.Errorf("empty h265 url")
|
||||
}
|
||||
now := time.Now()
|
||||
_, err := coll(nil).UpdateOne(bson.M{"_id": id}, bson.M{
|
||||
"$set": bson.M{
|
||||
"h265Url": h265URL,
|
||||
"h265Status": H265StatusSuccess,
|
||||
"h265FailCount": 0,
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$unset": bson.M{
|
||||
"h265QueuedAt": "",
|
||||
"h265PendingAt": "",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("MarkH265Success UpdateOne failed", log.Any("id", id), log.E(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarkPendingH265Success 仅完成仍对应同一源地址的 pending 任务。
|
||||
// 如果视频源已变更,或其他流程已经回填 H.265,旧云任务结果必须被忽略。
|
||||
func MarkPendingH265Success(id primitive.ObjectID, sourceURL string, pendingAt time.Time, h265URL string) (bool, error) {
|
||||
h265URL = strings.TrimSpace(h265URL)
|
||||
if strings.TrimSpace(sourceURL) == "" {
|
||||
return false, fmt.Errorf("empty source url")
|
||||
}
|
||||
if pendingAt.IsZero() {
|
||||
return false, fmt.Errorf("empty H265 pending time")
|
||||
}
|
||||
if h265URL == "" {
|
||||
return false, fmt.Errorf("empty h265 url")
|
||||
}
|
||||
now := time.Now()
|
||||
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
||||
result, err := coll(nil).UpdateOne(filter, bson.M{
|
||||
"$set": bson.M{
|
||||
"h265Url": h265URL,
|
||||
"h265Status": H265StatusSuccess,
|
||||
"h265FailCount": 0,
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$unset": bson.M{
|
||||
"h265QueuedAt": "",
|
||||
"h265PendingAt": "",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("MarkPendingH265Success UpdateOne failed", log.Any("id", id), log.E(err))
|
||||
return false, err
|
||||
}
|
||||
return result.ModifiedCount > 0, nil
|
||||
}
|
||||
|
||||
// MarkH265Failed 记录当前 pending 尝试失败;达到 H265MaxFailCount 后将不再自动重试。
|
||||
// 条件更新会跳过新尝试或已经成功回填的视频,避免旧轮询结果回退状态。
|
||||
func MarkH265Failed(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) {
|
||||
now := time.Now()
|
||||
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
||||
result, err := coll(nil).UpdateOne(filter, bson.M{
|
||||
"$set": bson.M{
|
||||
"h265Status": H265StatusFailed,
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$inc": bson.M{"h265FailCount": 1},
|
||||
"$unset": bson.M{
|
||||
"h265QueuedAt": "",
|
||||
"h265PendingAt": "",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("MarkH265Failed UpdateOne failed", log.Any("id", id), log.E(err))
|
||||
return false, err
|
||||
}
|
||||
return result.ModifiedCount > 0, nil
|
||||
}
|
||||
|
||||
func pendingH265AttemptFilter(id primitive.ObjectID, sourceURL string, pendingAt time.Time) bson.M {
|
||||
return bson.M{
|
||||
"_id": id,
|
||||
"sourceURL": sourceURL,
|
||||
"h265Status": H265StatusPending,
|
||||
"h265PendingAt": pendingAt,
|
||||
"$or": h265URLMissingFilter(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestPendingH265SuccessFilterBindsAttemptState(t *testing.T) {
|
||||
id := primitive.NewObjectID()
|
||||
source := " /sp/movie/index.m3u8 "
|
||||
pendingAt := time.Date(2026, 7, 25, 12, 30, 0, 0, time.UTC)
|
||||
filter := pendingH265AttemptFilter(id, source, pendingAt)
|
||||
|
||||
if filter["_id"] != id {
|
||||
t.Fatalf("filter id = %v, want %v", filter["_id"], id)
|
||||
}
|
||||
if filter["sourceURL"] != source {
|
||||
t.Fatalf("filter sourceURL = %q, want exact stored source %q", filter["sourceURL"], source)
|
||||
}
|
||||
if filter["h265Status"] != H265StatusPending {
|
||||
t.Fatalf("filter status = %v, want pending", filter["h265Status"])
|
||||
}
|
||||
if filter["h265PendingAt"] != pendingAt {
|
||||
t.Fatalf("filter pendingAt = %v, want %v", filter["h265PendingAt"], pendingAt)
|
||||
}
|
||||
missing, ok := filter["$or"].(bson.A)
|
||||
if !ok || len(missing) != 3 {
|
||||
t.Fatalf("filter must require a missing H265 URL: %#v", filter["$or"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestVideoModelSortUsesStableTieBreakers(t *testing.T) {
|
||||
reviewAt := time.Date(2026, 7, 28, 10, 0, 0, 0, time.UTC)
|
||||
olderReviewAt := reviewAt.Add(-time.Hour)
|
||||
lowerID := primitive.ObjectID{11: 1}
|
||||
higherID := primitive.ObjectID{11: 2}
|
||||
|
||||
videos := VideoModelSort{
|
||||
{ID: lowerID, LiaoBaTopSort: 100, ReviewAt: reviewAt},
|
||||
{ID: primitive.NewObjectID(), LiaoBaTopSort: 200, ReviewAt: olderReviewAt},
|
||||
{ID: primitive.NewObjectID(), LiaoBaTopSort: 100, ReviewAt: olderReviewAt},
|
||||
{ID: higherID, LiaoBaTopSort: 100, ReviewAt: reviewAt},
|
||||
}
|
||||
|
||||
sort.Sort(videos)
|
||||
|
||||
if got := videos[0].LiaoBaTopSort; got != 200 {
|
||||
t.Fatalf("first liaoBaTopSort = %d, want 200", got)
|
||||
}
|
||||
if got := videos[1].ID; got != higherID {
|
||||
t.Fatalf("same sort/reviewAt should use descending _id, got %s want %s", got.Hex(), higherID.Hex())
|
||||
}
|
||||
if got := videos[2].ID; got != lowerID {
|
||||
t.Fatalf("same sort should prefer newer reviewAt, got %s want %s", got.Hex(), lowerID.Hex())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/pageopt"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type VIDSlice []VideoModel
|
||||
|
||||
func (v VIDSlice) ToMap() map[ObjectID]*VideoModel {
|
||||
m := make(map[ObjectID]*VideoModel, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
m[v[i].ID] = &v[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (v VIDSlice) IDs() []ObjectID {
|
||||
ids := make([]ObjectID, len(v))
|
||||
for i, v := range v {
|
||||
ids[i] = v.ID
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
type Matcher = pageopt.Matcher
|
||||
|
||||
// IDInMatch
|
||||
type IDInMatch = pageopt.IDInMatch
|
||||
|
||||
// PublisherIDMatch
|
||||
type PublisherIDMatch struct {
|
||||
PublisherID *uint64
|
||||
}
|
||||
|
||||
func (c *PublisherIDMatch) New() Matcher {
|
||||
return pageopt.NewAssignMatch("publisherID", c.PublisherID)
|
||||
}
|
||||
|
||||
// PublisherIDInMatch
|
||||
type PublisherIDInMatch struct {
|
||||
PublisherIDList []uint64
|
||||
}
|
||||
|
||||
func (c *PublisherIDInMatch) New() Matcher {
|
||||
return pageopt.NewInMatch("publisherID", c.PublisherIDList)
|
||||
}
|
||||
|
||||
// StatusInMatch
|
||||
type StatusInMatch struct {
|
||||
StatusList []int
|
||||
}
|
||||
|
||||
func (c *StatusInMatch) New() Matcher {
|
||||
return pageopt.NewInMatch("status", c.StatusList)
|
||||
}
|
||||
|
||||
// StatusInMatch
|
||||
type StatusMatch struct {
|
||||
Status int
|
||||
}
|
||||
|
||||
func (c *StatusMatch) New() Matcher {
|
||||
return pageopt.NewAssignMatch("status", c.Status)
|
||||
}
|
||||
|
||||
// ReviewAtGTEAndLTMatch
|
||||
type ReviewAtGTEAndLTMatch struct {
|
||||
GTE *time.Time
|
||||
LT *time.Time
|
||||
}
|
||||
|
||||
func (c *ReviewAtGTEAndLTMatch) New() Matcher {
|
||||
return pageopt.NewGTEAndLTMatch("reviewAt", c.GTE, c.LT)
|
||||
}
|
||||
|
||||
// UpdatedAtGTEAndLTMatch
|
||||
type UpdatedAtGTEAndLTMatch struct {
|
||||
GTE *time.Time
|
||||
LT *time.Time
|
||||
}
|
||||
|
||||
func (c *UpdatedAtGTEAndLTMatch) New() Matcher {
|
||||
return pageopt.NewGTEAndLTMatch("updatedAt", c.GTE, c.LT)
|
||||
}
|
||||
|
||||
type CreatedAtGTEAndLTMatch = pageopt.CreatedAtGTEAndLTMatch
|
||||
|
||||
type Sort = bson.D
|
||||
|
||||
func List(sort Sort, skip, limit *int64, matchers ...Matcher) (VIDSlice, error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
opt := &options.FindOptions{}
|
||||
if len(sort) != 0 {
|
||||
opt.SetSort(sort)
|
||||
}
|
||||
if skip != nil {
|
||||
opt.SetSkip(*skip)
|
||||
}
|
||||
if limit != nil {
|
||||
opt.SetLimit(*limit)
|
||||
}
|
||||
list := VIDSlice{}
|
||||
if err := coll(nil).Find(&list, filter, opt); err != nil {
|
||||
log.Error("vidmod List error", log.E(err))
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func Count(matchers ...Matcher) (int64, error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
count, err := coll(nil).Count(filter)
|
||||
if err != nil {
|
||||
log.Error("vidmod Count error", log.E(err))
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func CountMapByPublisherID(matchers ...Matcher) (map[uint64]int64, error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
pipeline := []M{
|
||||
{"$match": filter},
|
||||
{"$group": M{"_id": "$publisherID", "count": M{"$sum": 1}}},
|
||||
}
|
||||
var list []struct {
|
||||
PublisherID uint64 `bson:"_id"`
|
||||
Count int64 `bson:"count"`
|
||||
}
|
||||
if err := coll(nil).Aggregate(&list, pipeline); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CountTotalLikeByUID", table, "Aggregate", err),
|
||||
log.Any("filter", filter),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[uint64]int64, len(list))
|
||||
for _, v := range list {
|
||||
m[v.PublisherID] = v.Count
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func PublisherIDList(matchers ...Matcher) (uids []uint64, err error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
list, err := coll(nil).Distinct("publisherID", filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, i := range list {
|
||||
id, err := strconv.Atoi(fmt.Sprintf("%v", i))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
uids = append(uids, uint64(id))
|
||||
}
|
||||
return uids, nil
|
||||
}
|
||||
|
||||
func CountNum(filter interface{}) (int64, error) {
|
||||
count, err := coll(nil).Count(filter)
|
||||
if err != nil {
|
||||
log.Error("vidmod Count error", log.E(err))
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func GetListByCond(cond bson.M, opts *options.FindOptions) ([]VideoModel, error) {
|
||||
var data []VideoModel
|
||||
if err := coll(nil).Find(&data, cond, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package vidmod
|
||||
|
||||
// M3u8Signer 就地改写单个 m3u8 播放地址字段,由 app/service/m3u8ticket 实现。
|
||||
// playable 表示正片、preview 表示试看,用于票据里区分播放语义。
|
||||
type M3u8Signer interface {
|
||||
SignM3u8URL(field *string, playable, preview bool)
|
||||
}
|
||||
|
||||
// M3u8Signable 由携带 m3u8 播放地址的响应体实现:把自己(含嵌套列表)所有需要签票的地址字段
|
||||
// 逐个交给 signer 就地改写,避免 m3u8ticket 反向依赖上层包,也避免每次请求反射遍历响应体。
|
||||
type M3u8Signable interface {
|
||||
SignM3u8(s M3u8Signer)
|
||||
}
|
||||
|
||||
// SignM3u8Infos 依次为列表里每个 *VideoInfo 签票,供各外层响应体的 SignM3u8 复用(nil 元素安全跳过)。
|
||||
func SignM3u8Infos(s M3u8Signer, list []*VideoInfo) {
|
||||
for _, v := range list {
|
||||
v.SignM3u8(s)
|
||||
}
|
||||
}
|
||||
|
||||
// SignM3u8Resps 依次为列表里每个 *VideoInfoResp 签票,供各外层响应体的 SignM3u8 复用(nil 元素安全跳过)。
|
||||
func SignM3u8Resps(s M3u8Signer, list []*VideoInfoResp) {
|
||||
for _, v := range list {
|
||||
v.SignM3u8(s)
|
||||
}
|
||||
}
|
||||
|
||||
// SignM3u8 对详情/列表项的 sourceURL、h265Url 按正片,previewURL 按试看交给 signer 签票。
|
||||
func (v *VideoInfo) SignM3u8(s M3u8Signer) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
s.SignM3u8URL(&v.SourceURL, true, false)
|
||||
s.SignM3u8URL(&v.H265Url, true, false)
|
||||
s.SignM3u8URL(&v.PreviewURL, false, true)
|
||||
}
|
||||
|
||||
// SignM3u8 对列表返回体(无 previewURL 字段)的 sourceURL、h265Url 按正片签票。
|
||||
func (v *VideoInfoResp) SignM3u8(s M3u8Signer) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
s.SignM3u8URL(&v.SourceURL, true, false)
|
||||
s.SignM3u8URL(&v.H265Url, true, false)
|
||||
}
|
||||
|
||||
// 编译期断言:叶子视频类型实现 M3u8Signable,供 m3u8ticket 零反射签票分支使用。
|
||||
var (
|
||||
_ M3u8Signable = (*VideoInfo)(nil)
|
||||
_ M3u8Signable = (*VideoInfoResp)(nil)
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package vidmod
|
||||
|
||||
import "testing"
|
||||
|
||||
// countingSigner 记录被交出的地址字段个数,用于验证各响应体 SignM3u8 的字段枚举是否完整。
|
||||
type countingSigner struct{ n int }
|
||||
|
||||
func (c *countingSigner) SignM3u8URL(field *string, playable, preview bool) { c.n++ }
|
||||
|
||||
func TestVideoInfoOffersThreeFields(t *testing.T) {
|
||||
c := &countingSigner{}
|
||||
(&VideoInfo{}).SignM3u8(c)
|
||||
if c.n != 3 { // sourceURL + h265Url + previewURL
|
||||
t.Fatalf("VideoInfo should offer 3 url fields, got %d", c.n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoInfoRespOffersTwoFields(t *testing.T) {
|
||||
c := &countingSigner{}
|
||||
(&VideoInfoResp{}).SignM3u8(c)
|
||||
if c.n != 2 { // sourceURL + h265Url(无 previewURL)
|
||||
t.Fatalf("VideoInfoResp should offer 2 url fields, got %d", c.n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilVideosAreSafe(t *testing.T) {
|
||||
c := &countingSigner{}
|
||||
var v *VideoInfo
|
||||
v.SignM3u8(c) // nil 接收者不应 panic
|
||||
SignM3u8Infos(c, []*VideoInfo{nil})
|
||||
SignM3u8Resps(c, []*VideoInfoResp{nil})
|
||||
if c.n != 0 {
|
||||
t.Fatalf("nil videos must not offer any field, got %d", c.n)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type PaymentGuideVideo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Cover string `json:"cover"`
|
||||
CoverThumb string `json:"coverThumb"`
|
||||
PlayTime uint `json:"playTime"`
|
||||
PlayCount int `json:"playCount"`
|
||||
FakePlayCount int `json:"-"`
|
||||
}
|
||||
|
||||
// LatestVIPContent returns the newest approved VIP videos for the
|
||||
// VIP-content-update guide. Free-area, paid-coin and non-recommendable videos
|
||||
// are not VIP content for this scene.
|
||||
func LatestVIPContent(limit int64, excludedModuleIDs []string) ([]PaymentGuideVideo, error) {
|
||||
if limit <= 0 {
|
||||
return []PaymentGuideVideo{}, nil
|
||||
}
|
||||
filter := latestVIPContentFilter(excludedModuleIDs)
|
||||
opts := options.Find().
|
||||
SetLimit(limit).
|
||||
SetSort(bson.D{{Key: "reviewAt", Value: -1}, {Key: "_id", Value: -1}}).
|
||||
SetProjection(bson.M{
|
||||
"_id": 1,
|
||||
"title": 1,
|
||||
"cover": 1,
|
||||
"coverThumb": 1,
|
||||
"playTime": 1,
|
||||
"playCount": 1,
|
||||
"fakePlayCount": 1,
|
||||
})
|
||||
var videos []VideoModel
|
||||
if err := coll(nil).Find(&videos, filter, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]PaymentGuideVideo, 0, len(videos))
|
||||
for _, video := range videos {
|
||||
if video.ID.IsZero() {
|
||||
continue
|
||||
}
|
||||
result = append(result, PaymentGuideVideo{
|
||||
ID: video.ID.Hex(),
|
||||
Title: video.Title,
|
||||
Cover: video.Cover,
|
||||
CoverThumb: video.CoverThumb,
|
||||
PlayTime: video.PlayTime,
|
||||
PlayCount: video.PlayCount,
|
||||
FakePlayCount: video.FakePlayCount,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func latestVIPContentFilter(excludedModuleIDs []string) bson.M {
|
||||
filter := bson.M{
|
||||
"status": CheckPass,
|
||||
"newsType": bson.M{"$in": []string{SP, SHORT}},
|
||||
"coins": 0,
|
||||
"freeArea": bson.M{"$ne": true},
|
||||
"recoWeight": bson.M{"$ne": -1},
|
||||
}
|
||||
if len(excludedModuleIDs) > 0 {
|
||||
filter["mId"] = bson.M{"$nin": excludedModuleIDs}
|
||||
}
|
||||
return filter
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func TestLatestVIPContentFilter(t *testing.T) {
|
||||
excluded := []string{"module-a", "module-b"}
|
||||
got := latestVIPContentFilter(excluded)
|
||||
want := bson.M{
|
||||
"status": CheckPass,
|
||||
"newsType": bson.M{"$in": []string{SP, SHORT}},
|
||||
"coins": 0,
|
||||
"freeArea": bson.M{"$ne": true},
|
||||
"recoWeight": bson.M{"$ne": -1},
|
||||
"mId": bson.M{"$nin": excluded},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("latestVIPContentFilter() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatestVIPContentFilterWithoutExcludedModules(t *testing.T) {
|
||||
got := latestVIPContentFilter(nil)
|
||||
if _, exists := got["mId"]; exists {
|
||||
t.Fatalf("mId filter must be absent when no modules are excluded: %#v", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"91porn-server/common/stderr"
|
||||
"91porn-server/models/commod"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const rankingQueryMaxTime = 3 * time.Second
|
||||
|
||||
// FindCumulativeRanking keeps the ranking usable when the Redis period rank is
|
||||
// empty. It reuses persisted popularity counters and the existing compound
|
||||
// index for a bounded fallback query.
|
||||
func FindCumulativeRanking(newsType string, page commod.Page) ([]*VideoModel, bool, error) {
|
||||
if newsType == "" || page.PageNumber == 0 || page.PageSize == 0 || page.PageSize > 100 {
|
||||
return nil, false, stderr.ErrParamError
|
||||
}
|
||||
filter, sorts := cumulativeRankingQuery(newsType)
|
||||
opts := options.Find().
|
||||
SetSkip(page.Skip64()).
|
||||
SetLimit(page.Limit64()).
|
||||
SetSort(sorts).
|
||||
SetMaxTime(rankingQueryMaxTime)
|
||||
return FindList(filter, opts)
|
||||
}
|
||||
|
||||
func cumulativeRankingQuery(newsType string) (bson.M, bson.D) {
|
||||
return bson.M{
|
||||
"newsType": newsType,
|
||||
"status": bson.M{"$in": []int{CheckPass, Free}},
|
||||
}, bson.D{{Key: "likeCount", Value: -1}, {Key: "reviewAt", Value: -1}}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
package vidmod
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCalculateRecommendScore(t *testing.T) {
|
||||
if got, want := CalculateRecommendScore(1, 2, 3, 4), int64(34); got != want {
|
||||
t.Fatalf("score=%d want=%d", got, want)
|
||||
}
|
||||
if got := CalculateRecommendScore(0, 0, 0, 0); got != 0 {
|
||||
t.Fatalf("zero score=%d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonNegativeHistoryValue(t *testing.T) {
|
||||
if got := nonNegativeInt(-1); got != 0 {
|
||||
t.Fatalf("negative history=%d want=0", got)
|
||||
}
|
||||
if got := maxInt64(8, nonNegativeInt(3)); got != 8 {
|
||||
t.Fatalf("existing monotonic count overwritten: %d", got)
|
||||
}
|
||||
if got := maxInt64(2, nonNegativeInt(3)); got != 3 {
|
||||
t.Fatalf("history initialization=%d want=3", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package vidmod
|
||||
|
||||
import "time"
|
||||
|
||||
type SetDoc struct {
|
||||
PublisherID *uint64 `json:"publisherID" bson:"publisherID,omitempty"` //上传者ID
|
||||
Status *int `json:"status" bson:"status,omitempty"` //状态,0 未审核 1通过 2审核失败 3已删除-逻辑删除 默认为0
|
||||
DeleteAt *time.Time `json:"deleteAt" bson:"deleteAt,omitempty"` //删除时间,不能去除omitempty
|
||||
}
|
||||
|
||||
func (u *SetDoc) SetPublisherID(publisherID uint64) *SetDoc {
|
||||
u.PublisherID = &publisherID
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *SetDoc) SetStatus(status int) *SetDoc {
|
||||
u.Status = &status
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *SetDoc) SetDeleteAt(deleteAt time.Time) *SetDoc {
|
||||
u.DeleteAt = &deleteAt
|
||||
return u
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"91porn-server/common/constant"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/db"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type ObjectID = primitive.ObjectID
|
||||
|
||||
// PretendAccInitCoins 金币初始为1000时为马甲账号
|
||||
var PretendAccInitCoins int64 = 1000
|
||||
|
||||
// PretendAccDefaultCoins 马甲账号默认金币数
|
||||
var PretendAccDefaultCoins int64 = 10
|
||||
|
||||
const (
|
||||
// SP string = "SP" // 短视频帖子类型
|
||||
// COVER string = "COVER" // 图文帖子
|
||||
// PIC string = "PIC" // 图集帖子
|
||||
// AD_COVER string = "AD_COVER" // 图片广告
|
||||
// AD_SP string = "AD_SP" // 视频广告
|
||||
// SEED_LINK string = "SEED_LINK" // 种子链接帖子
|
||||
)
|
||||
|
||||
const (
|
||||
SP = constant.SP // 长视频帖子
|
||||
SHORT = constant.SHORT // 短视频帖子
|
||||
COVER = constant.COVER // 图文帖子
|
||||
PIC = constant.PIC // 图集帖子
|
||||
AD_COVER = constant.AD_COVER // 图片广告
|
||||
AD_SP = constant.AD_SP // 视频广告
|
||||
SEED_LINK = constant.SEED_LINK // 种子链接帖子
|
||||
)
|
||||
|
||||
const (
|
||||
// 影片质量
|
||||
High string = "high" //影片质量高 720P以上
|
||||
Middle string = "middle" //影片质量高 480P-720P以上
|
||||
Low string = "low" //影片质量高 480P以下
|
||||
|
||||
// 影片板式
|
||||
Vertical string = "vertical" //竖屏
|
||||
Horizontal string = "horizontal" //横屏
|
||||
Square string = "square" //方形屏
|
||||
)
|
||||
|
||||
// H265Status 表示视频 H.265 异步转码状态。
|
||||
// 状态流转:None/Failed -> Queued -> Pending -> Success/Failed。
|
||||
type H265Status int
|
||||
|
||||
const (
|
||||
H265StatusFailed H265Status = -1 // 转码失败,未超过失败次数上限时可重新入队
|
||||
H265StatusNone H265Status = 0 // 未加入转码队列
|
||||
H265StatusQueued H265Status = 1 // 已进入本地等待队列
|
||||
H265StatusPending H265Status = 2 // 已提交云端,等待轮询结果
|
||||
H265StatusSuccess H265Status = 3 // 转码成功,h265Url 可用
|
||||
|
||||
H265MaxFailCount = 3
|
||||
)
|
||||
|
||||
// MinFreeTime 付费视频最低免费时长
|
||||
const MinFreeTime = 3
|
||||
|
||||
type M = bson.M
|
||||
|
||||
type A = bson.A
|
||||
|
||||
type D = bson.D
|
||||
|
||||
const (
|
||||
WaitingCheck = 0 // 帖子未审核
|
||||
CheckPass = 1 // 帖子通过
|
||||
CheckFailure = 2 // 帖子审核失败
|
||||
Free = 3 // 帖子视为免费 老版本才有 该状态已经废弃
|
||||
IsDeleted = 4 // 帖子逻辑删除
|
||||
OffShelf = 5 // 帖子下架
|
||||
OnlinePass = 6 // 帖子定时上架
|
||||
|
||||
// 免费观看时长
|
||||
free10 = 10
|
||||
free60 = 60
|
||||
free120 = 120
|
||||
|
||||
second30 = 30
|
||||
minite10 = 10 * 60
|
||||
minite30 = 30 * 60
|
||||
minite60 = 60 * 60
|
||||
)
|
||||
|
||||
// VideoModel 视频
|
||||
type VideoModel struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` // ID
|
||||
NewsType string `json:"newsType" bson:"newsType"` // 帖子类型, SP,视频帖子,COVER
|
||||
PublisherID uint64 `json:"publisherID" bson:"publisherID"` // 上传者ID
|
||||
Title string `json:"title" bson:"title"` // 视频标题
|
||||
Content string `json:"content" bson:"content"` // 视频内容
|
||||
Tags []primitive.ObjectID `json:"tags" bson:"tags"` // 视频标签
|
||||
Activity []primitive.ObjectID `json:"activity" bson:"activity"` // 活动ID
|
||||
SourceID string `json:"sourceID" bson:"sourceID"` // 视频在仓库中的资源ID
|
||||
SourceURL string `json:"sourceURL" bson:"sourceURL"` // 视频资源地址Path
|
||||
H265Url string `json:"h265Url" bson:"h265Url"` // H.265 视频资源地址
|
||||
H265Status H265Status `json:"h265Status" bson:"h265Status,omitempty"` // H.265 异步转码状态
|
||||
H265FailCount int `json:"h265FailCount" bson:"h265FailCount,omitempty"` // H.265 转码失败次数
|
||||
H265QueuedAt time.Time `json:"h265QueuedAt" bson:"h265QueuedAt,omitempty"` // H.265 入队时间
|
||||
H265PendingAt time.Time `json:"h265PendingAt" bson:"h265PendingAt,omitempty"` // H.265 提交云端时间
|
||||
PreviewURL string `json:"previewURL" bson:"previewURL"` // 预览视频资源地址(并非所有视频都有预览)
|
||||
MimeType string `json:"mimeType" bson:"mimeType"` // 视频格式类型
|
||||
Filename string `json:"fileName" bson:"fileName,omitempty"` // 文件名称
|
||||
PlayTime uint `json:"playTime" bson:"playTime"` // 影片长度
|
||||
Cover string `json:"cover" bson:"cover"` // 封面大图
|
||||
CoverThumb string `json:"coverThumb" bson:"coverThumb"` // 封⾯小图
|
||||
SeriesCover []string `json:"seriesCover" bson:"seriesCover"` // 帖子套图
|
||||
VideoCover []string `json:"videoCover" bson:"videoCover"` // 视频截图
|
||||
SeriesNum int `json:"seriesNum" bson:"seriesNum"` // 图集数量
|
||||
Via string `json:"via" bson:"via"` // 来源 自拍,上传
|
||||
Rating int `json:"rating" bson:"rating"` // 总历史点击数
|
||||
PlayCount int `json:"playCount" bson:"playCount"` // 总播放量
|
||||
EffectivePlayCount int `json:"effectivePlayCount" bson:"effectivePlayCount"` // 有效播放量
|
||||
PurchaseCount int `json:"purchaseCount" bson:"purchaseCount"` // 视频购买人数
|
||||
LikeCount int `json:"likeCount" bson:"likeCount"` // 点赞数
|
||||
CollectCount int `json:"collectCount" bson:"collectCount"` // 收藏数
|
||||
CommentCount int `json:"commentCount" bson:"commentCount"` // 评论数
|
||||
ShareCount int `json:"shareCount" bson:"shareCount"` // 分享数
|
||||
RecommendLikeCount int64 `json:"-" bson:"recommendLikeCount"` // 推荐累计点赞,只增不减
|
||||
RecommendCollectCount int64 `json:"-" bson:"recommendCollectCount"` // 推荐累计收藏,只增不减
|
||||
RecommendCommentCount int64 `json:"-" bson:"recommendCommentCount"` // 推荐累计评论,只增不减
|
||||
RecommendShareCount int64 `json:"-" bson:"recommendShareCount"` // 推荐累计分享,只增不减
|
||||
RecommendScore int64 `json:"-" bson:"recommendScore"` // 每日全量计算的推荐分,仅变化时落库
|
||||
RecommendScoreAt time.Time `json:"-" bson:"recommendScoreAt,omitempty"` // 推荐分最后变化或初始化时间
|
||||
RecommendInitialized bool `json:"-" bson:"recommendInitialized"` // 历史累计是否初始化
|
||||
FakeLikeCount int `json:"fakeLikeCount" bson:"fakeLikeCount"` // 点赞假数据 总点赞量 = 真点赞量+假点赞量
|
||||
FakeCommentCount int `json:"fakeCommentCount" bson:"fakeCommentCount"` // 评论假数据 总评论量 = 真评论量+假评论量
|
||||
FakeShareCount int `json:"fakeShareCount" bson:"fakeShareCount"` // 分享假数据 总分享量 = 真分享量+假分享量
|
||||
FakePlayCount int `json:"fakePlayCount" bson:"fakePlayCount"` // 播放假数据 总播放量 = 真播放量+假播放量
|
||||
Status int `json:"status" bson:"status"` // 状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
||||
Reason string `json:"reason" bson:"reason,omitempty"` // 视频未通过审核时的理由
|
||||
Location primitive.ObjectID `json:"location" bson:"location"` // 位置
|
||||
FreeTime int `json:"freeTime" bson:"freeTime,omitempty"` // 免费观影时长
|
||||
Coins int64 `json:"coins" bson:"coins"` // 定价
|
||||
Size int `json:"size" bson:"size"` // 文件大小 byte
|
||||
Resolution string `json:"resolution" bson:"resolution"` // 分辨率
|
||||
Width int `json:"width" bson:"width"` // 视频宽度
|
||||
Height int `json:"height" bson:"height"` // 视频高度
|
||||
Ratio float64 `json:"ratio" bson:"ratio"` // 宽高比
|
||||
Quality string `json:"quality" bson:"quality,omitempty"` // 视频质量 720P以上高质量-high 480-720P 中等质量-middle 其他为low
|
||||
Direction string `json:"direction" bson:"direction,omitempty"` // 视频版式 vertical-竖屏 horizontal-横屏 square-方屏
|
||||
MD5 string `json:"md5" bson:"md5,omitempty"` // 文件摘要
|
||||
Actor string `json:"actor" bson:"actor,omitempty"` // 演员
|
||||
Chosen bool `json:"chosen" bson:"chosen"` // 是否精选
|
||||
ChosenDate time.Time `json:"chosenDate" bson:"chosenDate,omitempty"` // 精选刷新时间
|
||||
FreeArea bool `json:"freeArea" bson:"freeArea"` // 免费专区
|
||||
FreeAreaDate time.Time `json:"freeAreaDate" bson:"freeAreaDate,omitempty"` // 加入免费专区刷新时间
|
||||
IsHideLocation bool `json:"isHideLocation" bson:"isHideLocation"` // 是否隐藏地址
|
||||
RecoWeight int `json:"recoWeight" bson:"recoWeight"` // 推荐权重 -1,不可推荐
|
||||
NewUpdatedAt string `json:"newUpdatedAt" bson:"newUpdatedAt"` // 从源站同步信息 使用的标识符
|
||||
ReviewAt time.Time `json:"reviewAt" bson:"reviewAt,omitempty"` // 审核时间
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt,omitempty"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt,omitempty"` // 刷新时间
|
||||
DeleteAt *time.Time `json:"deleteAt" bson:"deleteAt,omitempty"` // 删除时间,不能去除omitempty
|
||||
IsTopping bool `json:"isTopping" bson:"isTopping"` // IsTopping
|
||||
IsRecommend bool `json:"isRecommend" bson:"isRecommend"` // 力荐
|
||||
IsChoosen bool `json:"isChoosen" bson:"isChoosen"` // 置精(本项目的社区精华在使用)
|
||||
ReviewAccount string `json:"reviewAccount" bson:"reviewAccount"` // 审核人
|
||||
LinkUrl string `json:"linkUrl" bson:"linkUrl"` // 广告跳转连接 目前只有广告帖子有用
|
||||
Rewarded decimal.Decimal `json:"rewarded" bson:"rewarded"` // 打赏金额
|
||||
FakeRewarded decimal.Decimal `json:"fakeRewarded" bson:"fakeRewarded"` // (假)获得打赏金额
|
||||
SortCode int `json:"sortCode" bson:"sortCode"` // 排序号 目前只有广告帖子有用
|
||||
TagSort interface{} `json:"tagSort" bson:"tagSort"` // 标签排序
|
||||
LiaoBaTop bool `json:"liaoBaTop" bson:"liaoBaTop"` // "撩吧"页面置顶
|
||||
LiaoBaTopSort int `json:"liaoBaTopSort" bson:"liaoBaTopSort"` // 置顶排序号
|
||||
WorksSort int `json:"worksSort" bson:"worksSort"` // 作品排序-最多5个
|
||||
PageViewCount int64 `json:"pageViewCount" bson:"pageViewCount"` // 视频页面展示次数
|
||||
ActivityID primitive.ObjectID `json:"activityId" bson:"activityId"` // 参赛视频活动ID
|
||||
Hot float64 `json:"hot" bson:"hot"` // 热度值
|
||||
HappinessPlazaTop int32 `json:"happinessPlazaTop" bson:"happinessPlazaTop"` // 幸福广场置顶
|
||||
MDSID string `json:"mdsID" bson:"mdsID"` // 媒体资源库id
|
||||
VerticalCover string `json:"verticalCover" bson:"verticalCover"` // 竖版封面
|
||||
MID string `json:"mId" bson:"mId"` // 模块ID
|
||||
UpTag string `json:"upTag" bson:"upTag"` // 博主认证
|
||||
ShareSort int `json:"-" bson:"shareSort,omitempty"` // 分享视频列表排序. 出现在当用户分享视频时的推荐列表里. <=0时表示不推荐
|
||||
SeedLinkUrl string `json:"seedLinkUrl" bson:"seedLinkUrl,omitempty"` // 种子链接
|
||||
SeedSize uint64 `json:"seedSize" bson:"seedSize,omitempty"` // 种子影片大小 byte
|
||||
SeedPlayTime uint64 `json:"seedPlayTime" bson:"seedPlayTime,omitempty"` // 种子影片时长
|
||||
DiscountAreaId primitive.ObjectID `json:"discountAreaId" bson:"discountAreaId,omitempty"` // 折扣专区
|
||||
RichText string `json:"richText" bson:"richText"` // 富文本内容
|
||||
PreviewStart int `json:"previewStart" bson:"previewStart"` // 预览时间起始点
|
||||
TimeNodeList []TimeNode `json:"timeNodeList" bson:"timeNodeList"` // 时间节点
|
||||
DownloadAllow int `json:"downloadAllow" bson:"downloadAllow"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
ShowType int `json:"showType" bson:"showType"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
}
|
||||
|
||||
type TimeNode struct {
|
||||
Time int `json:"time" bson:"time"` // 时间节点 单位秒
|
||||
Name string `json:"name" bson:"name"` // 节点名称
|
||||
Img string `json:"img" bson:"img"` // 图片
|
||||
}
|
||||
|
||||
type ESVideo struct {
|
||||
ID primitive.ObjectID `json:"id"` // ID
|
||||
PublisherID uint64 `json:"publisherID"` // 上传者ID
|
||||
NewsType string `json:"newsType"` // 帖子类型, SP,视频帖子,COVER
|
||||
Title string `json:"title"` // 视频标题
|
||||
Tags []primitive.ObjectID `json:"tags"` // 视频标签
|
||||
TagsName []string `json:"tagsName"` // 标签名称
|
||||
Filename string `json:"fileName"` // 文件名称
|
||||
PlayCount int `json:"playCount"` // 总播放量
|
||||
PurchaseCount int `json:"purchaseCount"` // 视频购买人数
|
||||
LikeCount int `json:"likeCount"` // 点赞数
|
||||
CollectCount int `json:"collectCount" ` // 收藏数
|
||||
CommentCount int `json:"commentCount"` // 评论数
|
||||
ShareCount int `json:"shareCount"` // 分享数
|
||||
FakeLikeCount int `json:"fakeLikeCount"` // 点赞假数据 总点赞量 = 真点赞量+假点赞量 常威确认
|
||||
FakeShareCount int `json:"fakeShareCount"` // 分享假数据 总分享量 = 真分享量+假分享量 常威确认
|
||||
FakePlayCount int `json:"fakePlayCount"` // 播放假数据 总播放量 = 真播放量+假播放量 常威确认
|
||||
EffectivePlayCount int `json:"effectivePlayCount"` // 有效播放量
|
||||
Hot float64 `json:"hot"` // 视频热度值
|
||||
PlayTime uint `json:"playTime"` // 视频时长
|
||||
Status int `json:"status"` // 状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
||||
Coins int64 `json:"coins"` // 定价
|
||||
Location primitive.ObjectID `json:"location"` // 位置
|
||||
CreatedAt time.Time `json:"createdAt"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updatedAt"` // 刷新时间
|
||||
ReviewedAt time.Time `json:"reviewAt"` // 审核时间
|
||||
}
|
||||
|
||||
type ShareInfo struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"`
|
||||
Title string `json:"title" bson:"title"`
|
||||
PlayTime uint `json:"playTime" bson:"playTime"`
|
||||
Cover string `json:"cover" bson:"cover"`
|
||||
SourceUrl string `json:"sourceUrl" bson:"sourceURL"`
|
||||
}
|
||||
|
||||
// 推荐model
|
||||
type RecoVideoModel struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"`
|
||||
ChosenDate time.Time `json:"chosenDate" bson:"chosenDate,omitempty"` //精选刷新时间
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt,omitempty"` //创建时间
|
||||
}
|
||||
|
||||
type ESVideoSource struct {
|
||||
ID primitive.ObjectID `json:"_id"`
|
||||
Source ESVideo `json:"_source"`
|
||||
}
|
||||
|
||||
type ESVideoSourceWithTotal struct {
|
||||
Hits []ESVideoSource `json:"hits"`
|
||||
Total Total `json:"total"`
|
||||
}
|
||||
|
||||
type Total struct {
|
||||
Relation string `json:"relation"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
// CityCount 统计播放量/访问量
|
||||
type CityCount struct {
|
||||
ID primitive.ObjectID `bson:"_id"`
|
||||
Count int `bson:"count"`
|
||||
}
|
||||
|
||||
// PulisherVideoID 推荐发布者
|
||||
type PulisherVideoID struct {
|
||||
ID uint64 `bson:"_id"`
|
||||
VideoID primitive.ObjectID `bson:"videoID"`
|
||||
}
|
||||
|
||||
// UserVideoCount 我的视频数
|
||||
type UserVideoCount struct {
|
||||
ID uint64 `bson:"_id"`
|
||||
Count int `bson:"count"`
|
||||
}
|
||||
|
||||
func Init() {
|
||||
mdb = db.Init(table)
|
||||
initIndex()
|
||||
}
|
||||
|
||||
type PrefetchVideoModel struct {
|
||||
M3u8Url string `bson:"sourceURL"`
|
||||
Images []string `bson:"seriesCover"`
|
||||
}
|
||||
type VideoModelSort []*VideoModel
|
||||
|
||||
func (a VideoModelSort) Len() int { return len(a) }
|
||||
|
||||
func (a VideoModelSort) Less(i, j int) bool {
|
||||
if a[i].LiaoBaTopSort != a[j].LiaoBaTopSort {
|
||||
return a[i].LiaoBaTopSort > a[j].LiaoBaTopSort
|
||||
}
|
||||
if !a[i].ReviewAt.Equal(a[j].ReviewAt) {
|
||||
return a[i].ReviewAt.After(a[j].ReviewAt)
|
||||
}
|
||||
return a[i].ID.Hex() > a[j].ID.Hex()
|
||||
}
|
||||
|
||||
func (a VideoModelSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (info *VideoModel) IsCover() bool {
|
||||
return info.NewsType == COVER || info.NewsType == SEED_LINK || info.NewsType == PIC || info.MDSID != ""
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type ModuleLatestAt struct {
|
||||
ModuleID string `bson:"_id"`
|
||||
LatestAt time.Time `bson:"latestAt"`
|
||||
}
|
||||
|
||||
// LatestReviewAt 返回过滤条件下最近一次审核通过时间。
|
||||
func LatestReviewAt(filter bson.M) (*time.Time, error) {
|
||||
var list []VideoModel
|
||||
opts := options.Find().
|
||||
SetSort(bson.D{{Key: "reviewAt", Value: -1}, {Key: "_id", Value: -1}}).
|
||||
SetLimit(1).
|
||||
SetProjection(bson.M{"reviewAt": 1})
|
||||
if err := coll(nil).Find(&list, filter, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) == 0 || list[0].ReviewAt.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
latest := list[0].ReviewAt
|
||||
return &latest, nil
|
||||
}
|
||||
|
||||
// LatestReviewAtByModules 批量返回各亚模块最近一次内容审核通过时间。
|
||||
func LatestReviewAtByModules(moduleIDs []string) (map[string]time.Time, error) {
|
||||
result := make(map[string]time.Time, len(moduleIDs))
|
||||
if len(moduleIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
pipeline := []bson.M{
|
||||
{"$match": bson.M{
|
||||
"status": CheckPass,
|
||||
"mId": bson.M{"$in": moduleIDs},
|
||||
"reviewAt": bson.M{"$gt": time.Time{}},
|
||||
}},
|
||||
{"$group": bson.M{
|
||||
"_id": "$mId",
|
||||
"latestAt": bson.M{"$max": "$reviewAt"},
|
||||
}},
|
||||
}
|
||||
var rows []ModuleLatestAt
|
||||
if err := coll(nil).Aggregate(&rows, pipeline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, row := range rows {
|
||||
if row.ModuleID != "" && !row.LatestAt.IsZero() {
|
||||
result[row.ModuleID] = row.LatestAt
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWatchConsumeReqVideoIDCompatibility(t *testing.T) {
|
||||
tests := []struct {
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
{body: `{"videoId":"new-field"}`, want: "new-field"},
|
||||
{body: `{"vid":"legacy-field"}`, want: "legacy-field"},
|
||||
{body: `{"videoId":"new-field","vid":"legacy-field"}`, want: "new-field"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
var req WatchConsumeReq
|
||||
if err := json.Unmarshal([]byte(tt.body), &req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := req.GetVideoID(); got != tt.want {
|
||||
t.Fatalf("GetVideoID() = %q, want %q", got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchConsumeRespJSON(t *testing.T) {
|
||||
data, err := json.Marshal(WatchConsumeResp{
|
||||
IsCan: true,
|
||||
WatchCount: 2,
|
||||
TotalWatchCount: 3,
|
||||
Consumed: false,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, field := range []string{
|
||||
`"isCan":true`,
|
||||
`"watchCount":2`,
|
||||
`"totalWatchCount":3`,
|
||||
`"consumed":false`,
|
||||
} {
|
||||
if !strings.Contains(string(data), field) {
|
||||
t.Fatalf("%s missing from response: %s", field, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
package vidmod
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"91porn-server/common/stderr"
|
||||
"91porn-server/models/commod"
|
||||
"91porn-server/models/v/locmod"
|
||||
"91porn-server/models/v/usermod"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
const (
|
||||
Merging = "Merging" // 合并中
|
||||
MergeCompleted = "MergeCompleted" //合并完成
|
||||
MergeError = "MergeError" // 合并失败
|
||||
Converting = "Converting" //格式转换中
|
||||
ConvertCompleted = "ConvertCompleted" //转换完成
|
||||
ConvertError = "ConvertError" // 转换失败
|
||||
UploadLoadingToFs = "UpLoading" // 正在上传中
|
||||
UploadCompleted = "UploadCompleted" // 上传完成
|
||||
Completed = "Completed" // 完成
|
||||
|
||||
FileUploadError = "FileUploadError" //文件上传失败
|
||||
)
|
||||
|
||||
// WebSubmitReq web端视频上传请求
|
||||
type WebSubmitReq struct {
|
||||
UID uint64 `form:"uid" json:"uid" binding:"required"`
|
||||
NewsType string `form:"newsType" json:"newsType"`
|
||||
Title string `form:"title" json:"title"`
|
||||
Content string `form:"content" json:"content" bson:"content"`
|
||||
Tags []string `form:"tags" json:"tags" binding:"required"`
|
||||
PlayTime uint `form:"playTime" json:"playTime"`
|
||||
Cover string `form:"cover" json:"cover"`
|
||||
CoverThumb string `form:"coverThumb" json:"coverThumb"`
|
||||
VerticalCover string `form:"verticalCover" json:"verticalCover" ` //竖版封
|
||||
SeriesCover []string `form:"seriesCover" json:"seriesCover"`
|
||||
Via string `form:"via" json:"via"`
|
||||
Coins int64 `form:"coins" json:"coins"`
|
||||
Size int `form:"size" json:"size"`
|
||||
Resolution string `form:"resolution" json:"resolution"`
|
||||
Ratio float64 `json:"ratio" bson:"ratio"` //宽高比
|
||||
MimeType string `form:"mimeType" json:"mimeType"`
|
||||
Location locmod.Location `form:"location" json:"location"`
|
||||
Actor string `form:"actor" json:"actor"`
|
||||
SourceID string `form:"sourceID" json:"sourceID"`
|
||||
SourceURL string `form:"sourceURL" json:"sourceURL"`
|
||||
MD5 string `form:"md5" json:"md5"`
|
||||
Filename string `form:"filename" json:"filename"`
|
||||
FreeTime int `form:"freeTime" json:"freeTime"`
|
||||
Width int `form:"width" json:"width" ` // 视频宽度
|
||||
Height int `form:"height" json:"height" ` // 视频高度
|
||||
MDSID string `json:"mdsID" `
|
||||
Status int `json:"status"` // 状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
||||
Account string `json:"account"` //审核帐号
|
||||
SeedLinkUrl string `form:"seedLinkUrl" json:"seedLinkUrl" bson:"seedLinkUrl"` // 种子链接内容
|
||||
SeedSize uint64 `json:"seedSize" bson:"seedSize,omitempty"` // 种子影片大小 byte
|
||||
SeedPlayTime uint64 `json:"seedPlayTime" bson:"seedPlayTime,omitempty"` // 种子影片时长
|
||||
RichText string `form:"richText" json:"richText"` // 富文本内容
|
||||
PreviewStart int `form:"previewStart" json:"previewStart"` // 预览时间起始点
|
||||
}
|
||||
|
||||
// ListReq 视频列表请求
|
||||
type ListReq struct {
|
||||
Status int `form:"status" json:"status"`
|
||||
Key string `form:"key" json:"key"`
|
||||
Value int `form:"value" json:"value"`
|
||||
IsFree int `form:"isFree" json:"isFree"`
|
||||
IsUserUp int `form:"isUserUp" json:"isUserUp"`
|
||||
Title string `form:"title" json:"title"`
|
||||
UID uint64 `form:"uid" json:"uid"`
|
||||
Start time.Time `form:"start" json:"start"`
|
||||
End time.Time `form:"end" json:"end"`
|
||||
Chosen int `form:"chosen" json:"chosen"`
|
||||
FreeArea int `form:"freeArea" json:"freeArea"`
|
||||
Tag string `form:"tag" json:"tag"`
|
||||
ID string `form:"id" json:"id"`
|
||||
IsPretendAcc int `form:"isPretendAcc" json:"isPretendAcc"` //是否马甲账号,0,所有,1,是,2,否
|
||||
NewsType string `form:"newsType" json:"newsType"` //帖子类型,0,所以;1,视频,2,图集
|
||||
LiaoBaTop *bool `form:"liaoBaTop" json:"liaoBaTop"` //"撩吧"页面置顶
|
||||
SectionID string `form:"sectionID" json:"sectionID"` //专题ID
|
||||
IsSortedUnderModule bool `form:"isSortedUnderModule" json:"isSortedUnderModule"` //视频在专题下是否设置了排序码
|
||||
IsPush bool `form:"isPush" bson:"isPush"` //是否强推
|
||||
IsRecommended *bool `form:"isRecommended" bson:"isRecommended"`
|
||||
IsHappinessPlazaTop *bool `form:"isHappinessPlazaTop"` // 是否幸福广场置顶
|
||||
ReviewAccount string `form:"reviewAccount" json:"reviewAccount"` // 审核管理员账号
|
||||
ShowType *int `json:"showType" form:"showType"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
commod.Page
|
||||
}
|
||||
|
||||
// AwsPullReq 从AWS拉取视频信息请求接口
|
||||
type AwsPullReq struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag"`
|
||||
MaxPlayTime int `json:"maxPlayTime"`
|
||||
MinPlayTime int `json:"minPlayTime"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Page int `json:"page"`
|
||||
Status string `json:"status"`
|
||||
NewUpdateAt string `json:"newUpdateAt"`
|
||||
}
|
||||
|
||||
// PullReq 前端参数传递
|
||||
type PullReq struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
SyncType string `json:"syncType" binding:"required"` //同步类型 SP-同步视频 SERIES-同步套图
|
||||
UID []uint64 `json:"uids" binding:"required"`
|
||||
Cityes []string `json:"cityes" binding:"required"`
|
||||
RStatus string `json:"rStatus" binding:"required"` //审核状态,
|
||||
Coins string `json:"coins" binding:"required"`
|
||||
Retry bool `json:"retry"` //表示从某一批次 从头开始导入数据
|
||||
AwsPullReq
|
||||
}
|
||||
|
||||
// EditInfo 修改内容
|
||||
type EditInfo struct {
|
||||
NewsType *string `form:"newsType" json:"newsType" bson:"newsType,omitempty"` // 帖子类型, SP,COVER,SEED_LINK
|
||||
PublisherID *uint64 `form:"publisherID" json:"publisherID" bson:"publisherID,omitempty"` // 上传者ID
|
||||
Cover *string `form:"cover" json:"cover,omitempty" bson:"cover,omitempty"` //封面大图
|
||||
CoverThumb *string `form:"coverThumb" json:"coverThumb,omitempty" bson:"coverThumb,omitempty"`
|
||||
FakeLikeCount *int `form:"fakeLikeCount" json:"fakeLikeCount,omitempty" bson:"fakeLikeCount,omitempty"` //点赞假数据
|
||||
FakeCommentCount *int `form:"fakeCommentCount" json:"fakeCommentCount,omitempty" bson:"fakeCommentCount,omitempty"` //评论假数据
|
||||
FakeShareCount *int `form:"fakeShareCount" json:"fakeShareCount,omitempty" bson:"fakeShareCount,omitempty"` //分享假数据
|
||||
FakePlayCount *int `form:"fakePlayCount" json:"fakePlayCount,omitempty" bson:"fakePlayCount,omitempty"` //播发假数据
|
||||
FreeTime *int `form:"freeTime" json:"freeTime,omitempty" bson:"freeTime,omitempty"` //免费观影时长
|
||||
Coins *uint `form:"coins" json:"coins,omitempty" bson:"coins,omitempty"` //定价
|
||||
Title *string `form:"title" json:"title,omitempty" bson:"title,omitempty"`
|
||||
Content *string `form:"content" json:"content,omitempty" bson:"content,omitempty"` // 帖子内容
|
||||
UpTag *string `form:"upTag" json:"upTag,omitempty" bson:"upTag,omitempty"` // 博主认证
|
||||
Status *int `form:"status" json:"status" bson:"status,omitempty"` // 状态
|
||||
CreatedAt *string `form:"createdAt" json:"createdAt,omitempty" bson:"createdAt,omitempty"` //视频上传时间
|
||||
SeriesCover *[]string `form:"seriesCover" json:"seriesCover" bson:"seriesCover,omitempty"` //帖子套图
|
||||
SeriesNum *int `form:"seriesNum" json:"seriesNum" bson:"seriesNum,omitempty"` //图集数量
|
||||
RecoWeight *int `form:"recoWeight" json:"recoWeight" bson:"recoWeight,omitempty"` //推荐权重 -1,不可推荐
|
||||
LinkUrl *string `form:"linkUrl" json:"linkUrl" bson:"linkUrl,omitempty"`
|
||||
PreviewURL *string `json:"previewURL" bson:"previewURL,omitempty"` // 预览视频资源地址(并非所有视频都有预览)
|
||||
SortCode *int `form:"sortCode" json:"sortCode" bson:"sortCode"` //排序号 目前只有广告帖子有用
|
||||
FakeRewarded *int `form:"fakeRewarded" json:"fakeRewarded" bson:"fakeRewarded,omitempty"` //(假)获得打赏次数
|
||||
LiaoBaTop *bool `form:"liaoBaTop" json:"liaoBaTop" bson:"liaoBaTop,omitempty"` //"撩吧"页面置顶
|
||||
LiaoBaTopSort *int `form:"liaoBaTopSort" json:"liaoBaTopSort" bson:"liaoBaTopSort,omitempty"` //"撩吧"页面置顶 排序
|
||||
WorksSort *int `form:"worksSort" json:"worksSort" bson:"worksSort,omitempty"` //作品排序
|
||||
ActivityID *string `form:"activityId" json:"activityId" bson:"activityId,omitempty"` //参赛作品活动ID
|
||||
HappinessPlazaTop *int32 `json:"happinessPlazaTop" bson:"happinessPlazaTop,omitempty"` // 幸福广场置顶
|
||||
ShareSort *int `json:"shareSort" bson:"shareSort,omitempty"` // 分享视频列表排序. 出现在当用户分享视频时的推荐列表里. <=0时表示不推荐
|
||||
SeedLinkUrl *string `json:"seedLinkUrl" bson:"seedLinkUrl,omitempty"` // 种子下载链接
|
||||
SeedSize *int `json:"seedSize" bson:"seedSize,omitempty"` // 种子影片大小 byte
|
||||
SeedPlayTime *uint `json:"seedPlayTime" bson:"seedPlayTime,omitempty"` // 种子影片时长
|
||||
SeedDownloadDesc *string `json:"seedDownloadDesc" bson:"seedDownloadDesc,omitempty"` // 种子下载说明
|
||||
SeedTips *string `json:"seedTips" bson:"seedTips,omitempty"` // 种子温馨提示
|
||||
PreviewStart *int `json:"previewStart" form:"previewStart" bson:"previewStart,omitempty"` // 预览时间起点
|
||||
RichText *string `json:"richText" bson:"-"` // 富文本内容(单独更新,不需要bson)
|
||||
TimeNodeList *[]TimeNode `json:"timeNodeList" form:"timeNodeList"` // 时间节点
|
||||
ShowType *int `json:"showType" bson:"showType,omitempty"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
DownloadAllow *int `json:"downloadAllow" bson:"downloadAllow,omitempty"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
}
|
||||
|
||||
// EditReq 视频更新参数
|
||||
type EditReq struct {
|
||||
ID string `form:"id" json:"id"`
|
||||
Type int `form:"type" json:"type"` //0 所有 1只标签 2只更新富文本
|
||||
Tags []string `form:"tags" json:"tags" bson:"tags,omitempty"`
|
||||
TagSort []struct {
|
||||
TagId string `form:"tagId" json:"tagId"`
|
||||
SortCode int `form:"sortCode" json:"sortCode"`
|
||||
} `form:"tagSort" json:"tagSort"`
|
||||
EditInfo
|
||||
}
|
||||
|
||||
// DeleteReq 视频批量操作参数
|
||||
type DeleteReq struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
|
||||
// BatchReq 视频批量操作参数
|
||||
type BatchReq struct {
|
||||
IDs []string `json:"ids"`
|
||||
Pass int `json:"pass"` //1通过 2不通过 3通过并修改成免费视屏
|
||||
Reason string `json:"reason"` //不通过时的理由
|
||||
}
|
||||
|
||||
// BatchEditInfo 批量更新的结构
|
||||
type BatchEditInfo struct {
|
||||
ID string `json:"id"`
|
||||
Reason string `json:"reason"`
|
||||
Coins int `json:"coins"`
|
||||
FreeTime int `json:"freeTime"`
|
||||
}
|
||||
|
||||
// EditManyReq 视频批量更新参数
|
||||
type EditManyReq struct {
|
||||
IDs []string `json:"ids"`
|
||||
Field string `json:"field"` //批量更新字段
|
||||
Status bool `json:"status"` //更新视频状态,比如免费专区/取消免费专区,收费/取消收费,精选/取消精选
|
||||
TagIds []primitive.ObjectID `json:"tagIds"` // tag更新
|
||||
Coins *int `json:"coins"` // 价格
|
||||
PublisherID *uint64 `form:"publisherID" json:"publisherID"` // 上传者ID
|
||||
OriginPublisherID *uint64 `form:"originPublisherID" json:"originPublisherID"` // 原上传者ID(用于转移功能)
|
||||
UpStatus *int `json:"upStatus"` // 上架状态 1-上架 5-下架
|
||||
Tags []string `form:"tags" json:"tags" bson:"tags,omitempty"`
|
||||
FreeTime *int `json:"freeTime"` // 免费观影时长
|
||||
DownloadAllow *int `json:"downloadAllow" bson:"downloadAllow"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
}
|
||||
|
||||
// PassManyReq 视频批量审核参数
|
||||
type PassManyReq struct {
|
||||
Infos []BatchEditInfo `json:"infos"`
|
||||
Pass int `json:"pass"`
|
||||
}
|
||||
|
||||
// WebVideo 返回视频的基本信息
|
||||
type WebVideo struct {
|
||||
ID primitive.ObjectID `json:"id" xlsx:"帖子ID"` // 帖子ID
|
||||
NewsType string `json:"newsType" xlsx:"-"` // 帖子类型, VID,视频帖子,COVER
|
||||
Publisher usermod.BaseInfo `json:"publisher" xlsx:"-"` // 帖子发布者ID
|
||||
PubliserID uint64 `json:"-" xlsx:"用户ID"` // 用户ID
|
||||
PublisherName string `json:"-" xlsx:"用户昵称"` // 用户昵称
|
||||
Title string `json:"title" xlsx:"视频标题"` // 帖子标题
|
||||
Content string `json:"content" bson:"content"` // 视频内容
|
||||
Tags []TagInfo `json:"tags" xlsx:"-"` // 帖子标签列表
|
||||
SourceID string `json:"sourceID" xlsx:"-"` // 帖子源站ID
|
||||
SourceURL string `json:"sourceURL" xlsx:"-"` // 帖子源站URL
|
||||
PreviewURL string `json:"previewURL" bson:"previewURL"` // 预览视频资源地址(并非所有视频都有预览)
|
||||
FileName string `json:"fileName" xlsx:"-"` // 文件名
|
||||
PlayTime uint `json:"playTime" xlsx:"总时长"` // 视频播放时长
|
||||
Status int `json:"status" xlsx:"-"` // 状态,0 未审核 1通过 2审核失败 3通过并认为免费 默认为0
|
||||
Cover string `json:"cover" xlsx:"-"` // 视频封面
|
||||
CoverThumb string `json:"coverThumb" xlsx:"-"` // 视频封面缩略图
|
||||
SeriesCover []string `json:"seriesCover" xlsx:"-"` // 封面套图
|
||||
IsUser bool `json:"isUser" xlsx:"-"` // TBD
|
||||
PlayCount int `json:"playCount" xlsx:"播放量(真)"` // 视频播放量(真)
|
||||
LikeCount int `json:"likeCount" xlsx:"点赞量(真)"` // 视频点赞量(真)
|
||||
CommentCount int `json:"commentCount" xlsx:"评论数(真)"` // 视频评论量(真)
|
||||
ShareCount int `json:"shareCount" xlsx:"-"` // 视频分享量(真)
|
||||
FakeLikeCount int `json:"fakeLikeCount" xlsx:"-"` // 点赞假数据
|
||||
FakeCommentCount int `json:"fakeCommentCount" xlsx:"-"` // 评论假数据
|
||||
FakeShareCount int `json:"fakeShareCount" xlsx:"-"` // 分享假数据
|
||||
FakePlayCount int `json:"fakePlayCount" xlsx:"-"` // 播发假数据
|
||||
LikeRate float64 `json:"likeRate" xlsx:"点赞率"` // 点赞率
|
||||
Coins int64 `json:"coins" xlsx:"价格"` // 视频定价金币数
|
||||
Resolution string `json:"resolution" xlsx:"-"` // 视频分辨率
|
||||
FreeTime int `json:"freeTime" xlsx:"免费时长"` // 视频免费观看时长
|
||||
Chosen bool `json:"chosen" xlsx:"-"` // 是否精选
|
||||
FreeArea bool `json:"freeArea" xlsx:"-"` // 是否免费专区
|
||||
Pushing bool `json:"pushing" xlsx:"-"` // 是否推送
|
||||
Size int `json:"size" xlsx:"-"` // 文件大小 byte
|
||||
CreatedAt time.Time `json:"createdAt" xlsx:"上传时间"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updatedAt" xlsx:"更新时间"` // 更新时间
|
||||
Reason string `json:"reason" xlsx:"-"` // 审核失败的理由
|
||||
IsTopping bool `json:"isTopping" xlsx:"-"` // TBD
|
||||
IsRecommend bool `json:"isRecommend" xlsx:"-"` // 力荐
|
||||
IsChoosen bool `json:"isChoosen" xlsx:"是否精选"` // 置精
|
||||
IsMadou bool `json:"isMadou" xlsx:"-"` // 是否麻豆上传
|
||||
RecoWeight int `json:"recoWeight" xlsx:"-"` // 推荐权重 -1,不可推荐
|
||||
ReviewAccount string `json:"reviewAccount" xlsx:"-"` // 审核人
|
||||
LinkUrl string `json:"linkUrl" xlsx:"-"` // TBD
|
||||
SortCode int `json:"sortCode" xlsx:"-"` // 排序号 目前只有广告帖子有用
|
||||
TagSort []TagSort `json:"tagSort" xlsx:"-"` // 标签排序
|
||||
Rewarded decimal.Decimal `json:"rewarded" xlsx:"-"` // 打赏
|
||||
FakeRewarded decimal.Decimal `json:"fakeRewarded" xlsx:"-"` // (假)获得打赏
|
||||
LiaoBaTop bool `json:"liaoBaTop" xlsx:"-"` // 是否撩吧置顶
|
||||
LiaoBaTopSort int `json:"liaoBaTopSort" xlsx:"-"` // 撩吧置顶排序码
|
||||
WorksSort int `json:"worksSort" xlsx:"-"` // TBD
|
||||
TotalSellDays float64 `json:"totalSellDays" xlsx:"售卖天数"` // 视频总的售卖天数: 从审核通过后开始计算
|
||||
TotalSellCount int64 `json:"totalSellCount" xlsx:"售卖次数"` // 视频总的售卖次数
|
||||
TotalSellAmount int64 `json:"totalSellAmount" xlsx:"售卖金币"` // 视频总的售卖金币数
|
||||
PurchaseRate float64 `json:"purchaseRate" xlsx:"成交率"` // 视频成交率: 视频总的售卖次数 / 视频播放量
|
||||
SectionID primitive.ObjectID `json:"sectionID" xlsx:"-"` // 视频所属专题ID
|
||||
SortCodeUnderSection int `json:"sortCodeUnderSection" xlsx:"-"` // 视频在某个专题下的排序
|
||||
SectionName string `json:"sectionName" bson:"sectionName"` // 专题名称
|
||||
ModuleName string `json:"moduleName" bson:"moduleName"` // 父专题名称
|
||||
PageViewCount int64 `json:"pageViewCount" xlsx:"展现量"` // 视频页面展示次数
|
||||
HitRate float64 `json:"hitRate" xlsx:"点击率"` // 视频点击率 = 页面展示次数 / 视频播放量
|
||||
TagsExport string `json:"-" xlsx:"标签"` // 标签,仅用于导出
|
||||
ActivityID primitive.ObjectID `json:"activityId" xlsx:"-"` // 参赛视频活动ID
|
||||
HappinessPlazaTop int32 `json:"happinessPlazaTop" xlsx:"-"` // 幸福广场置顶
|
||||
ReviewAt time.Time `json:"reviewAt" xlsx:"-"` // 审核时间
|
||||
ShareSort int `json:"shareSort" bson:"shareSort,omitempty"` // 分享视频列表排序. 出现在当用户分享视频时的推荐列表里. <=0时表示不推荐
|
||||
SeedLinkUrl string `json:"seedLinkUrl" bson:"seedLinkUrl,omitempty"` // 种子链接
|
||||
SeedSize uint64 `json:"seedSize" bson:"seedSize,omitempty"` // 种子影片大小 byte
|
||||
SeedPlayTime uint64 `json:"seedPlayTime" bson:"seedPlayTime,omitempty"` // 种子影片时长
|
||||
RichText string `json:"richText" bson:"richText"` // 富文本内容
|
||||
PreviewStart int `json:"previewStart" bson:"previewStart"` // 预览时间起始点
|
||||
TimeNodeList []TimeNode `json:"timeNodeList" bson:"timeNodeList"` // 时间节点
|
||||
DownloadAllow int `json:"downloadAllow" bson:"downloadAllow"` // 允许下载的VIP级别,0表示不允许下载 1表示VIP 2表示免费
|
||||
ShowType int `json:"showType" bson:"showType"` // 0-所有的人都可以看 1-奇数可看 2-偶数可看
|
||||
}
|
||||
|
||||
type TagSort struct {
|
||||
TagId string `json:"tagId"`
|
||||
TagName string `json:"tagName"`
|
||||
SortCode int `json:"sortCode"`
|
||||
}
|
||||
|
||||
// ListResp web 视频列表应答
|
||||
type ListResp struct {
|
||||
VInfos []*WebVideo `json:"vInfos"` // 帖子列表
|
||||
Total int64 `json:"total"` // 帖子总量
|
||||
}
|
||||
|
||||
// AwsPullResp 从AWS拉取视频响应体
|
||||
type AwsPullResp struct {
|
||||
ID string `json:"id"`
|
||||
PublishID string `json:"publishID"` //视频上传ID
|
||||
CheckSum string `json:"checkSum"`
|
||||
Title string `json:"title"`
|
||||
Actors []string `json:"actors"`
|
||||
PlayTime uint `json:"playTime"`
|
||||
Tags []string `json:"tags"`
|
||||
Size int `json:"size"`
|
||||
Filename string `json:"filename"`
|
||||
Desc string `json:"desc"`
|
||||
FieldNameFs string `json:"fieldNameFs"`
|
||||
CoverImg []string `json:"coverImg"`
|
||||
Via string `json:"via"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Status string `json:"status"`
|
||||
Ratio float64 `json:"ratio"`
|
||||
}
|
||||
|
||||
// AwsPullSeriesResp 从AWS拉取套图响应体
|
||||
type AwsPullSeriesResp struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"` //套图类型 SP 泡芙短视频套图 AV 泡芙长视频套图
|
||||
Title string `json:"title"` // 标题
|
||||
Tags []string `json:"tags"` // 标签
|
||||
Desc string `json:"desc"` // 详细描述
|
||||
CoverImg string `json:"coverImg"` // 封面
|
||||
SeriesCover []string `json:"seriesCover"` // 套图
|
||||
Number int64 `json:"number"` // 图片数量
|
||||
Status string `json:"status"` // 当前状态
|
||||
MimeType string `json:"mimeType"` // mime文件类型
|
||||
Via string `json:"via"` // 来源
|
||||
NewUpdateAt string `json:"newUpdateAt"` // q1需要要加
|
||||
}
|
||||
|
||||
// 从server-file 返回的发布者信息
|
||||
type Publiser struct {
|
||||
UID string `json:"uid" bson:"uid" binding:"required"` //用户id
|
||||
UserName string `json:"name" bson:"name"` //昵称
|
||||
Portrait string `json:"portrait" bson:"portrait"` //头像
|
||||
Summary string `json:"summary" bson:"summary"` //简介
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
}
|
||||
|
||||
// AwsResport 上传
|
||||
type PublishResport struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data Publiser `json:"data"`
|
||||
}
|
||||
|
||||
type Resport struct {
|
||||
Code stderr.Code `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data []AwsPullResp `json:"data"`
|
||||
}
|
||||
|
||||
type PuFinfoResp struct {
|
||||
Code stderr.Code `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data AwsPullResp `json:"data"`
|
||||
}
|
||||
|
||||
// 套图响应结果
|
||||
type SeriesResport struct {
|
||||
Code stderr.Code `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data []AwsPullSeriesResp `json:"data"`
|
||||
}
|
||||
|
||||
// OperateResult 更新或者删除的操作返回结果
|
||||
type OperateResult struct {
|
||||
Count int64 `json:"count"`
|
||||
MergingCnt int64 `json:"mergingCnt"` //合并中的视频数
|
||||
ConvertingCnt int64 `json:"convertingCnt"` //转码中的视频数
|
||||
MerErrCnt int64 `json:"merErrCnt"` //合并失败视频
|
||||
ConvertErrCnt int64 `json:"convertErrCnt"` //转码失败视频数
|
||||
}
|
||||
|
||||
// WebSubmitReq 视频发布请求
|
||||
type WenUploadRes struct {
|
||||
PlayTime uint `json:"playTime" binding:"required"`
|
||||
Size int `json:"size"`
|
||||
Resolution string `json:"resolution"`
|
||||
MimeType string `json:"mimeType"`
|
||||
SourceID string `json:"sourceID" binding:"required"`
|
||||
SourceURL string `json:"sourceURL" binding:"required"`
|
||||
MD5 string `json:"md5"`
|
||||
Filename string `json:"filename"`
|
||||
}
|
||||
|
||||
// AwsResport 上传
|
||||
type AwsResport struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data AwsUploadResp `json:"data"`
|
||||
}
|
||||
|
||||
// AwsUploadResp AwsUploadResp
|
||||
type AwsUploadResp struct {
|
||||
VID string `json:"id"`
|
||||
VideoURI string `json:"videoUri"`
|
||||
}
|
||||
|
||||
type FsSendSingleResp struct {
|
||||
Code stderr.Code `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data FsSendSingleData `json:"data"`
|
||||
}
|
||||
|
||||
// FsSendSingleData FsSendSingleData
|
||||
type FsSendSingleData struct {
|
||||
Domain string `json:"domain"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
|
||||
// FsSendSingleBatchResp FsSendSingleBatchResp
|
||||
type FsSendBatchResp struct {
|
||||
Code stderr.Code `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data FsSendBatchData `json:"data"`
|
||||
}
|
||||
|
||||
// FsSendSingleData FsSendSingleData
|
||||
type FsSendBatchData struct {
|
||||
Batch []*FsSendSingleData `json:"batch"`
|
||||
}
|
||||
|
||||
func (this *FsSendBatchData) GetFileNames() []string {
|
||||
names := []string{}
|
||||
for _, v := range this.Batch {
|
||||
names = append(names, v.FileName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (this *FsSendBatchData) GetDomains() []string {
|
||||
domains := []string{}
|
||||
for _, v := range this.Batch {
|
||||
domains = append(domains, v.Domain)
|
||||
}
|
||||
return domains
|
||||
}
|
||||
|
||||
func (this *FsSendBatchData) Count() int {
|
||||
return len(this.Batch)
|
||||
}
|
||||
|
||||
// UpserVideoInfo 用于upsert video的 结构体
|
||||
type UpserVideoInfo struct {
|
||||
Title string `json:"title" bson:"title,omitempty"` //视频标题
|
||||
Tags []primitive.ObjectID `json:"tags" bson:"tags,omitempty"` //视频标签
|
||||
SourceID string `json:"sourceID" bson:"sourceID,omitempty"` //视频在仓库中的资源ID
|
||||
SourceURL string `json:"sourceURL" bson:"sourceURL,omitempty"` //视频资源地址Path
|
||||
MimeType string `json:"mimeType" bson:"mimeType,omitempty"` //视频格式类型
|
||||
Filename string `json:"fileName" bson:"fileName,omitempty"` //文件名称
|
||||
PlayTime uint `json:"playTime" bson:"playTime,omitempty"` //影片长度
|
||||
Cover string `json:"cover" bson:"cover,omitempty"` //封面大图
|
||||
CoverThumb string `json:"coverThumb" bson:"coverThumb,omitempty"` //封⾯小图
|
||||
SeriesCover []string `json:"seriesCover" bson:"seriesCover,omitempty"` //封面套图
|
||||
Via string `json:"via" bson:"via,omitempty"` //来源 自拍,上传
|
||||
Size int `json:"size" bson:"size,omitempty"` //文件大小 byte
|
||||
Resolution string `json:"resolution" bson:"resolution,omitempty"` //分辨率
|
||||
Ratio float64 `json:"ratio" bson:"ratio,omitempty"` //宽高比
|
||||
MD5 string `json:"md5" bson:"md5,omitempty"` //文件摘要
|
||||
Actor string `json:"actor" bson:"actor,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt,omitempty"` //刷新时间
|
||||
}
|
||||
|
||||
// TitleTime 视频标题时长数据
|
||||
type TitleTime struct {
|
||||
Title string `json:"title"`
|
||||
PlayTime uint `json:"playTime"`
|
||||
}
|
||||
|
||||
// WebVideoUpdateDoc 用于审核视频 同步信息
|
||||
type WebVideoUpdateDoc struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Cover *string `json:"cover,omitempty" bson:"cover,omitempty"`
|
||||
CoverThumb *string `json:"coverThumb,omitempty" bson:"coverThumb,omitempty"`
|
||||
SourceID *string `json:"sourceID,omitempty" bson:"sourceID,omitempty"`
|
||||
PlayTime *uint `json:"playTime,omitempty" bson:"playTime,omitempty"`
|
||||
FreeTime *int `json:"freeTime,omitempty" bson:"freeTime,omitempty"`
|
||||
SeriesCover *[]string `json:"seriesCover,omitempty" bson:"seriesCover,omitempty"` // 封面套图
|
||||
VideoCover *[]string `json:"videoCover,omitempty" bson:"videoCover,omitempty"` // 视频截图
|
||||
Resolution *string `json:"resolution,omitempty" bson:"resolution,omitempty"`
|
||||
Width *int `json:"width,omitempty" bson:"width,omitempty"` //视频宽度
|
||||
Height *int `json:"height,omitempty" bson:"height,omitempty"` //视频高度
|
||||
MD5 *string `json:"md5,omitempty" bson:"md5,omitempty" ` //文件摘要
|
||||
Actor *string `json:"actor,omitempty" bson:"actor,omitempty"`
|
||||
Size *int `json:"size,omitempty" bson:"size,omitempty"`
|
||||
Filename *string `json:"fileName,omitempty" bson:"fileName,omitempty"`
|
||||
Via *string `json:"via,omitempty" bson:"via,omitempty"`
|
||||
Ratio *float64 `json:"ratio,omitempty" bson:"ratio,omitempty"`
|
||||
Quality *string `json:"quality,omitempty" bson:"quality,omitempty"` //视频质量 720P以上高质量-high 480-720P 中等质量-middle 其他为low
|
||||
Direction *string `json:"direction,omitempty" bson:"direction,omitempty"` //视频版式 vertical-竖屏 horizontal-横屏 square-方屏
|
||||
}
|
||||
|
||||
// SyncServerFileEdit 用户同步从server-file获取的基本信息 强制更新
|
||||
type WebSyncServerFileEdit struct {
|
||||
PlayTime uint `json:"playTime" bson:"playTime"` //播放时长
|
||||
Size int `json:"size" bson:"size"` //文件大小 byte
|
||||
Resolution string `json:"resolution" bson:"resolution"` //分辨率
|
||||
Ratio float64 `json:"ratio" bson:"ratio"` //宽高比
|
||||
MD5 string `json:"md5" bson:"md5"` //文件摘要
|
||||
}
|
||||
|
||||
type BatchUpdateRecoRequest struct {
|
||||
IDs []primitive.ObjectID `json:"ids"` // 帖子ID列表
|
||||
Reco bool `json:"reco"` // 是否推荐
|
||||
}
|
||||
|
||||
type WebElasticSearchRequest struct {
|
||||
Keyword string `json:"keyword"` // 关键词
|
||||
commod.Page
|
||||
}
|
||||
|
||||
type WebElasticSearchResponse struct {
|
||||
List []ESVideo `json:"list"` // list
|
||||
HasNext bool `json:"hasNext"` // hasNext
|
||||
Total int `json:"total"` // total
|
||||
}
|
||||
|
||||
// UploadMediaVideo 上传到媒资库的基础信息请求体
|
||||
type UploadMediaVideo struct {
|
||||
FsResourceId string `json:"fs_resource_id"` // 文件资源id
|
||||
HashId string `json:"hash_id"` // 视频唯一id
|
||||
Title string `json:"title"` // 视频标题
|
||||
CoverImage string `json:"cover_image"` // 封面图
|
||||
M3u8Src string `json:"m3u8_src"` // m3u8地址
|
||||
FileSize int `json:"file_size"` // 视频大小
|
||||
Length int `json:"length"` // 视频时长 秒
|
||||
Width int `json:"width"` // 宽度
|
||||
Height int `json:"height"` // 高度
|
||||
TagsText string `json:"tags_text"` // 视频标签,多个以逗号分割
|
||||
}
|
||||
|
||||
// BatchUpdateVidNewsTypeReq 批量更帖子类型参数
|
||||
type BatchUpdateVidNewsTypeReq struct {
|
||||
Ids []primitive.ObjectID `form:"ids" json:"ids" bson:"ids"` // 帖子Id
|
||||
NewsType string `form:"newsType" json:"newsType" bson:"newsType,omitempty"` // 帖子类型
|
||||
}
|
||||
|
||||
// EditVideTagsReq 批量更视频参数
|
||||
type EditVideTagsReq struct {
|
||||
Ids []string `form:"ids" json:"ids" bson:"ids"` // 视频ID
|
||||
Tags []string `form:"tags" json:"tags" bson:"tags,omitempty"` // 标签
|
||||
Overwrite bool `form:"overwrite" json:"overwrite"` // 是否覆盖
|
||||
}
|
||||
Reference in New Issue
Block a user