package dramaser import ( "encoding/json" "errors" "fmt" "sort" "time" "91porn-server/app/appg" "91porn-server/app/service/mediaser" "91porn-server/common/dramatopic" "91porn-server/common/log" "91porn-server/models/commod" "91porn-server/models/v/mediamod" "91porn-server/models/v/modulesectionmod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo/options" ) const everyoneLikesCacheTTL = 30 * time.Second type TopicSummary struct { TopicID string `json:"topicId"` Name string `json:"name"` TopicType string `json:"topicType"` SystemKey string `json:"systemKey"` Sort int `json:"sort"` WorkCount int64 `json:"workCount"` } type TopicListResponse struct { List []TopicSummary `json:"list"` } type TopicWorksRequest struct { TopicID string `form:"topicId" json:"topicId" binding:"required"` commod.Page } type TopicWorksResponse struct { Topic TopicSummary `json:"topic"` Total int64 `json:"total"` HasNext bool `json:"hasNext"` List []*mediamod.AppMediaBase `json:"list"` } func GetTopics(now time.Time) (TopicListResponse, error) { moduleID, ok, err := dramatopic.ModuleID(now) if err != nil { return TopicListResponse{}, err } total, err := mediamod.QueryAllCount(activeDramaFilter()) if err != nil { return TopicListResponse{}, err } sections := make([]modulesectionmod.Section, 0) if ok { sections, err = modulesectionmod.ListBySubModule(moduleID, 1000) if err != nil { return TopicListResponse{}, err } } sectionIDs := make([]primitive.ObjectID, 0, len(sections)) for _, section := range sections { if section.Status != nil && *section.Status == 1 { sectionIDs = append(sectionIDs, section.ID) } } counts, err := mediamod.CountActiveDramaBySectionIDs(sectionIDs) if err != nil { return TopicListResponse{}, err } items := make([]topicSortItem, 0, 3+len(sections)) for _, system := range dramatopic.SystemTopics() { items = append(items, topicSortItem{summary: TopicSummary{ TopicID: system.ID, Name: system.Name, TopicType: dramatopic.TypeSystem, SystemKey: system.SystemKey, Sort: system.Sort, WorkCount: total, }, systemOrder: system.TieOrder}) } for _, section := range sections { if section.Status == nil || *section.Status != 1 { continue } items = append(items, topicSortItem{summary: TopicSummary{ TopicID: section.ID.Hex(), Name: section.SectionName, TopicType: dramatopic.TypeCustom, Sort: valueOrZero(section.Sort), WorkCount: counts[section.ID], }, updatedAt: section.UpdatedAt}) } sortTopicItems(items) response := TopicListResponse{List: make([]TopicSummary, 0, len(items))} for _, item := range items { response.List = append(response.List, item.summary) } return response, nil } type topicSortItem struct { summary TopicSummary systemOrder int updatedAt time.Time } func sortTopicItems(items []topicSortItem) { sort.SliceStable(items, func(i, j int) bool { if items[i].summary.Sort != items[j].summary.Sort { return items[i].summary.Sort > items[j].summary.Sort } if items[i].systemOrder != items[j].systemOrder { return items[i].systemOrder > items[j].systemOrder } return items[i].updatedAt.After(items[j].updatedAt) }) } func GetTopicWorks(uid uint64, req TopicWorksRequest, now time.Time) (TopicWorksResponse, error) { if system, ok := dramatopic.FindSystem(req.TopicID); ok { return getSystemTopicWorks(uid, req, system) } moduleID, ok, err := dramatopic.ModuleID(now) if err != nil || !ok { if err == nil { err = errors.New("短剧专题模块未配置") } return TopicWorksResponse{}, err } topicID, err := primitive.ObjectIDFromHex(req.TopicID) if err != nil { return TopicWorksResponse{}, errors.New("无效的专题ID") } section, err := modulesectionmod.GetBySectionByID(topicID) if err != nil || section.SubModuleID != moduleID || section.DeletedAt != nil || section.Status == nil || *section.Status != 1 { if err == nil { err = errors.New("专题不存在或已停用") } return TopicWorksResponse{}, err } filter := activeDramaFilter() filter["sId"] = topicID total, err := mediamod.QueryAllCount(filter) if err != nil { return TopicWorksResponse{}, err } list, err := mediamod.QueryAllList(filter, options.Find(). SetSkip(req.Skip64()).SetLimit(req.Limit64()+1). SetSort(bson.D{{Key: "sectionSort", Value: -1}, {Key: "latestPublishedAt", Value: -1}, {Key: "_id", Value: -1}})) if err != nil { return TopicWorksResponse{}, err } hasNext := len(list) > int(req.PageSize) if hasNext { list = list[:req.PageSize] } return TopicWorksResponse{ Topic: TopicSummary{TopicID: req.TopicID, Name: section.SectionName, TopicType: dramatopic.TypeCustom, Sort: valueOrZero(section.Sort), WorkCount: total}, Total: total, HasNext: hasNext, List: fillTopicMedias(list, uid), }, nil } func getSystemTopicWorks(uid uint64, req TopicWorksRequest, topic dramatopic.SystemTopic) (TopicWorksResponse, error) { filter := activeDramaFilter() total, err := mediamod.QueryAllCount(filter) if err != nil { return TopicWorksResponse{}, err } var list []*mediamod.Media var hasNext bool if topic.ID == dramatopic.IDEveryone { list, err = listEveryoneLikes(req.Skip64(), req.Limit64()+1) if err != nil { return TopicWorksResponse{}, err } hasNext = len(list) > int(req.PageSize) if hasNext { list = list[:req.PageSize] } } else { sortBy := bson.D{{Key: "sortCode", Value: -1}, {Key: "latestPublishedAt", Value: -1}, {Key: "_id", Value: -1}} if topic.ID == dramatopic.IDLatest { sortBy = bson.D{{Key: "latestPublishedAt", Value: -1}, {Key: "createdAt", Value: -1}, {Key: "_id", Value: -1}} } list, err = mediamod.QueryAllList(filter, options.Find().SetSkip(req.Skip64()).SetLimit(req.Limit64()+1).SetSort(sortBy)) if err != nil { return TopicWorksResponse{}, err } hasNext = len(list) > int(req.PageSize) if hasNext { list = list[:req.PageSize] } } return TopicWorksResponse{ Topic: TopicSummary{TopicID: topic.ID, Name: topic.Name, TopicType: dramatopic.TypeSystem, SystemKey: topic.SystemKey, Sort: topic.Sort, WorkCount: total}, Total: total, HasNext: hasNext, List: fillTopicMedias(list, uid), }, nil } func fillTopicMedias(list []*mediamod.Media, uid uint64) []*mediamod.AppMediaBase { filled := mediaser.FillMedias(list, uid, true) if filled == nil { return []*mediamod.AppMediaBase{} } return filled } func listEveryoneLikes(skip, limit int64) ([]*mediamod.Media, error) { key := fmt.Sprintf("drama:topic:everyone-likes:v1:%d:%d", skip, limit) if appg.Redis != nil { cached, err := appg.Redis.Get(key) if err == nil && cached != nil { var list []*mediamod.Media if jsonErr := json.Unmarshal([]byte(*cached), &list); jsonErr == nil { return list, nil } } } list, err := mediamod.ListActiveDramaByInteractionScore(skip, limit) if err != nil { return nil, err } if appg.Redis != nil { if data, marshalErr := json.Marshal(list); marshalErr == nil { if cacheErr := appg.Redis.Set(key, data, everyoneLikesCacheTTL); cacheErr != nil { log.Warn("缓存大家爱看短剧列表失败", log.E(cacheErr)) } } } return list, nil } func activeDramaFilter() bson.M { return bson.M{"mediaType": mediamod.MediaTypeDrama, "status": 1, "isDelete": false} } func valueOrZero(value *int) int { if value == nil { return 0 } return *value }