Initial commit: web_hghl_server
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
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),
|
||||
}},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user