Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
@@ -0,0 +1,76 @@
package audiobooksearcher
import (
"errors"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/models/v/audiobookmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// FictionSearcher FictionSearcher
type FictionSearcher struct {
uid *uint64
}
// NewFictionSearcher NewFictionSearcher
func NewFictionSearcher(uid *uint64) searcher.Searcher {
return &FictionSearcher{
uid: uid,
}
}
// Search Search
func (t *FictionSearcher) Search(keywrod searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
kw, ok := keywrod.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
if kw == "" { //关键字为空,热搜
audiobook, hasNext, err := audiobookmod.GetNew(opt)
if err != nil {
return nil, err
}
return &Result{dat: audiobook, hasNext: hasNext}, nil
}
if appg.Conf.Elastic.IsActive {
audiobook, _ := esSearch(kw, opt)
hasNext := false
if len(audiobook) > int(opt.Limit()) {
hasNext = true
audiobook = audiobook[:opt.Limit()]
}
ab, err := fillAudioBook(*t.uid, audiobook)
if err != nil {
return nil, err
}
return &Result{dat: ab, hasNext: hasNext}, nil
}
audiobook, hasNext, err := audiobookmod.GetNew(opt)
if err != nil {
return nil, err
}
return &Result{dat: audiobook, hasNext: hasNext}, nil
}
func fillAudioBook(uid uint64, data []audiobookmod.AudioBookBase) (res []audiobookmod.AudioBookAppRes, err error) {
ids := make([]primitive.ObjectID, len(data))
for i := range data {
ids[i] = data[i].ID
}
return audiobookmod.GetByIds(ids)
}
func esSearch(keyword string, opt searcher.Opter) (data []audiobookmod.AudioBookBase, err error) {
audiobook, err := audiobookmod.Search(keyword, opt.Skip(), opt.Limit()+1)
if err != nil {
return
}
data = make([]audiobookmod.AudioBookBase, len(audiobook))
for i := range audiobook {
data[i] = audiobook[i].Source
}
return
}
@@ -0,0 +1,31 @@
package audiobooksearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/audiobookmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
type Result struct {
dat []audiobookmod.AudioBookAppRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.LouFengSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,63 @@
package fictionsearcher
import (
"errors"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/models/v/fictionmod"
)
// FictionSearcher FictionSearcher
type FictionSearcher struct {
uid *uint64
}
// NewFictionSearcher NewFictionSearcher
func NewFictionSearcher(uid *uint64) searcher.Searcher {
return &FictionSearcher{
uid: uid,
}
}
// Search Search
func (t *FictionSearcher) Search(keywrod searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
var fiction []fictionmod.FictionBase
kw, ok := keywrod.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
hasNext := false
if kw == "" { //关键字为空,热搜
fiction, hasNext, err = fictionmod.GetNew(opt)
if err != nil {
return nil, err
}
return &Result{fiction, hasNext}, nil
}
if appg.Conf.Elastic.IsActive {
fiction, err = esSearch(kw, opt)
if len(fiction) > int(opt.Limit()) {
hasNext = true
fiction = fiction[:opt.Limit()]
}
} else {
fiction, hasNext, err = fictionmod.GetNew(opt)
}
if err != nil {
return nil, err
}
return &Result{fiction, hasNext}, nil
}
func esSearch(keyword string, opt searcher.Opter) (data []fictionmod.FictionBase, err error) {
fiction, err := fictionmod.Search(keyword, opt.Skip(), opt.Limit()+1)
if err != nil {
return
}
data = make([]fictionmod.FictionBase, len(fiction))
for i := range fiction {
data[i] = fiction[i].Source
}
return
}
@@ -0,0 +1,31 @@
package fictionsearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/fictionmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
type Result struct {
dat []fictionmod.FictionBase
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.LouFengSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
+91
View File
@@ -0,0 +1,91 @@
package searcher
import (
"time"
"91porn-server/common/timeutil/timerange"
"91porn-server/models/v/tonerecomod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// KeyWorder KeyWorder
type KeyWorder interface {
Get() interface{}
}
// Keyword Keyword
type Keyword struct {
Word string
}
func (s *Keyword) Get() interface{} {
return s.Word
}
// StringList StringList
type StringList struct {
Words []string
}
func (s *StringList) Get() interface{} {
return s.Words
}
// ObjectList ObjectList
type ObjIDList struct {
IDList []primitive.ObjectID
}
func (o *ObjIDList) Get() interface{} {
return o.IDList
}
// ObjectList ObjectList
type ObjID struct {
ID primitive.ObjectID
}
func (o *ObjID) Get() interface{} {
return o.ID
}
// ThemeList ThemeList
type ThemeList struct {
Themes []tonerecomod.ThemeType
}
func (s *ThemeList) Get() interface{} {
return s.Themes
}
// TimeRange TimeRange
type TimeRange struct {
Range timerange.TimeRange
}
func (t *TimeRange) Get() interface{} {
return t.Range
}
func NewTimeRange(start, end time.Time) KeyWorder {
timeRange := timerange.TimeRange{
Head: start,
Tail: end,
}
return &TimeRange{timeRange}
}
// SumDate SumDate
type SumDate struct {
Date time.Time
}
func (t *SumDate) Get() interface{} {
return t.Date
}
func NewSumDate(position time.Time) KeyWorder {
sumDate := timerange.LocDayRange(position).Head
return &SumDate{sumDate}
}
+51
View File
@@ -0,0 +1,51 @@
package searcher
import (
"91porn-server/models/v/locmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// LocInfo LocInfo
type LocInfo struct {
ID *primitive.ObjectID `json:"id" bson:"_id"`
Longitude *string `json:"longitude,omitempty"` //经度
Latitude *string `json:"latitude,omitempty"` //纬度
Country *string `json:"country,omitempty"` //国家
CountryCode *string `json:"countryCode,omitempty"` //国家编码
Province *string `json:"province,omitempty"` //省份
ProvinceCode *string `json:"provinceCode,omitempty"` //省份编码
City *string `json:"city,omitempty"` //城市
CityCode *string `json:"cityCode,omitempty"` //城市编码
Address *string `json:"address,omitempty"` //地址
Cover *string `json:"cover,omitempty"` //封面
Visit *int `json:"visit,omitempty"` //访问人数
}
// LocMap LocMap
func LocMap(lidList []ObjectID) (map[ObjectID]*LocInfo, error) {
locMap, err := locmod.GetLocationInfoByIDs(lidList)
if err != nil {
return nil, err
}
m := make(map[ObjectID]*LocInfo, len(locMap))
for k, v := range locMap {
loc := v
lInfo := LocInfo{
ID: &loc.ID,
Longitude: &loc.Longitude,
Latitude: &loc.Latitude,
Country: &loc.Country,
CountryCode: &loc.CountryCode,
Province: &loc.Province,
ProvinceCode: &loc.ProvinceCode,
City: &loc.City,
CityCode: &loc.CityCode,
Address: &loc.Address,
Cover: &loc.Cover,
Visit: &loc.Visit,
}
m[k] = &lInfo
}
return m, nil
}
+83
View File
@@ -0,0 +1,83 @@
package searcher
import (
"91porn-server/app/service/walletser"
"91porn-server/common/log"
"91porn-server/models/v/usermod"
)
// UserInfo UserInfo
type UserInfo struct {
UID *uint64 `json:"uid,omitempty"`
Name *string `json:"name,omitempty"`
Age *int `json:"age,omitempty"`
Gender *string `json:"gender,omitempty"` //性别
Portrait *string `json:"portrait,omitempty"` //头像
Region *string `json:"region,omitempty"` //地区
Summary *string `json:"summary,omitempty"` //简介
VipLevel *int `json:"vipLevel,omitempty"` //vip等级
RechargeLevel *int `json:"rechargeLevel" ` //头像显示的vip等级,累计充值决定
SuperUser *bool `json:"superUser"` //大v
ActiveValue *int `json:"activeValue"` //活跃度
OfficialCert *bool `json:"officialCert"` //是否官方认证
}
// UserRes UserRes
type UserRes struct {
UserInfo `bson:",inline"`
HasFollowed *bool `json:"hasFollowed,omitempty"` //已关注
FansCount *int64 `json:"fansCount,omitempty"` //粉丝数
}
// MapToUserResList MapToUserResList
func MapToUserResList(userList []*usermod.User, uid uint64) []UserRes {
uidList := make([]uint64, len(userList))
for i, u := range userList {
uidList[i] = u.UID
}
mLevel, err := walletser.GetUidsRchgLevel(uidList)
if err != nil {
log.Error("GetUidsRchgLevel error", log.E(err))
return []UserRes{}
}
userResList := make([]UserRes, len(userList))
for i, userp := range userList {
age := userp.Age()
level := mLevel[userp.UID].Level
userInfo := UserInfo{
Age: &age,
UID: &userp.UID,
Name: &userp.Name,
Gender: &userp.Gender,
Portrait: &userp.Portrait,
Region: &userp.Region,
Summary: &userp.Summary,
VipLevel: &userp.VipLevel,
SuperUser: &userp.SuperUser,
ActiveValue: &userp.ActiveValue,
OfficialCert: &userp.OfficialCert,
RechargeLevel: &level,
}
userResList[i] = UserRes{
UserInfo: userInfo,
}
}
return userResList
}
// UserResMap UserResMap
func UserResMap(uidList []uint64, uid uint64) (map[uint64]*UserRes, error) {
userList, err := usermod.FindUsersByUID(uidList)
if err != nil {
return nil, err
}
userResList := MapToUserResList(userList, uid)
m := make(map[uint64]*UserRes, len(userList))
for _, res := range userResList {
if res.UID != nil {
_res := res
m[*res.UID] = &_res
}
}
return m, nil
}
+57
View File
@@ -0,0 +1,57 @@
package searcher
// Opter Opter
type Opter interface {
SetSkip(v int64) Opter
Skip() int64
SetLimit(v int64) Opter
Limit() int64
SetSort(key string, v int64) Opter
Sort() D
}
type Option struct {
skip *int64
limit *int64
sort D
}
const MaxLimit = 50
const MinLimit = 1
func (o *Option) Skip() int64 {
if o.skip == nil {
return 0
}
return *o.skip
}
func (o *Option) SetSkip(v int64) Opter {
o.skip = &v
return o
}
func (o *Option) Limit() int64 {
if o.limit == nil {
return MinLimit
}
if *o.limit > MaxLimit {
return MaxLimit
}
return *o.limit
}
func (o *Option) SetLimit(v int64) Opter {
o.limit = &v
return o
}
func (o *Option) Sort() D {
return o.sort
}
func (o *Option) SetSort(key string, v int64) Opter {
o.sort = append(o.sort, E{Key: key, Value: v})
return o
}
+23
View File
@@ -0,0 +1,23 @@
package searcher
type ResultTypes = int
const (
TagSearchResult ResultTypes = 1 << iota //0 which is 00000001
ToneSearchResult
UserSearchResult
VidPCountSearchResult
VidRichSearchResult
VidSearchResult
VidTagSearchResult
VidToneSearchResult
VidHotKeywordSearchResult
LouFengSearchResult
)
type Resulter interface {
Types() ResultTypes
Data() interface{}
Count() int64
HasNext() bool
}
+6
View File
@@ -0,0 +1,6 @@
package searcher
// Searcher Searcher
type Searcher interface {
Search(KeyWorder, Opter) (Resulter, error)
}
+86
View File
@@ -0,0 +1,86 @@
package searcher
import (
"strings"
"91porn-server/models/v/tagmod"
)
// TagInfo TagInfo
type TagInfo struct {
ID *ObjectID `json:"id,omitempty"` //标签id
Name *string `json:"name,omitempty"` //标签名字
CoverImg *string `json:"coverImg,omitempty"` //封面图片
Description *string `json:"description,omitempty"` //文字说明
PlayCount *int64 `json:"playCount,omitempty"` //播放量
VidCount *int64 `json:"vidCount,omitempty"` //帖子数量
FollowCount *int64 `json:"followCount,omitempty"` //关注数量
}
// ToTagMap ToTagMap
func ToTagMap(tInfoList []TagInfo) map[ObjectID]TagInfo {
m := make(map[ObjectID]TagInfo, len(tInfoList))
for _, v := range tInfoList {
if v.ID == nil {
continue
}
m[*v.ID] = v
}
return m
}
// TagInfoListByTIDList TagInfoListByTIDList
func TagInfoListByTIDList(uid *uint64, tidList []ObjectID) ([]TagInfo, error) {
tMap, err := tagmod.TagMap(tidList)
if err != nil {
return nil, err
}
tagList := make([]tagmod.Tag, len(tMap))
i := 0
for _, tag := range tMap {
tagList[i] = tag
i++
}
tInfoList, err := TagInfoList(uid, tagList)
if err != nil {
return nil, err
}
return tInfoList, nil
}
// TagInfoList TagInfoList
//
//uid:nil
func TagInfoList(uid *uint64, tagList []tagmod.Tag) ([]TagInfo, error) {
tInfoList := make([]*TagInfo, 0, len(tagList))
for _, v := range tagList {
_v := v
tagName := strings.TrimSpace(_v.TagName)
if tagName == "" { //临时解决话题搜索返回空tag
continue
}
tInfo := &TagInfo{
ID: &_v.ID,
Name: &tagName,
CoverImg: &_v.CoverImg,
Description: &_v.Description,
PlayCount: &_v.TPlayCount, //夏沫确认 前端播放量计算方法
VidCount: &_v.VideoCount,
FollowCount: &_v.CollCount,
}
if uid == nil {
tInfoList = append(tInfoList, tInfo)
continue
}
tInfoList = append(tInfoList, tInfo)
}
tagInfoList := make([]TagInfo, len(tagList))
for i, tInfo := range tInfoList {
if uid == nil {
tagInfoList[i] = *tInfo
continue
}
tagInfoList[i] = *tInfo
}
return tagInfoList, nil
}
@@ -0,0 +1,32 @@
package tagsearcher
import (
"91porn-server/app/service/searcher"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
type TagRes = searcher.TagInfo
type Result struct {
dat []TagRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.TagSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,87 @@
package tagsearcher
import (
"errors"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/models/v/tagmod"
)
// TagSearcher TagSearcher
type TagSearcher struct {
uid *uint64
}
// NewTagSearcher NewTagSearcher
func NewTagSearcher(uid *uint64) searcher.Searcher {
return &TagSearcher{
uid: uid,
}
}
// Search Search
func (t *TagSearcher) Search(keywrod searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
kw, ok := keywrod.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
limitEx1 := opt.Limit() + 1
hasNext := false
if kw == "" { //关键字为空,热搜
tags, err := tagmod.TagListByFakePlayCount(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(tags) > int(opt.Limit()) {
hasNext = true
tags = tags[:opt.Limit()]
}
tagResList, err := searcher.TagInfoList(t.uid, tags)
if err != nil {
return nil, err
}
return &Result{tagResList, hasNext}, nil
}
var tags []tagmod.Tag
if appg.Conf.Elastic.IsActive {
tags, err = esSearch(kw, opt)
} else {
tags, err = tagmod.GetTagListByKeyword(kw, opt.Sort(), opt.Skip(), limitEx1)
}
if err != nil {
return nil, err
}
if len(tags) > int(opt.Limit()) {
hasNext = true
tags = tags[:opt.Limit()]
}
tagResList, err := searcher.TagInfoList(t.uid, tags)
if err != nil {
return nil, err
}
return &Result{tagResList, hasNext}, nil
}
func esSearch(keyword string, opt searcher.Opter) ([]tagmod.Tag, error) {
esTags, err := tagmod.Search(keyword, opt.Skip(), opt.Limit()+1)
if err != nil {
return nil, err
}
if len(esTags) == 0 {
return nil, nil
}
tagsID := make([]ObjectID, len(esTags))
for i, v := range esTags {
tagsID[i] = v.ID
}
tMap, err := tagmod.FindTagsMapByIDS(tagsID)
if err != nil || tMap == nil {
return nil, err
}
tags := make([]tagmod.Tag, len(tagsID))
for i, v := range tagsID {
tags[i] = tMap[v]
}
return tags, nil
}
@@ -0,0 +1,33 @@
package tonesearcher
import "91porn-server/app/service/searcher"
type ToneRes struct {
Name string `json:"name"` //主题名
Types string `json:"types"` //类型
Cover string `json:"cover"` //封面
PlayCount int64 `json:"playCount"` //播放数
PayCount int64 `json:"payCount"` //购买数
Sort int `json:"-"` //排序
}
type Result struct {
dat []ToneRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.ToneSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,74 @@
package tonesearcher
import (
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/common/constant/redisconst"
"91porn-server/common/log"
"91porn-server/models/v/tonemod"
"91porn-server/models/v/tonerecomod"
"go.mongodb.org/mongo-driver/bson"
)
// M M
type M = bson.M
type ToneValue = tonemod.ToneValue
// ToneSearcher ToneSearcher
type ToneSearcher struct {
}
// NewToneSearcher NewToneSearcher
func NewToneSearcher() searcher.Searcher {
return &ToneSearcher{}
}
// Search Search
func (t *ToneSearcher) Search(unuse1 searcher.KeyWorder, unuse2 searcher.Opter) (result searcher.Resulter, err error) {
enable := true
enableMatch := tonemod.EnableMatch{Enable: &enable}
max := int64(7)
list, err := tonemod.List(bson.D{{Key: "sortKey", Value: 1}}, 0, max, enableMatch.New())
if err != nil {
return nil, err
}
toneValue := GetToneValue()
toneResList := make([]ToneRes, len(list))
for i, v := range list {
toneRes := ToneRes{
Name: v.Name,
Types: v.Types,
Cover: v.Cover,
Sort: v.SortKey,
}
switch v.Types {
case tonerecomod.OfficialRecom:
toneRes.PlayCount = toneValue.OfficialRecomPlayCount
case tonerecomod.GoldCoinArea:
toneRes.PayCount = toneValue.GoldCoinAreaPayCount
case tonerecomod.LatestUpload:
toneRes.PlayCount = toneValue.LatestUploadPlayCount
case tonerecomod.MostLikes:
toneRes.PlayCount = toneValue.MostLikesPlayCount
case tonerecomod.PlayAtMost:
toneRes.PlayCount = toneValue.PlayAtMostPlayCount
case tonerecomod.MostComments:
toneRes.PlayCount = toneValue.MostCommentsPlayCount
case tonerecomod.FreeArea:
toneRes.PlayCount = toneValue.FreeAreaPlayCount
}
toneResList[i] = toneRes
}
return &Result{toneResList, false}, nil
}
func GetToneValue() ToneValue {
var toneValue ToneValue
redisKey := redisconst.ToneKey()
if err := appg.Redis.Scan(redisKey, &toneValue); err != nil {
log.Warn("tone Run redis Scan error", log.E(err))
}
return toneValue
}
+37
View File
@@ -0,0 +1,37 @@
package searcher
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// LouFengInfo LouFengInfo
type LouFengInfo struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Title string `json:"title" bson:"title,omitempty"` //标题
Number int `json:"number" bson:"number,omitempty"` //编号
Quantity string `json:"quantity" bson:"quantity,omitempty"` //数量
Age string `json:"age" bson:"age,omitempty"` //年龄
Cover string `json:"cover" bson:"cover,omitempty"` //封面
Price string `json:"price" bson:"price"` //价格 单位 元
BusinessHours string `json:"businessHours" bson:"businessHours,omitempty"` //营业时间 0-24
ServiceItems []string `json:"serviceItems" bson:"serviceItems,omitempty"` //服务项目
City string `json:"city" bson:"city,omitempty"` //市
District string `json:"district" bson:"district,omitempty"` //区,县
Contact string `json:"contact" bson:"contact,omitempty"` //联系方式
ContactPrice int64 `json:"contactPrice" bson:"contactPrice,omitempty"` //联系方式价格 单位 金币(角)
Impression string `json:"impression" bson:"impression,omitempty"` //狼友印象
EnvStar int `json:"envStar" bson:"envStar,omitempty"` //环境评分
PrettyStar int `json:"prettyStar" bson:"prettyStar,omitempty"` //颜值评分
OverallRatings int `json:"overallRatings" bson:"overallRatings,omitempty"` //综合评分
ServiceStar int `json:"serviceStar" bson:"serviceStar,omitempty"` //服务星级
BusinessDate string `json:"businessDate" bson:"businessDate,omitempty"` //营业月 份 "2020-06"
IsActive bool `json:"isActive " bson:"isActive"` //是否激活
IsVerify bool `json:"isVerify" bson:"isVerify"` //是否验证
CountPurchases int64 `json:"countPurchases" bson:"countPurchases"` //购买数
CountBrowse int64 `json:"countBrowse" bson:"countBrowse"` //浏览数
CountCollect int64 `json:"countCollect" bson:"countCollect"` //收藏数
CreatedAt time.Time `json:"createdAt" bson:"createdAt,omitempty"` //创建时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt,omitempty"` //修改时间
}
@@ -0,0 +1,26 @@
package usersearcher
import "91porn-server/app/service/searcher"
type UserRes = searcher.UserRes
type Result struct {
dat []searcher.UserRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.UserSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,105 @@
package usersearcher
import (
"errors"
"strconv"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/models/v/usermod"
)
// UserSearcher UserSearcher
type UserSearcher struct {
uid uint64
}
// NewUserSearcher NewUserSearcher
func NewUserSearcher(uid uint64) searcher.Searcher {
return &UserSearcher{
uid: uid,
}
}
// Search Search
func (u *UserSearcher) Search(keyword searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
kw, ok := keyword.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
limitEx1 := opt.Limit() + 1
hasNext := false
if kw == "" { //关键字为空 返回最新用户
uList, err := usermod.NewUserList(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(uList) > int(opt.Limit()) {
hasNext = true
uList = uList[:opt.Limit()]
}
userResList := searcher.MapToUserResList(uList, u.uid)
return &Result{userResList, hasNext}, nil
}
userList := make([]*usermod.User, 0, limitEx1)
uid, err := strconv.ParseUint(kw, 10, 64)
var esHasNext bool
if err == nil { //能解析成Int 通过uid搜索
user, err := usermod.FindUserByUID(uid)
if err != nil {
return nil, err
}
if user != nil {
userList = append(userList, user)
}
} else { //通过用户名搜索
var uList []*usermod.User
if appg.Conf.Elastic.IsActive {
uList, esHasNext, err = esSearch(kw, opt)
} else {
uList, err = usermod.FindUsersByKeyword(kw, opt.Skip(), limitEx1)
}
if err != nil {
return nil, err
}
userList = append(userList, uList...)
}
if len(userList) == 0 {
return &Result{[]UserRes{}, false}, nil
}
if len(userList) > int(opt.Limit()) {
hasNext = true
userList = userList[:opt.Limit()]
}
if appg.Conf.Elastic.IsActive {
hasNext = esHasNext
}
userResList := searcher.MapToUserResList(userList, u.uid)
return &Result{userResList, hasNext}, nil
}
func esSearch(keyword string, opt searcher.Opter) (users []*usermod.User, hasNext bool, err error) {
esUsers, err := usermod.Search(keyword, opt.Skip(), opt.Limit()+1)
if err != nil {
return
}
if len(esUsers) == 0 {
return
}
uidList := make([]uint64, len(esUsers))
for i, v := range esUsers {
uidList[i] = v.Source.UID
}
hasNext = len(esUsers) > int(opt.Limit())
uMap, err := usermod.FindUsersMapByUID(uidList)
if err != nil || uMap == nil {
return
}
users = make([]*usermod.User, 0, len(esUsers))
for _, v := range esUsers {
if u, ok := uMap[v.Source.UID]; ok {
users = append(users, u)
}
}
return
}
+562
View File
@@ -0,0 +1,562 @@
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)
}
@@ -0,0 +1,37 @@
package vidhkwsearcher
import (
"91porn-server/app/service/searcher"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type M = bson.M
type VideoRes = searcher.VideoRes
type ObjectID = primitive.ObjectID
type Video = searcher.VideoInfo
type Result struct {
dat []VideoRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidHotKeywordSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,112 @@
package vidhkwsearcher
import (
"91porn-server/common/constant"
"errors"
"fmt"
"time"
"91porn-server/app/service/searcher"
"91porn-server/app/service/searcher/vidsearcher"
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/models/s/kwrankmod"
)
// VidHKWSearcher VidHKWSearcher
type VidHKWSearcher struct {
uid uint64
vidOpt searcher.Opter
}
// NewVidHKWSearcher NewVidHKWSearcher
func NewVidHKWSearcher(uid uint64, vidOpt searcher.Opter) searcher.Searcher {
return &VidHKWSearcher{
uid: uid,
vidOpt: vidOpt,
}
}
type pair struct {
Word string
Result searcher.Resulter
}
const timeout = 3 * time.Second
const gorouCountMax = 20
// Search Search
func (v *VidHKWSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limitEx1 := opt.Limit() + 1
//获取热词
words, err := kwrankmod.RecentRanking(limitEx1)
if err != nil {
return nil, errors.New("vidhkwsearcher RecentRanking error")
}
//没有热词
if len(words) == 0 {
return &Result{[]VideoRes{}, false}, nil
}
hasNext := false
if len(words) > int(opt.Limit()) {
words = words[:opt.Limit()]
hasNext = true
}
gorouCount := opt.Limit()
if gorouCount > gorouCountMax {
gorouCount = gorouCountMax
}
ch := make(chan pair, gorouCount)
err_ch := make(chan error)
for _, w := range words {
word := w.KeyWord
common.Go(func() {
k := &searcher.Keyword{Word: word}
result, err := vidsearcher.NewVidSearcher(v.uid, constant.SearchSP, 0).Search(k, v.vidOpt)
if err != nil {
err_ch <- err
return
}
pair := pair{
Word: word,
Result: result,
}
ch <- pair
})
}
count := len(words)
resultMap := make(map[string]searcher.Resulter, count)
timer := time.NewTimer(timeout)
for i := 0; i < count; i++ {
timer.Reset(timeout)
select {
case pair := <-ch:
resultMap[pair.Word] = pair.Result
if i == count-1 {
return toResult(words, v.vidOpt.Limit(), resultMap, hasNext), nil
}
case err := <-err_ch: //获取出错前搜索到的视屏
log.Error(fmt.Sprintf("VidKWSearcher error:%+v\n", err))
return toResult(words, v.vidOpt.Limit(), resultMap, hasNext), nil
case <-timer.C: //获取超时前搜索到的视屏
log.Warn(fmt.Sprintf("VidKWSearcher timeout::%+v\n", timeout))
return toResult(words, v.vidOpt.Limit(), resultMap, hasNext), nil
}
}
return &Result{[]VideoRes{}, false}, nil
}
func toResult(words []kwrankmod.Keyword, vidCountPer int64, resultMap map[string]searcher.Resulter, hasNext bool) searcher.Resulter {
resList := make([]VideoRes, 0, vidCountPer)
//还原视屏的排序
for _, w := range words {
word := w.KeyWord
result, ok := resultMap[word]
if !ok {
continue
}
videoList := result.Data().([]VideoRes)
resList = append(resList, videoList...)
}
return &Result{resList, hasNext}
}
@@ -0,0 +1,40 @@
package vidhotsearcher
import "91porn-server/app/service/searcher"
type VideoRes = searcher.VideoRes
type RichVidRes = searcher.RichVidRes
type ResultType = int
const (
Video ResultType = iota
RichVido
)
type Result struct {
dat []VideoRes
richDat []RichVidRes
typ ResultType
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidPCountSearchResult
}
func (r *Result) Data() interface{} {
if r.typ == Video {
return r.dat
}
return r.richDat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,117 @@
package vidhotsearcher
import (
"time"
"91porn-server/app/service/searcher"
"91porn-server/common/log"
"91porn-server/models/s/statvidmod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
// VidHotSearcher VidHotSearcher
type VidHotSearcher struct {
uid uint64
typ ResultType
}
// NewVidHotSearcher NewVidHotSearcher
func NewVidHotSearcher(uid uint64) searcher.Searcher {
return &VidHotSearcher{
uid: uid,
typ: Video,
}
}
// NewRichVidHotSearcher NewRichVidHotSearcher
func NewRichVidHotSearcher(uid uint64) searcher.Searcher {
return &VidHotSearcher{
uid: uid,
typ: RichVido,
}
}
// Search Search
func (v *VidHotSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
var (
vidList []ObjectID
countMap map[ObjectID]int64
)
limitEx := opt.Limit() + 1
if isEarlyMorning1AM(time.Now()) {
vidList, err = vidmod.VIDListByCreatedAt(opt.Skip(), limitEx)
} else {
vidList, countMap, err = statvidmod.GetDailyPlayCountGroup(opt.Skip(), limitEx)
}
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
dat: []VideoRes{},
richDat: []RichVidRes{},
typ: v.typ,
hasNext: false,
}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
vidList = vidList[:opt.Limit()]
hasNext = true
}
if v.typ == Video {
videoResList, err := searcher.GetVideoResList(v.uid, vidList)
if err != nil {
return nil, err
}
setVideoResListPayCount(videoResList, countMap)
return &Result{
dat: videoResList,
typ: v.typ,
hasNext: hasNext,
}, nil
} else {
richVideoResList, err := searcher.GetRichVideoResList(v.uid, vidList)
if err != nil {
return nil, err
}
setRichVideoResListPayCount(richVideoResList, countMap)
return &Result{
richDat: richVideoResList,
typ: v.typ,
hasNext: hasNext,
}, nil
}
}
func setVideoResListPayCount(list []VideoRes, countMap map[ObjectID]int64) {
for i := 0; i < len(list); i++ {
video := list[i]
vid := *video.VideoInfo.ID
playCount := int(countMap[vid])
video.PlayCount = &playCount
list[i] = video
}
}
func setRichVideoResListPayCount(list []RichVidRes, countMap map[ObjectID]int64) {
for i := 0; i < len(list); i++ {
video := list[i]
vid := *video.VideoInfo.ID
playCount := int(countMap[vid])
video.PlayCount = &playCount
list[i] = video
}
}
//1:00 - 1:10
func isEarlyMorning1AM(cur time.Time) bool {
start := time.Date(cur.Year(), cur.Month(), cur.Day(), 1, 0, 0, 0, cur.Location())
end := time.Date(cur.Year(), cur.Month(), cur.Day(), 1, 10, 0, 0, cur.Location())
log.Info("vidhotsearcher isEarlyMorning1AM", log.Any("start", start.String()), log.Any("end", end.String()), log.Any("cur", cur.String()))
return cur.After(start) && cur.Before(end)
}
@@ -0,0 +1,40 @@
package vidpcountsearcher
import "91porn-server/app/service/searcher"
type VideoRes = searcher.VideoRes
type RichVidRes = searcher.RichVidRes
type ResultType = int
const (
Video ResultType = iota
RichVido
)
type Result struct {
dat []VideoRes
richDat []RichVidRes
typ ResultType
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidPCountSearchResult
}
func (r *Result) Data() interface{} {
if r.typ == Video {
return r.dat
}
return r.richDat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,73 @@
package vidpcountsearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
// VidPlayCountSearcher VidPlayCountSearcher
type VidPlayCountSearcher struct {
uid uint64
typ ResultType
}
// NewVidPlayCountSearcher NewVidPlayCountSearcher
func NewVidPlayCountSearcher(uid uint64) searcher.Searcher {
return &VidPlayCountSearcher{
uid: uid,
typ: Video,
}
}
// NewRichVidPlayCountSearcher NewRichVidPlayCountSearcher
func NewRichVidPlayCountSearcher(uid uint64) searcher.Searcher {
return &VidPlayCountSearcher{
uid: uid,
typ: RichVido,
}
}
// Search Search
func (v *VidPlayCountSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limitEx := opt.Limit() + 1
vidList, err := vidmod.VIDListByFakePlayCount(opt.Skip(), limitEx)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
typ: v.typ,
dat: []VideoRes{},
richDat: []RichVidRes{},
}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
if v.typ == Video {
videoResList, err := searcher.GetVideoResList(v.uid, vidList)
if err != nil {
return nil, err
}
return &Result{
dat: videoResList,
typ: v.typ,
hasNext: hasNext,
}, nil
}
richVideoResList, err := searcher.GetRichVideoResList(v.uid, vidList)
if err != nil {
return nil, err
}
return &Result{
richDat: richVideoResList,
typ: v.typ,
hasNext: hasNext,
}, nil
}
@@ -0,0 +1,28 @@
package vidrichsearcher
import "91porn-server/app/service/searcher"
type RichVidRes = searcher.RichVidRes
type VideoSy = searcher.VideoInfo
type Result struct {
dat []RichVidRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidRichSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,76 @@
package vidrichsearcher
import (
"errors"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/app/service/searcher/vidhotsearcher"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
// VidRichSearcher VidRichSearcher
type VidRichSearcher struct {
uid uint64
}
// NewVidRichSearcher NewVidRichSearcher
func NewVidRichSearcher(uid uint64) searcher.Searcher {
return &VidRichSearcher{
uid: uid,
}
}
// Search Search
func (v *VidRichSearcher) Search(keyword searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
kw, ok := keyword.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
if kw == "" { //关键字为空,热搜
return vidhotsearcher.NewRichVidHotSearcher(v.uid).Search(nil, opt)
}
limitEx1 := opt.Limit() + 1
var vidList []vidmod.ObjectID
if appg.Conf.Elastic.IsActive {
vidList, err = esSearch(kw, opt)
} else {
vidList, err = searcher.VideoListByKeyword(kw, opt.Skip(), limitEx1)
}
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{[]RichVidRes{}, false}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
richVideoResList, err := searcher.GetRichVideoResListForSearch(v.uid, vidList)
if err != nil {
return nil, err
}
return &Result{richVideoResList, hasNext}, nil
}
func esSearch(keyword string, opt searcher.Opter) ([]vidmod.ObjectID, error) {
esVideos, err := vidmod.Search(keyword, opt.Skip(), opt.Limit())
if err != nil {
return nil, err
}
esVideosLen := len(esVideos)
if esVideosLen == 0 {
return nil, nil
}
videosID := make([]vidmod.ObjectID, esVideosLen)
for i, v := range esVideos {
videosID[i] = v.ID
}
return videosID, nil
}
@@ -0,0 +1,41 @@
package vidsearcher
import (
"91porn-server/app/service/searcher"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// M M
type M = bson.M
// A A
type A = bson.A
type VideoRes = searcher.VideoRes
type ObjectID = primitive.ObjectID
type Video = searcher.VideoInfo
type Result struct {
dat []VideoRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,72 @@
package vidsearcher
import (
"errors"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/app/service/searcher/vidhotsearcher"
"91porn-server/models/v/vidmod"
)
// VidSearcher VidSearcher
type VidSearcher struct {
uid uint64
vidType string
sortType int
}
// NewVidSearcher NewVidSearcher
func NewVidSearcher(uid uint64, vidType string, sortType int) searcher.Searcher {
return &VidSearcher{uid: uid, vidType: vidType, sortType: sortType}
}
// Search Search
func (v *VidSearcher) Search(keyword searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
kw, ok := keyword.Get().(string)
if !ok {
return nil, errors.New("keyword type error")
}
if kw == "" { //关键字为空,热搜
return vidhotsearcher.NewVidHotSearcher(v.uid).Search(nil, opt)
}
limitEx1 := opt.Limit() + 1
var vidList []vidmod.ObjectID
if appg.Conf.Elastic.IsActive { // 使用ES搜索视频
vidList, err = esSearch(kw, v.vidType, opt, v.sortType)
} else {
vidList, err = searcher.VideoListByKeyword(kw, opt.Skip(), limitEx1)
}
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{[]VideoRes{}, false}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResListForSearch(v.uid, vidList)
if err != nil {
return nil, err
}
return &Result{videoResList, hasNext}, nil
}
func esSearch(keyword string, vidType string, opt searcher.Opter, sortType int) ([]vidmod.ObjectID, error) {
esVideos, err := vidmod.DirectSearch(keyword, vidType, sortType, opt.Skip(), opt.Limit()+1)
if err != nil {
return nil, nil
}
esVideosLen := len(esVideos)
if esVideosLen == 0 {
return nil, err
}
videosID := make([]vidmod.ObjectID, esVideosLen)
for i, v := range esVideos {
videosID[i] = v.ID
}
return videosID, nil
}
@@ -0,0 +1,34 @@
package vidtagsearcher
import (
"91porn-server/app/service/searcher"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type VideoRes = searcher.VideoRes
type ObjectID = primitive.ObjectID
type Video = searcher.VideoInfo
type Result struct {
dat []VideoRes
hasNext bool
}
func (*Result) Types() searcher.ResultTypes {
return searcher.VidTagSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
return int64(len(r.dat))
}
func (r *Result) HasNext() bool {
return r.hasNext
}
@@ -0,0 +1,63 @@
package vidtagsearcher
import (
"errors"
"91porn-server/app/service/searcher"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson"
)
// M M
type M = bson.M
// A A
type A = bson.A
// VidTagSearcher VidTagSearcher
type VidTagSearcher struct {
uid uint64
}
var (
videoListByTagID = vidmod.VideoListByTagID
getVideoResListForSearch = searcher.GetVideoResListForSearch
)
// NewVidWonderSearcher NewVidWonderSearcher
func NewVidTagSearcher(uid uint64) searcher.Searcher {
return &VidTagSearcher{
uid: uid,
}
}
// Search Search
func (v *VidTagSearcher) Search(tid searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
tagId, ok := tid.Get().(ObjectID)
if !ok {
return nil, errors.New("keyword type error")
}
limitEx1 := opt.Limit() + 1
videoList, err := videoListByTagID(tagId, opt.Sort(), opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(videoList) == 0 {
return &Result{[]VideoRes{}, false}, nil
}
hasNext := false
if len(videoList) > int(opt.Limit()) {
hasNext = true
videoList = videoList[:opt.Limit()]
}
vidList := make([]ObjectID, len(videoList))
for i, video := range videoList {
vidList[i] = video.ID
}
videoResList, err := getVideoResListForSearch(v.uid, vidList)
if err != nil {
return nil, err
}
return &Result{videoResList, hasNext}, nil
}
@@ -0,0 +1,78 @@
package vidtagsearcher
import (
"reflect"
"testing"
"91porn-server/app/service/searcher"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestSearchUsesSearchVisibleVideoResults(t *testing.T) {
originalVideoListByTagID := videoListByTagID
originalGetVideoResListForSearch := getVideoResListForSearch
t.Cleanup(func() {
videoListByTagID = originalVideoListByTagID
getVideoResListForSearch = originalGetVideoResListForSearch
})
tagID := primitive.NewObjectID()
videoIDs := []primitive.ObjectID{
primitive.NewObjectID(),
primitive.NewObjectID(),
primitive.NewObjectID(),
}
videoListByTagID = func(gotTagID primitive.ObjectID, sort bson.D, skip, limit int64) ([]vidmod.VideoModel, error) {
if gotTagID != tagID {
t.Fatalf("tagID = %s, want %s", gotTagID.Hex(), tagID.Hex())
}
if skip != 0 || limit != 3 {
t.Fatalf("skip/limit = %d/%d, want 0/3", skip, limit)
}
return []vidmod.VideoModel{
{ID: videoIDs[0]},
{ID: videoIDs[1]},
{ID: videoIDs[2]},
}, nil
}
const uid = uint64(17803584)
searchAccessToken := "search-token"
getVideoResListForSearch = func(gotUID uint64, gotVideoIDs []primitive.ObjectID) ([]searcher.VideoRes, error) {
if gotUID != uid {
t.Fatalf("uid = %d, want %d", gotUID, uid)
}
if !reflect.DeepEqual(gotVideoIDs, videoIDs[:2]) {
t.Fatalf("video IDs = %v, want %v", gotVideoIDs, videoIDs[:2])
}
return []searcher.VideoRes{
{VideoInfo: searcher.VideoInfo{ID: &gotVideoIDs[0], SearchAccessToken: &searchAccessToken}},
{VideoInfo: searcher.VideoInfo{ID: &gotVideoIDs[1], SearchAccessToken: &searchAccessToken}},
}, nil
}
option := &searcher.Option{}
option.SetLimit(2)
result, err := NewVidTagSearcher(uid).Search(&searcher.ObjID{ID: tagID}, option)
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if !result.HasNext() {
t.Fatal("Search() hasNext = false, want true")
}
videos, ok := result.Data().([]searcher.VideoRes)
if !ok {
t.Fatalf("Search() data type = %T, want []searcher.VideoRes", result.Data())
}
if len(videos) != 2 {
t.Fatalf("Search() result length = %d, want 2", len(videos))
}
for i, video := range videos {
if video.SearchAccessToken == nil || *video.SearchAccessToken != searchAccessToken {
t.Fatalf("video %d searchAccessToken = %v, want %q", i, video.SearchAccessToken, searchAccessToken)
}
}
}
@@ -0,0 +1,55 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/common/log"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
type freeAreaSearcher struct {
uid uint64
}
func newFreeAreaSearcher(uid uint64) searcher.Searcher {
return &freeAreaSearcher{
uid: uid,
}
}
// Search Search
func (f *freeAreaSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limitEx1 := opt.Limit() + 1
vidList, err := vidmod.GetFreeVidList(opt.Sort(), opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.FreeArea,
List: []VideoRes{},
PayCount: &zero,
},
}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoInfoList, err := searcher.GetVideoResList(f.uid, vidList)
if err != nil {
return nil, err
}
log.Info("freeArea search result", log.Any("totalVideoCount", len(vidList)), log.Any("respVideoCount", len(videoInfoList)))
freeAreaPlayCount := GetToneValue().FreeAreaPlayCount
return &Result{
&Resp{
List: videoInfoList,
Theme: tonerecomod.FreeArea,
PlayCount: &freeAreaPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,80 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/common/log"
"91porn-server/models/s/statvidtotalmod"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
type VideoSy = searcher.VideoInfo
// VidToneSearcher VidToneSearcher
type goldCoinAreaSearcher struct {
uid uint64
}
var zero = int64(0)
// newGoldCoinAreaSearcher newGoldCoinAreaSearcher
func newGoldCoinAreaSearcher(uid uint64) searcher.Searcher {
return &goldCoinAreaSearcher{
uid: uid,
}
}
// Search Search
func (g *goldCoinAreaSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limitEx1 := opt.Limit() + 1
vidList, err := vidmod.GetPayVidList(opt.Sort(), opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.GoldCoinArea,
List: []VideoRes{},
PayCount: &zero,
},
}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoInfoList, err := searcher.GetVideoResList(g.uid, vidList)
if err != nil {
return nil, err
}
log.Info("goldCoinArea search result", log.Any("totalVideoCount", len(vidList)), log.Any("respVideoCount", len(videoInfoList)))
payCountMap, err := statvidtotalmod.GetPayCountMap(vidList)
if err != nil {
return nil, err
}
videoInfoPCList := make([]VideoRes, 0, len(videoInfoList))
for _, info := range videoInfoList {
if info.VideoInfo.ID == nil {
continue
}
id := *info.VideoInfo.ID
payCount := int(payCountMap[id])
info.VideoInfo.PayCount = &payCount
videoInfoPCList = append(videoInfoPCList, info)
}
goldCoinAreaPayCount := GetToneValue().GoldCoinAreaPayCount
return &Result{
&Resp{
List: videoInfoPCList,
Theme: tonerecomod.GoldCoinArea,
PayCount: &goldCoinAreaPayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,58 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
// latestUpload latestUpload
type latestUploadSearcher struct {
uid uint64
}
// latestUpload latestUpload
func newLatestUploadSearcher(uid uint64) searcher.Searcher {
return &latestUploadSearcher{
uid: uid,
}
}
// Search Search
func (l *latestUploadSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limit := opt.Limit()
skip := opt.Skip()
limitEx1 := limit + 1
vidList, err := vidmod.VIDListByCreatedAt(skip, limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.LatestUpload,
List: []VideoRes{},
PlayCount: &zero,
HasNext: false,
},
}, nil
}
hasNext := false
if len(vidList) > int(limit) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResList(l.uid, vidList)
if err != nil {
return nil, err
}
latestUploadPlayCount := GetToneValue().LatestUploadPlayCount
return &Result{
&Resp{
List: videoResList,
Theme: tonerecomod.LatestUpload,
PlayCount: &latestUploadPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,57 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
// mostCommentsSearcher mostCommentsSearcher
type mostCommentsSearcher struct {
uid uint64
}
// newMostCommentsSearcher newMostCommentsSearcher
func newMostCommentsSearcher(uid uint64) searcher.Searcher {
return &mostCommentsSearcher{
uid: uid,
}
}
// Search Search
func (g *mostCommentsSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limit := opt.Limit()
limitEx1 := limit + 1
vidList, err := vidmod.VIDListSortByFakeCommentCount(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.MostComments,
List: []VideoRes{},
PlayCount: &zero,
HasNext: false,
},
}, nil
}
hasNext := false
if len(vidList) > int(limit) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResList(g.uid, vidList)
if err != nil {
return nil, err
}
mostCommentsPlayCount := GetToneValue().MostCommentsPlayCount
return &Result{
&Resp{
List: videoResList,
Theme: tonerecomod.MostComments,
PlayCount: &mostCommentsPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,57 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
// latestUpload latestUpload
type mostLikesSearcher struct {
uid uint64
}
// latestUpload latestUpload
func newMostLikesSearcher(uid uint64) searcher.Searcher {
return &mostLikesSearcher{
uid: uid,
}
}
// Search Search
func (g *mostLikesSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limit := opt.Limit()
limitEx1 := limit + 1
vidList, err := vidmod.VIDListByFakeLikeCount(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.MostLikes,
List: []VideoRes{},
PlayCount: &zero,
HasNext: false,
},
}, nil
}
hasNext := false
if len(vidList) > int(limit) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResList(g.uid, vidList)
if err != nil {
return nil, err
}
mostLikesPlayCount := GetToneValue().MostLikesPlayCount
return &Result{
&Resp{
List: videoResList,
Theme: tonerecomod.MostLikes,
PlayCount: &mostLikesPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,56 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
// VidToneSearcher VidToneSearcher
type officialRecomSearcher struct {
uid uint64
}
// officialRecomSearcher officialRecomSearcher
func newOfficialRecomSearcher(uid uint64) searcher.Searcher {
return &officialRecomSearcher{
uid: uid,
}
}
// Search Search
func (g *officialRecomSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limitEx1 := opt.Limit() + 1
vidList, err := vidmod.VIDListToneOfficialRecom(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.OfficialRecom,
List: []VideoRes{},
PlayCount: &zero,
HasNext: false,
},
}, nil
}
hasNext := false
if len(vidList) > int(opt.Limit()) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResList(g.uid, vidList)
if err != nil {
return nil, err
}
officialRecomPlayCount := GetToneValue().OfficialRecomPlayCount
return &Result{
&Resp{
List: videoResList,
Theme: tonerecomod.OfficialRecom,
PlayCount: &officialRecomPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,57 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"91porn-server/models/v/tonerecomod"
"91porn-server/models/v/vidmod"
)
// playAtMostSearcher playAtMostSearcher
type playAtMostSearcher struct {
uid uint64
}
// newPlayAtMostSearcher newPlayAtMostSearcher
func newPlayAtMostSearcher(uid uint64) searcher.Searcher {
return &playAtMostSearcher{
uid: uid,
}
}
// Search Search
func (g *playAtMostSearcher) Search(unuse searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
limit := opt.Limit()
limitEx1 := limit + 1
vidList, err := vidmod.VIDListByFakePlayCount(opt.Skip(), limitEx1)
if err != nil {
return nil, err
}
if len(vidList) == 0 {
return &Result{
&Resp{
Theme: tonerecomod.PlayAtMost,
List: []VideoRes{},
PlayCount: &zero,
HasNext: false,
},
}, nil
}
hasNext := false
if len(vidList) > int(limit) {
hasNext = true
vidList = vidList[:opt.Limit()]
}
videoResList, err := searcher.GetVideoResList(g.uid, vidList)
if err != nil {
return nil, err
}
playAtMostPlayCount := GetToneValue().PlayAtMostPlayCount
return &Result{
&Resp{
List: videoResList,
Theme: tonerecomod.PlayAtMost,
PlayCount: &playAtMostPlayCount,
HasNext: hasNext,
},
}, nil
}
@@ -0,0 +1,51 @@
package vidtonesearcher
import (
"91porn-server/app/service/searcher"
"go.mongodb.org/mongo-driver/bson"
)
// M M
type M = bson.M
// A A
type A = bson.A
type VideoRes = searcher.VideoRes
type Resp struct {
List []VideoRes `json:"list"`
Theme string `json:"theme"`
PlayCount *int64 `json:"playCount,omitempty"`
PayCount *int64 `json:"payCount,omitempty"`
HasNext bool `json:"hasNext"`
}
type Result struct {
dat *Resp
}
const MaxVideoCount = 400
func (*Result) Types() searcher.ResultTypes {
return searcher.VidToneSearchResult
}
func (r *Result) Data() interface{} {
return r.dat
}
func (r *Result) Count() int64 {
if r.dat == nil {
return 0
}
return 1
}
func (r *Result) HasNext() bool {
if r.dat == nil {
return false
}
return r.dat.HasNext
}
@@ -0,0 +1,64 @@
package vidtonesearcher
import (
"fmt"
"91porn-server/app/appg"
"91porn-server/app/service/searcher"
"91porn-server/common/constant/redisconst"
"91porn-server/common/log"
"91porn-server/models/v/tonemod"
"91porn-server/models/v/tonerecomod"
)
type ToneValue = tonemod.ToneValue
// VidToneSearcher VidToneSearcher
type VidToneSearcher struct {
uid uint64
theme tonerecomod.ThemeType
}
// NewVidToneSearcher NewVidToneSearcher
func NewVidToneSearcher(theme tonerecomod.ThemeType, uid uint64) searcher.Searcher {
return &VidToneSearcher{
uid: uid,
theme: theme,
}
}
// Realm Realm
func (searcher *VidToneSearcher) Theme() string {
return searcher.theme
}
// Search Search
func (v *VidToneSearcher) Search(keyword searcher.KeyWorder, opt searcher.Opter) (result searcher.Resulter, err error) {
switch v.theme {
case tonerecomod.OfficialRecom:
return newOfficialRecomSearcher(v.uid).Search(nil, opt)
case tonerecomod.GoldCoinArea:
return newGoldCoinAreaSearcher(v.uid).Search(nil, opt)
case tonerecomod.LatestUpload:
return newLatestUploadSearcher(v.uid).Search(nil, opt)
case tonerecomod.MostLikes:
return newMostLikesSearcher(v.uid).Search(nil, opt)
case tonerecomod.PlayAtMost:
return newPlayAtMostSearcher(v.uid).Search(nil, opt)
case tonerecomod.MostComments:
return newMostCommentsSearcher(v.uid).Search(nil, opt)
case tonerecomod.FreeArea:
return newFreeAreaSearcher(v.uid).Search(nil, opt)
default:
return nil, fmt.Errorf("search vidtonesearcher invalid theme: %s", v.theme)
}
}
func GetToneValue() ToneValue {
var toneValue ToneValue
redisKey := redisconst.ToneKey()
if err := appg.Redis.Scan(redisKey, &toneValue); err != nil {
log.Warn("search vidtonesearcher GetToneValue Scan error", log.E(err))
}
return toneValue
}