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), }}, } }