Files
web_hghl_server/app/service/actressser/actress.go
T
2026-10-26 16:28:14 +08:00

334 lines
12 KiB
Go

package actressser
import (
"context"
"videoweb/app/api/proto"
"videoweb/app/service/configser"
"videoweb/app/service/moreser"
"videoweb/app/service/slugser"
"videoweb/app/service/translationser"
"videoweb/pub/common/log"
"videoweb/pub/common/netutil"
"videoweb/pub/common/response"
"videoweb/pub/common/srvdef"
"videoweb/pub/models/actress"
"videoweb/pub/models/videoinformationmod"
"videoweb/pub/pubdef"
"videoweb/pub/storage/proxy"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
const homeActressPageSize int64 = 9
const videoHomeActressPageSize int64 = 16
const (
actressWorkDefaultPageSize int64 = 10
actressWorkMaxPageSize int64 = 100
actressWorkAdInterval int64 = 8
)
// ActressListData 查询女优榜单列表,按热度值降序返回全部可用数据
func ActressListData(ctx context.Context, path string) (*proto.QueryActressListResp, *srvdef.SrvErr) {
list := configser.GetActressWorks()
if len(list) == 0 {
var err *srvdef.SrvErr
list, err = queryActressListDataFromDB(ctx)
if err != nil {
return nil, err
}
}
resp := &proto.QueryActressListResp{
Path: path,
Translations: translationser.GetTranslationData(),
BottonTranslations: translationser.GetTranslationBottomList(),
Advitise: configser.GetAdvitise(),
ActressInfoList: list,
}
configser.RewriteImageCdn(ctx, resp)
return resp, nil
}
// HomeActressData 查询首页女优数据,按热度值降序取前 9 条
func HomeActressData(ctx context.Context) ([]proto.ActressInfo, *srvdef.SrvErr) {
return homeActressDataByCdn(ctx, homeActressPageSize, configser.GetConfigInfo().ImgCdn)
}
// VideoHomeActressData 查询视频首页热门女优数据,按热度值降序取前 16 条
func VideoHomeActressData(ctx context.Context) ([]proto.ActressInfo, *srvdef.SrvErr) {
return homeActressDataByCdn(ctx, videoHomeActressPageSize, configser.ImageCdnFromCtx(ctx))
}
// HomeActressDataByLimit 按热度值降序查询指定数量的女优数据
func HomeActressDataByLimit(ctx context.Context, limit int64) ([]proto.ActressInfo, *srvdef.SrvErr) {
return homeActressDataByCdn(ctx, limit, configser.ImageCdnFromCtx(ctx))
}
func homeActressDataByCdn(ctx context.Context, limit int64, imgCdn string) ([]proto.ActressInfo, *srvdef.SrvErr) {
if limit <= 0 {
limit = homeActressPageSize
}
list, err := actress.FindManyActresss(ctx, bson.M{
actress.FN.Actress.Status: true,
}, homeActressFindOptions(limit))
if err != nil {
log.Warn("查询首页女优数据失败", log.E(err))
return nil, err
}
data := make([]proto.ActressInfo, 0, len(list))
for _, item := range list {
data = append(data, homeActressInfo(item, imgCdn))
}
return data, nil
}
// queryActressListDataFromDB 回源查询女优榜单,使用字段投影减少 MongoDB 返回体积
func queryActressListDataFromDB(ctx context.Context) ([]proto.ActressInfoData, *srvdef.SrvErr) {
config := configser.GetConfigInfo()
list, err := actress.FindManyActresss(ctx, bson.M{
actress.FN.Actress.Status: true,
}, options.Find().
SetSort(bson.D{{Key: actress.FN.Actress.HotValue, Value: -1}, {Key: actress.FN.Actress.Id, Value: -1}}).
SetProjection(bson.M{
actress.FN.Actress.Id: 1,
actress.FN.Actress.Alias: 1,
actress.FN.Actress.Slug: 1,
actress.FN.Actress.Avatar: 1,
actress.FN.Actress.Cover: 1,
actress.FN.Actress.Desc: 1,
actress.FN.Actress.Office: 1,
actress.FN.Actress.DebutDate: 1,
actress.FN.Actress.BornDate: 1,
actress.FN.Actress.Region: 1,
actress.FN.Actress.Score: 1,
actress.FN.Actress.Works: 1,
actress.FN.Actress.HotValue: 1,
actress.FN.Actress.SeoInfo: 1,
actress.FN.Actress.Meas.Gender: 1,
actress.FN.Actress.Meas.Height: 1,
actress.FN.Actress.Meas.Weight: 1,
actress.FN.Actress.Meas.Bust: 1,
actress.FN.Actress.Meas.Waist: 1,
actress.FN.Actress.Meas.Hip: 1,
actress.FN.Actress.Meas.Cup: 1,
actress.FN.Actress.Meas.Len: 1,
actress.FN.Actress.Meas.Dia: 1,
actress.FN.Actress.Meas.Blood: 1,
actress.FN.Actress.Real.WatchTimes: 1,
actress.FN.Actress.Real.ShowTimes: 1,
actress.FN.Actress.Real.Comments: 1,
}))
if err != nil {
log.Warn("查询女优榜单数据失败", log.E(err))
return nil, err
}
data := make([]proto.ActressInfoData, 0, len(list))
for _, item := range list {
data = append(data, proto.ActressInfoToData(*item, config.ImgCdn))
}
return data, nil
}
// homeActressFindOptions 限定首页女优查询数量、排序和字段投影,减少 MongoDB 返回数据量
func homeActressFindOptions(limit int64) *options.FindOptions {
return options.Find().
SetLimit(limit).
SetSort(bson.D{{Key: actress.FN.Actress.HotValue, Value: -1}, {Key: actress.FN.Actress.Id, Value: -1}}).
SetProjection(bson.M{
actress.FN.Actress.Id: 1,
actress.FN.Actress.Alias: 1,
actress.FN.Actress.Slug: 1,
actress.FN.Actress.Avatar: 1,
actress.FN.Actress.Works: 1,
actress.FN.Actress.HotValue: 1,
})
}
// homeActressInfo 将女优模型转换为首页轻量女优结构
func homeActressInfo(item *actress.Actress, imgCdn string) proto.ActressInfo {
return proto.ActressInfo{
Id: item.Id,
Alias: item.Alias,
Slug: item.Slug,
Avatar: netutil.MergeURL(imgCdn, item.Avatar),
Works: item.Works,
HotValue: item.HotValue,
}
}
// QueryActressWorkList 查询指定女优详情和作品混排列表
func QueryActressWorkList(ctx context.Context, req *proto.QueryActressWorkListReq) (*proto.QueryActressWorkListResp, *srvdef.SrvErr) {
if req == nil {
return nil, srvdef.New(srvdef.ErrParamError, "", "")
}
actressId, err := slugser.ActressID(ctx, req.Id, req.Slug, true)
if err != nil {
return nil, err
}
req.Id = actressId
pageNum, pageSize := moreser.NormalizePage(req.PageNum, req.PageSize, actressWorkDefaultPageSize, actressWorkMaxPageSize)
config := configser.GetRequestConfig(ctx)
actressInfo := configser.GetActressInfo(req.Id)
if actressInfo == nil {
log.Warn("女优详情不存在", log.Any("actressId", req.Id))
return nil, srvdef.New(srvdef.AtressNotExist)
}
filter := bson.M{
videoinformationmod.FN.VideoInformation.ActressIds: bson.M{"$in": []int64{req.Id}},
videoinformationmod.FN.VideoInformation.Status: pubdef.InformationStatusVerifyOK,
}
totalCount, err := videoinformationmod.CountVideoInformations(ctx, filter)
if err != nil {
log.Warn("统计女优作品总数失败", log.Any("actressId", req.Id), log.E(err))
return nil, err
}
ads := configser.GetAdvitise().PostList
adsTotalCount := moreser.InsertedAdsCount(totalCount, int64(len(ads)), actressWorkAdInterval)
mixedTotalCount := totalCount + adsTotalCount
resp := &proto.QueryActressWorkListResp{
BasePageResp: response.BasePageResp{
TotalCount: mixedTotalCount,
PageSize: int(pageSize),
PageNum: int(pageNum),
List: make([]*proto.QueryVideoListItem, 0),
},
Translations: translationser.GetTranslationData(),
BottonTranslations: translationser.GetTranslationBottomList(),
ActressInfo: proto.ActressInfoToData(*actressInfo, config.ImgCdn),
}
if totalCount == 0 {
return resp, nil
}
start := (pageNum - 1) * pageSize
if start >= mixedTotalCount {
return resp, nil
}
end := start + pageSize
if end > mixedTotalCount {
end = mixedTotalCount
}
contentStart, contentLimit := moreser.ContentRange(start, end, totalCount, adsTotalCount, actressWorkAdInterval)
list := videoinformationmod.VideoInformationList{}
if contentLimit > 0 {
list, err = videoinformationmod.FindManyVideoInformations(ctx, filter, options.Find().
SetSort(actressWorkSort(req)).
SetProjection(actressWorkProjection()).
SetSkip(contentStart).
SetLimit(contentLimit))
if err != nil {
log.Warn("查询女优作品列表失败", log.Any("actressId", req.Id), log.E(err))
return nil, err
}
}
mediaList := buildActressWorkMixedListWithoutTotal(list, ads, config.ImgCdn, start, end, contentStart, adsTotalCount)
resp.List = mediaList
return resp, nil
}
// actressWorkSort 观看量和收藏量按真实数据 real.watchTimes、real.likes 排序,不使用 fake 数据
func actressWorkSort(req *proto.QueryActressWorkListReq) bson.D {
sort := make(bson.D, 0, 4)
if dir := actressWorkSortDir(req.AddedTimeSort); dir != 0 {
sort = append(sort, bson.E{Key: videoinformationmod.FN.VideoInformation.AddedTime, Value: dir})
}
if dir := actressWorkSortDir(req.WatchTimesSort); dir != 0 {
sort = append(sort, bson.E{Key: videoinformationmod.FN.VideoInformation.Real.WatchTimes, Value: dir})
}
if dir := actressWorkSortDir(req.LikesSort); dir != 0 {
sort = append(sort, bson.E{Key: videoinformationmod.FN.VideoInformation.Real.Likes, Value: dir})
}
if len(sort) > 0 {
sort = append(sort, bson.E{Key: videoinformationmod.FN.VideoInformation.Id, Value: -1})
return sort
}
return bson.D{
{Key: videoinformationmod.FN.VideoInformation.VideoCount, Value: -1},
{Key: videoinformationmod.FN.VideoInformation.AddedTime, Value: -1},
{Key: videoinformationmod.FN.VideoInformation.Id, Value: -1},
}
}
// actressWorkProjection 限定作品列表卡片字段,避免读取详情页大字段和视频节点。
func actressWorkProjection() bson.M {
return bson.M{
videoinformationmod.FN.VideoInformation.Id: 1,
videoinformationmod.FN.VideoInformation.Title: 1,
videoinformationmod.FN.VideoInformation.Slug: 1,
videoinformationmod.FN.VideoInformation.AddedTime: 1,
videoinformationmod.FN.VideoInformation.CoverImg: 1,
videoinformationmod.FN.VideoInformation.Tags: 1,
videoinformationmod.FN.VideoInformation.Real.WatchTimes: 1,
videoinformationmod.FN.VideoInformation.Fake.WatchTimes: 1,
videoinformationmod.FN.VideoInformation.Real.Likes: 1,
videoinformationmod.FN.VideoInformation.Fake.Likes: 1,
}
}
// actressWorkSortDir 将接口排序值转换为 MongoDB 排序方向
func actressWorkSortDir(sortType int64) int {
switch sortType {
case 1:
return 1
case 2:
return -1
default:
return 0
}
}
// buildActressWorkMixedListWithoutTotal 根据无总数混排分页窗口组装当前页列表
func buildActressWorkMixedListWithoutTotal(list videoinformationmod.VideoInformationList, ads []proxy.AdvitiseInfo, cdn string, start, end, contentStart, adsTotal int64) []*proto.QueryVideoListItem {
resp := make([]*proto.QueryVideoListItem, 0, end-start)
contentByIndex := make(map[int64]*videoinformationmod.VideoInformation, len(list))
for i, item := range list {
contentByIndex[contentStart+int64(i)] = item
}
for pos := start; pos < end; pos++ {
adIndex := moreser.AdIndexAtPositionWithoutTotal(pos, adsTotal, actressWorkAdInterval)
if adIndex >= 0 && adIndex < adsTotal {
resp = append(resp, actressWorkAdToListItem(ads[adIndex], cdn))
continue
}
contentIndex := moreser.ContentIndexAtPositionWithoutTotal(pos, adsTotal, actressWorkAdInterval)
if item := contentByIndex[contentIndex]; item != nil {
resp = append(resp, actressWorkVideoToListItem(item, cdn))
}
}
return resp
}
// actressWorkVideoToListItem 将视频模型转换为女优作品列表中的视频项
func actressWorkVideoToListItem(item *videoinformationmod.VideoInformation, cdn string) *proto.QueryVideoListItem {
tagNames, tagSlugs := configser.VideoTagNamesAndSlugs(item.Tags)
return &proto.QueryVideoListItem{
Type: proto.QueryVideoItemTypeVideo,
VideoInfo: proto.VideoInfo{ContentData: proto.ContentData{
Id: item.Id,
Title: item.Title,
Slug: item.Slug,
AddedTime: item.AddedTime,
CoverImg: netutil.MergeURL(cdn, item.CoverImg),
Tags: item.Tags,
TagsName: tagNames,
TagSlugs: tagSlugs,
WatchNum: item.Real.WatchTimes + item.Fake.WatchTimes,
LikeNum: item.Real.Likes + item.Fake.Likes,
}},
}
}
// actressWorkAdToListItem 将列表广告转换为女优作品列表中的广告项
func actressWorkAdToListItem(ad proxy.AdvitiseInfo, cdn string) *proto.QueryVideoListItem {
return &proto.QueryVideoListItem{
Type: proto.QueryVideoItemTypeAdvertise,
AdvertiseId: ad.Id,
Link: ad.Link,
Location: ad.Location,
VideoInfo: proto.VideoInfo{ContentData: proto.ContentData{
Title: ad.Title,
CoverImg: netutil.MergeURL(cdn, ad.Images),
}},
}
}