563 lines
18 KiB
Go
563 lines
18 KiB
Go
package searcher
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"91porn-server/app/service/searchaccessser"
|
|
"91porn-server/app/service/vidhelpser"
|
|
"91porn-server/common"
|
|
"91porn-server/common/log"
|
|
"91porn-server/models/l/payvidlgmod"
|
|
"91porn-server/models/v/cmtmod"
|
|
"91porn-server/models/v/freeVidmod"
|
|
"91porn-server/models/v/moduleconfmod"
|
|
"91porn-server/models/v/productmod"
|
|
"91porn-server/models/v/tagmod"
|
|
"91porn-server/models/v/usermod"
|
|
"91porn-server/models/v/videodiscountmod"
|
|
"91porn-server/models/v/vidmod"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
// A A
|
|
type A = bson.A
|
|
|
|
// M M
|
|
type M = bson.M
|
|
|
|
// D D
|
|
type D = bson.D
|
|
|
|
// E E
|
|
type E = bson.E
|
|
|
|
// ObjectID ObjectID
|
|
type ObjectID = primitive.ObjectID
|
|
|
|
// VideoInfo VideoInfo
|
|
type VideoInfo struct {
|
|
ID *ObjectID `json:"id" bson:"_id"`
|
|
NewsType *string `json:"newsType"` //帖子类型, VID,视频帖子,COVER
|
|
Title *string `json:"title,omitempty"` //视频标题
|
|
Tags []TagInfo `json:"tags"` //视频标签
|
|
SourceID *string `json:"sourceID,omitempty"` //视频在仓库中的资源ID
|
|
SourceURL *string `json:"sourceURL,omitempty"` //视频资源地址Path
|
|
H265Url *string `json:"h265Url"` // H.265 视频资源地址
|
|
PlayTime *uint `json:"playTime,omitempty"` //影片长度
|
|
Cover *string `json:"cover,omitempty"` //封面大图
|
|
CoverThumb *string `json:"coverThumb,omitempty"` //封⾯小图
|
|
SeriesCover []string `json:"seriesCover"` //封面套图
|
|
Via *string `json:"via,omitempty"` //来源 自拍,上传
|
|
PlayCount *int `json:"playCount,omitempty"` //总的播放量量(假数据)
|
|
LikeCount *int `json:"likeCount,omitempty"` //点赞数(假数据)
|
|
PayCount *int `json:"payCount,omitempty"` //购买数
|
|
CommentCount *int `json:"commentCount,omitempty"` //评论数(假数据)
|
|
ShareCount *int `json:"shareCount,omitempty"` //分享数(假数据)
|
|
Coins *int64 `json:"coins,omitempty"` //折扣后价格
|
|
Size *int `json:"size,omitempty"` //文件大小 byte
|
|
Status *int `json:"status,omitempty"` //状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
|
Resolution *string `json:"resolution,omitempty"` //分辨率
|
|
Ratio *float64 `json:"ratio,omitempty"` //宽高比
|
|
MimeType *string `json:"mimeType,omitempty"` //视频格式类型
|
|
Actor *string `json:"actor,omitempty"`
|
|
FreeTime *int `json:"freeTime,omitempty"` //免费观影时长
|
|
CreatedAt *time.Time `json:"createdAt,omitempty"` //创建时间
|
|
ReviewedAt *time.Time `json:"reviewAt,omitempty"` // 审核时间
|
|
UInfo vidmod.Publisher `json:"publisher,omitempty" bson:"publisher"` //用户信息
|
|
FreeArea *bool `json:"freeArea,omitempty"` //免费专区
|
|
PublisherID *uint64 `json:"-"` //发布者ID(user)
|
|
LocationID *primitive.ObjectID `json:"-"` //位置ID
|
|
IsTopping *bool `json:"isTopping"`
|
|
IsRecommend *bool `json:"isRecommend"` //力荐
|
|
IsChoosen *bool `json:"isChoosen"` //置精
|
|
OriginCoins *int64 `json:"originCoins"` // 视频原价
|
|
SearchAccessToken *string `json:"searchAccessToken,omitempty"`
|
|
ShowFreeTrialBadge bool `json:"showFreeTrialBadge" bson:"-"`
|
|
FreeTrialRemaining uint64 `json:"freeTrialRemaining" bson:"-"`
|
|
CanUseFreeTrial bool `json:"canUseFreeTrial" bson:"-"`
|
|
}
|
|
|
|
// VideoMap VideoMap
|
|
func videoMap(vidList []ObjectID, uid uint64, allowSearchOnly bool) (map[ObjectID]*VideoInfo, error) {
|
|
if len(vidList) == 0 {
|
|
return make(map[ObjectID]*VideoInfo), nil
|
|
}
|
|
videoMap, vInfo, err := vidmod.SearchVideoMap(vidList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockedModules := make(map[string]struct{})
|
|
if allowSearchOnly {
|
|
excludedModules, excludeErr := moduleconfmod.ExcludedSearchModuleIDs()
|
|
if excludeErr != nil {
|
|
return nil, excludeErr
|
|
}
|
|
for _, moduleID := range excludedModules {
|
|
blockedModules[moduleID] = struct{}{}
|
|
}
|
|
} else {
|
|
moduleIDs := make([]string, 0, len(vInfo))
|
|
for _, video := range vInfo {
|
|
if video.MID != "" {
|
|
moduleIDs = append(moduleIDs, video.MID)
|
|
}
|
|
}
|
|
blockedModules, err = moduleconfmod.BlockedOutsideSearchModuleIDs(moduleIDs, time.Now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var (
|
|
vidInfoMap = make(map[ObjectID]*VideoInfo, len(videoMap))
|
|
tidUniqueMap = make(map[ObjectID]byte)
|
|
mFree = map[uint64]bool{}
|
|
self *usermod.User
|
|
videoDiscLog videodiscountmod.VideoDiscountLog
|
|
largestDiscount int
|
|
wg sync.WaitGroup
|
|
mUser map[uint64]*vidmod.Publisher
|
|
)
|
|
uids := make([]uint64, len(vInfo))
|
|
for i, v := range vInfo {
|
|
uids[i] = v.PublisherID
|
|
}
|
|
wg.Add(5)
|
|
common.Go(func() {
|
|
defer wg.Done()
|
|
self, err = usermod.FindUserByUID(uid)
|
|
if err != nil {
|
|
log.Error("VideoMap: usermod.FindUserByUID", log.Any("uid", uid), log.E(err))
|
|
return
|
|
}
|
|
})
|
|
common.Go(func() {
|
|
defer wg.Done()
|
|
mUser = vidhelpser.GetUserInfo2Map(uid, uids)
|
|
})
|
|
common.Go(func() {
|
|
defer wg.Done()
|
|
largestDiscount, err = productmod.GetLargestDiscountVIPCard()
|
|
if err != nil {
|
|
log.Error("VideoMap GetLargestDiscountVIPCard", log.Any("uid", uid), log.E(err))
|
|
}
|
|
})
|
|
common.Go(func() {
|
|
defer wg.Done()
|
|
mFree = freeVidmod.GetFreeVideoMapByUID(uid)
|
|
})
|
|
common.Go(func() {
|
|
defer wg.Done()
|
|
videoDiscLog, err = videodiscountmod.GetByUID(uid)
|
|
if err != nil {
|
|
log.Error("VideoMap videodiscountmod.GetByUID", log.Any("uid", uid), log.E(err))
|
|
}
|
|
})
|
|
wg.Wait()
|
|
freeTrialContext := vidhelpser.FreeTrialBadgeContextForUser(uid, self)
|
|
for vid, vidInfo := range videoMap {
|
|
videoPtr := vidInfo
|
|
if _, blocked := blockedModules[videoPtr.MID]; blocked {
|
|
continue
|
|
}
|
|
status := videoPtr.Status
|
|
if status == 3 {
|
|
status = 1
|
|
}
|
|
var uInfo vidmod.Publisher
|
|
if mUser[vidInfo.PublisherID] != nil {
|
|
uInfo = *mUser[vidInfo.PublisherID]
|
|
} else {
|
|
//处理作者被删除的情况
|
|
if len(mUser) > 0 {
|
|
for i := range mUser {
|
|
uInfo = *mUser[i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
newsType := vidmod.SP
|
|
if len(videoPtr.NewsType) != 0 {
|
|
newsType = videoPtr.NewsType
|
|
}
|
|
discountCoins := vidhelpser.CalcVideoCoins(videoPtr.Coins, videoPtr.PublisherID, self, mFree, videoDiscLog,
|
|
largestDiscount, nil)
|
|
h265URL := vidhelpser.H265URLForApp(videoPtr)
|
|
vInfo := VideoInfo{
|
|
ID: &videoPtr.ID,
|
|
NewsType: &newsType,
|
|
Title: &videoPtr.Title, //视频标题
|
|
Tags: []TagInfo{}, //视频标签
|
|
SourceID: &videoPtr.SourceID, //视频在仓库中的资源ID
|
|
SourceURL: &videoPtr.SourceURL, //视频资源地址Path
|
|
H265Url: &h265URL, // H.265 视频资源地址
|
|
PlayTime: &videoPtr.PlayTime, //影片长度
|
|
Cover: &videoPtr.Cover, //封面大图
|
|
CoverThumb: &videoPtr.CoverThumb, //封⾯小图
|
|
SeriesCover: videoPtr.SeriesCover,
|
|
Via: &videoPtr.Via, //来源 自拍,上传
|
|
PlayCount: &videoPtr.FakePlayCount, //总的播放量量(假数据)
|
|
LikeCount: &videoPtr.FakeLikeCount, //点赞数(假数据)
|
|
CommentCount: &videoPtr.FakeCommentCount, //评论数(假数据)
|
|
ShareCount: &videoPtr.FakeShareCount, //分享数(假数据)
|
|
Size: &videoPtr.Size, //文件大小 byte
|
|
Ratio: &videoPtr.Ratio, //分辨率
|
|
Status: &status, //状态,0 未审核 1通过 2审核失败 3视为免费 默认为0
|
|
Resolution: &videoPtr.Resolution, //分辨率
|
|
MimeType: &videoPtr.MimeType, //视频格式类型
|
|
Actor: &videoPtr.Actor,
|
|
FreeTime: &videoPtr.FreeTime, //免费观影时长
|
|
PublisherID: &videoPtr.PublisherID, //发布者ID(user)
|
|
FreeArea: &videoPtr.FreeArea, //免费专区
|
|
LocationID: &videoPtr.Location, //位置ID
|
|
CreatedAt: &videoPtr.CreatedAt, //创建时间
|
|
ReviewedAt: &videoPtr.ReviewAt,
|
|
UInfo: uInfo,
|
|
IsTopping: &videoPtr.IsTopping,
|
|
IsChoosen: &videoPtr.IsChoosen,
|
|
IsRecommend: &videoPtr.IsRecommend,
|
|
OriginCoins: &videoPtr.Coins, // 定价
|
|
Coins: &discountCoins, // 折扣价
|
|
}
|
|
vInfo.ShowFreeTrialBadge, vInfo.FreeTrialRemaining, vInfo.CanUseFreeTrial = freeTrialContext.Fields(
|
|
newsType,
|
|
videoPtr.Coins,
|
|
videoPtr.FreeArea,
|
|
videoPtr.PublisherID,
|
|
)
|
|
if allowSearchOnly {
|
|
token, tokenErr := searchaccessser.Issue(uid, videoPtr.ID.Hex(), time.Now())
|
|
if tokenErr != nil {
|
|
return nil, tokenErr
|
|
}
|
|
vInfo.SearchAccessToken = &token
|
|
}
|
|
if len(videoPtr.Tags) != 0 {
|
|
for _, tid := range videoPtr.Tags {
|
|
tidUniqueMap[tid] = 0
|
|
}
|
|
}
|
|
//fill video Map
|
|
vidInfoMap[vid] = &vInfo
|
|
}
|
|
tidList := make([]ObjectID, len(tidUniqueMap))
|
|
i := 0
|
|
for k := range tidUniqueMap {
|
|
tidList[i] = k
|
|
i++
|
|
}
|
|
//获取所有tagInfo List
|
|
tInfoList, err := TagInfoListByTIDList(&uid, tidList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tInfoMap := ToTagMap(tInfoList)
|
|
//vidInfoMap填充tagInfo
|
|
for vid, vidInfo := range vidInfoMap {
|
|
video := videoMap[vid]
|
|
tidList := video.Tags
|
|
tInfoList := make([]TagInfo, 0, len(tidList))
|
|
for _, t := range video.Tags {
|
|
tInfo := tInfoMap[t]
|
|
tInfoList = append(tInfoList, tInfo)
|
|
}
|
|
vidInfo.Tags = tInfoList
|
|
}
|
|
return vidInfoMap, nil
|
|
}
|
|
|
|
// VidStatus VidStatus
|
|
type VidStatus struct {
|
|
HasPaid *bool `json:"hasPaid,omitempty"` //已支付
|
|
}
|
|
|
|
// VidAndStatus VidAndStatus
|
|
type VidAndStatus struct {
|
|
VID ObjectID
|
|
*VidStatus
|
|
}
|
|
|
|
// VidStatusMap VidStatusMap
|
|
func VidStatusMap(vidList []ObjectID, uid uint64) map[ObjectID]VidStatus {
|
|
var (
|
|
payUniqList = make([]string, 0, len(vidList))
|
|
payUniqMap = make(map[ObjectID]string, len(vidList))
|
|
)
|
|
for _, v := range vidList {
|
|
payUniq := payvidlgmod.Unique(uid, v)
|
|
payUniqList = append(payUniqList, payUniq)
|
|
payUniqMap[v] = payUniq
|
|
}
|
|
payStatueMap, err := payvidlgmod.PayStatueMap(payUniqList)
|
|
if err != nil {
|
|
return map[ObjectID]VidStatus{}
|
|
}
|
|
vidStatusMap := make(map[ObjectID]VidStatus, len(vidList))
|
|
for _, v := range vidList {
|
|
payHash := payUniqMap[v]
|
|
payStatue := payStatueMap[payHash]
|
|
vidStatusMap[v] = VidStatus{
|
|
HasPaid: &payStatue,
|
|
}
|
|
}
|
|
return vidStatusMap
|
|
}
|
|
|
|
// Publisher Publisher
|
|
type Publisher = UserRes
|
|
|
|
// VideoRes VideoRes
|
|
type VideoRes struct {
|
|
VideoInfo `bson:",inline"` //视屏
|
|
LocInfo `json:"location" bson:"location"` //位置
|
|
VidStatus `json:"vidStatus" bson:"vidStatus"` //用户与视频状态信息
|
|
Publisher `json:"publisher" bson:"publisher"` //发布者
|
|
}
|
|
|
|
// GetVideoRes GetVideoRes
|
|
func GetVideoRes(vid ObjectID, vInfoMap map[ObjectID]*VideoInfo, publMap map[uint64]*UserRes, locMap map[ObjectID]*LocInfo, statusMap map[ObjectID]VidStatus) VideoRes {
|
|
res := VideoRes{}
|
|
video := vInfoMap[vid]
|
|
if video == nil {
|
|
return res
|
|
}
|
|
res.VideoInfo = *video
|
|
if video.PublisherID != nil {
|
|
publisher := publMap[*video.PublisherID]
|
|
if publisher != nil {
|
|
res.Publisher = *publisher
|
|
}
|
|
}
|
|
if video.LocationID != nil {
|
|
loc := locMap[*video.LocationID]
|
|
if loc != nil {
|
|
res.LocInfo = *loc
|
|
}
|
|
}
|
|
status := statusMap[vid]
|
|
res.VidStatus = status
|
|
return res
|
|
}
|
|
|
|
func getVideoParentCommentCountMap(vidList []ObjectID) map[ObjectID]int {
|
|
res := make(map[ObjectID]int, len(vidList))
|
|
if len(vidList) == 0 {
|
|
return res
|
|
}
|
|
countMap, err := cmtmod.CmtFindParentCountByObjIDs(cmtmod.CmtTypeVideo, vidList)
|
|
if err != nil {
|
|
log.Error("GetVideoResList CmtFindParentCountByObjIDs error", log.E(err), log.Any("vidList", vidList))
|
|
return res
|
|
}
|
|
for id, count := range countMap {
|
|
res[id] = int(count)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// GetVideoResList GetVideoResList
|
|
func GetVideoResList(uid uint64, vidList []ObjectID) ([]VideoRes, error) {
|
|
return getVideoResList(uid, vidList, false)
|
|
}
|
|
|
|
// GetVideoResListForSearch 保留当前仅能通过搜索入口访问的视频。
|
|
func GetVideoResListForSearch(uid uint64, vidList []ObjectID) ([]VideoRes, error) {
|
|
return getVideoResList(uid, vidList, true)
|
|
}
|
|
|
|
func getVideoResList(uid uint64, vidList []ObjectID, allowSearchOnly bool) ([]VideoRes, error) {
|
|
videoCount := len(vidList)
|
|
//vid->video Map
|
|
videoMap, err := videoMap(vidList, uid, allowSearchOnly)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
commentCountMap := getVideoParentCommentCountMap(vidList)
|
|
//Publisher
|
|
//发布者ID List
|
|
pidList := make([]uint64, 0, videoCount)
|
|
for _, video := range videoMap {
|
|
if video.PublisherID == nil {
|
|
continue
|
|
}
|
|
pidList = append(pidList, *video.PublisherID)
|
|
}
|
|
//LocationID List
|
|
lidList := make([]ObjectID, 0, videoCount)
|
|
for _, v := range videoMap {
|
|
if v.LocationID == nil {
|
|
continue
|
|
}
|
|
lidList = append(lidList, *v.LocationID)
|
|
}
|
|
var (
|
|
wg = sync.WaitGroup{}
|
|
publMap = make(map[uint64]*UserRes)
|
|
locMap = make(map[ObjectID]*LocInfo)
|
|
statusMap = make(map[ObjectID]VidStatus)
|
|
)
|
|
//一共3个map,每个携程填充一个Map
|
|
wg.Add(3)
|
|
common.Go(func() {
|
|
//PublisherID->User map
|
|
defer wg.Done()
|
|
m, err := UserResMap(pidList, uid)
|
|
if err != nil {
|
|
log.Error("GetVideoResList PublisherID->User error", log.E(err))
|
|
return
|
|
}
|
|
publMap = m
|
|
})
|
|
//Location map
|
|
common.Go(func() {
|
|
//LocationID->Location map
|
|
defer wg.Done()
|
|
m, err := LocMap(lidList)
|
|
if err != nil {
|
|
log.Error("GetVideoResList Location map error", log.E(err))
|
|
return
|
|
}
|
|
locMap = m
|
|
})
|
|
//Video Status map
|
|
common.Go(func() {
|
|
//Vid->视屏状态 map
|
|
defer wg.Done()
|
|
statusMap = VidStatusMap(vidList, uid)
|
|
})
|
|
wg.Wait()
|
|
//组装 []VidInfo
|
|
videoResList := make([]VideoRes, 0, videoCount)
|
|
for _, vid := range vidList {
|
|
info := GetVideoRes(vid, videoMap, publMap, locMap, statusMap)
|
|
if info.Publisher.UID == nil {
|
|
log.Warn("Publisher UID is invalid")
|
|
continue
|
|
}
|
|
if info.VideoInfo.ID != nil {
|
|
commentCount := commentCountMap[*info.VideoInfo.ID]
|
|
info.CommentCount = &commentCount
|
|
}
|
|
info.Publisher.HasFollowed = &videoMap[vid].UInfo.HasFollowed
|
|
isFree := true
|
|
info.FreeArea = &isFree
|
|
videoResList = append(videoResList, info)
|
|
}
|
|
return videoResList, nil
|
|
}
|
|
|
|
// RichVidRes RichVidRes
|
|
type RichVidRes struct {
|
|
VideoInfo `bson:",inline"` //视屏
|
|
LocInfo `json:"location" bson:"location"` //位置
|
|
VidStatus `json:"vidStatus" bson:"vidStatus"` //用户与视频状态信息
|
|
Publisher `json:"publisher,omitempty" bson:"publisher"` //发布者
|
|
}
|
|
|
|
// GetRichVideoRes GetRichVideoRes
|
|
func GetRichVideoRes(vid ObjectID, vInfoMap map[ObjectID]*VideoInfo, publMap map[uint64]*UserRes, locMap map[ObjectID]*LocInfo, statusMap map[ObjectID]VidStatus) RichVidRes {
|
|
res := GetVideoRes(vid, vInfoMap, publMap, locMap, statusMap)
|
|
if res.VideoInfo.ID == nil {
|
|
return RichVidRes{}
|
|
}
|
|
richRes := RichVidRes(res)
|
|
return richRes
|
|
}
|
|
|
|
// GetRichVideoResList GetRichVideoResList
|
|
func GetRichVideoResList(uid uint64, vidList []ObjectID) ([]RichVidRes, error) {
|
|
return getRichVideoResList(uid, vidList, false)
|
|
}
|
|
|
|
// GetRichVideoResListForSearch 保留当前仅能通过搜索入口访问的视频。
|
|
func GetRichVideoResListForSearch(uid uint64, vidList []ObjectID) ([]RichVidRes, error) {
|
|
return getRichVideoResList(uid, vidList, true)
|
|
}
|
|
|
|
func getRichVideoResList(uid uint64, vidList []ObjectID, allowSearchOnly bool) ([]RichVidRes, error) {
|
|
videoCount := len(vidList)
|
|
//vid->video Map
|
|
videoMap, err := videoMap(vidList, uid, allowSearchOnly)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//Publisher
|
|
//发布者ID List
|
|
pidList := make([]uint64, 0, videoCount)
|
|
for _, video := range videoMap {
|
|
if video.PublisherID == nil {
|
|
continue
|
|
}
|
|
pidList = append(pidList, *video.PublisherID)
|
|
}
|
|
//LocationID List
|
|
lidList := make([]ObjectID, 0, videoCount)
|
|
for _, v := range videoMap {
|
|
if v.LocationID == nil {
|
|
continue
|
|
}
|
|
lidList = append(lidList, *v.LocationID)
|
|
}
|
|
var (
|
|
wg = sync.WaitGroup{}
|
|
publMap = make(map[uint64]*UserRes)
|
|
locMap = make(map[ObjectID]*LocInfo)
|
|
statusMap = make(map[ObjectID]VidStatus)
|
|
)
|
|
//一共4个map,每个携程填充一个Map
|
|
wg.Add(3)
|
|
common.Go(func() {
|
|
//PublisherID->User map
|
|
defer wg.Done()
|
|
m, err := UserResMap(pidList, uid)
|
|
if err != nil {
|
|
log.Error("GetVideoResList PublisherID->User error", log.E(err))
|
|
return
|
|
}
|
|
publMap = m
|
|
})
|
|
//Location map
|
|
common.Go(func() {
|
|
//LocationID->Location map
|
|
defer wg.Done()
|
|
m, err := LocMap(lidList)
|
|
if err != nil {
|
|
log.Error("GetVideoResList Location map error", log.E(err))
|
|
return
|
|
}
|
|
locMap = m
|
|
})
|
|
//Video Status map
|
|
common.Go(func() {
|
|
//Vid->视屏状态 map
|
|
defer wg.Done()
|
|
statusMap = VidStatusMap(vidList, uid)
|
|
})
|
|
wg.Wait()
|
|
//组装 []RichVidRes
|
|
videoResList := make([]RichVidRes, 0, videoCount)
|
|
for _, vid := range vidList {
|
|
info := GetRichVideoRes(vid, videoMap, publMap, locMap, statusMap)
|
|
if info.Publisher.UID == nil {
|
|
continue
|
|
}
|
|
videoResList = append(videoResList, info)
|
|
}
|
|
return videoResList, nil
|
|
}
|
|
|
|
// VideoListByKeyword 通过关键字匹配title和tagName获取VID List
|
|
func VideoListByKeyword(keyword string, skip int64, limit int64) ([]vidmod.ObjectID, error) {
|
|
tags, err := tagmod.GetTagsByKeyword(keyword, 0, 3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tidList := make([]primitive.ObjectID, len(tags))
|
|
for i, tag := range tags {
|
|
tidList[i] = tag.ID
|
|
}
|
|
return vidmod.VIDListByKeywordOrTags(keyword, tidList, skip, limit)
|
|
}
|