package srv_aicomment import ( "context" "fmt" "strconv" "strings" "sync/atomic" "videoweb/pub/common/log" "videoweb/pub/common/srvdef" "videoweb/pub/models/playletmod" "videoweb/pub/models/playlettagmod" "videoweb/pub/models/playletthememod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) const ( homepagePlayletCandidateLimit int64 = 60 homepagePlayletFeedThemeLimit int64 = 30 maxSubmitRequestsPerRun = 7 ) var homepagePlayletProjection = bson.D{ {Key: playletmod.FN.Playlet.Id, Value: 1}, {Key: playletmod.FN.Playlet.Title, Value: 1}, {Key: playletmod.FN.Playlet.Desc, Value: 1}, {Key: playletmod.FN.Playlet.TagIds, Value: 1}, {Key: playletmod.FN.Playlet.OnlineAt, Value: 1}, {Key: playletmod.FN.Playlet.CreatedAt, Value: 1}, {Key: playletmod.FN.Playlet.IsFeatured, Value: 1}, {Key: playletmod.FN.Playlet.IsRecommend, Value: 1}, {Key: playletmod.FN.Playlet.ShowOnBanner, Value: 1}, {Key: playletmod.FN.Playlet.TopRank, Value: 1}, {Key: playletmod.FN.Playlet.RecommendRank, Value: 1}, {Key: playletmod.FN.Playlet.CountBrowse, Value: 1}, {Key: playletmod.FN.Playlet.CountComment, Value: 1}, } var homepageSubmitRunning atomic.Bool func AutoProcessHomepageVisibleContent(ctx context.Context) error { if !homepageSubmitRunning.CompareAndSwap(false, true) { log.Debug("首页内容自动评论补提交正在执行中,本次跳过") return nil } defer homepageSubmitRunning.Store(false) service := GetAiCommentService() if !service.Configured() { log.Warn("自动评论服务未配置,跳过首页内容补提交") return nil } playlets, serr := collectHomepagePlaylets(ctx) if serr != nil { return fmt.Errorf("查询首页短剧失败: %w", serr) } videos, serr := buildHomepagePlayletVideoItems(ctx, playlets) if serr != nil { return fmt.Errorf("组装首页短剧AI评论参数失败: %w", serr) } if err := submitAndRecordInBatches(ctx, service, videos); err != nil { return fmt.Errorf("提交首页短剧AI评论任务失败: %w", err) } log.Info("自动评论首页内容补提交完成", log.Any("playletCandidates", len(playlets)), log.Any("playletPending", len(videos)), ) return nil } func submitAndRecordInBatches(ctx context.Context, service *AiCommentService, videos []VideoItem) error { batches, deferred := buildAICommentSubmissionBatches(videos, maxSubmitRequestsPerRun) for _, batch := range batches { if err := service.SubmitAndRecord(ctx, batch); err != nil { return err } } if deferred { log.Info("自动评论达到单轮提交请求上限,剩余任务等待下次续传", log.Any("requestLimit", maxSubmitRequestsPerRun), log.Any("videoCount", len(videos))) } return nil } func buildAICommentSubmissionBatches(videos []VideoItem, requestLimit int) ([][]VideoItem, bool) { batches := make([][]VideoItem, 0) for _, round := range buildAICommentSubmissionRounds(videos) { for start := 0; start < len(round); start += maxSubmitVideoCount { end := start + maxSubmitVideoCount if end > len(round) { end = len(round) } batches = append(batches, round[start:end]) } } if requestLimit <= 0 || len(batches) <= requestLimit { return batches, false } return batches[:requestLimit], true } func buildAICommentSubmissionRounds(videos []VideoItem) [][]VideoItem { pending := append([]VideoItem(nil), videos...) rounds := make([][]VideoItem, 0) for len(pending) > 0 { round := make([]VideoItem, 0, len(pending)) next := make([]VideoItem, 0, len(pending)) for _, video := range pending { remaining := video.CommentTotal if remaining <= 0 { remaining = commentsPerGenerationTask } retainCount := min(remaining, commentsPerGenerationTask) taskVideo := video taskVideo.CommentCount = randomCommentLengthLimit() taskVideo.RetainCount = retainCount taskVideo.CommentTotal = 0 round = append(round, taskVideo) remaining -= retainCount if remaining > 0 { video.CommentTotal = remaining next = append(next, video) } } rounds = append(rounds, round) pending = next } return rounds } func collectHomepagePlaylets(ctx context.Context) (playletmod.PlayletList, *srvdef.SrvErr) { playlets := make(playletmod.PlayletList, 0, maxSubmitVideoCount) seen := make(map[int64]struct{}) if serr := appendHomepagePlaylets(ctx, &playlets, seen, homepagePlayletFilter(bson.M{playletmod.FN.Playlet.ShowOnBanner: true}), bson.D{ {Key: playletmod.FN.Playlet.TopRank, Value: -1}, {Key: playletmod.FN.Playlet.MediaUpdatedAt, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, 6, ); serr != nil { return nil, serr } if serr := appendHomepageFeedThemePlaylets(ctx, &playlets, seen); serr != nil { return nil, serr } queries := []struct { filter bson.M sort bson.D limit int64 }{ { filter: homepagePlayletFilter(bson.M{playletmod.FN.Playlet.IsRecommend: true}), sort: bson.D{ {Key: playletmod.FN.Playlet.RecommendRank, Value: -1}, {Key: playletmod.FN.Playlet.CreatedAt, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, limit: homepagePlayletCandidateLimit, }, { filter: homepagePlayletFilter(nil), sort: bson.D{ {Key: playletmod.FN.Playlet.RecommendRank, Value: -1}, {Key: playletmod.FN.Playlet.MediaUpdatedAt, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, limit: homepagePlayletCandidateLimit, }, { filter: homepagePlayletFilter(nil), sort: bson.D{ {Key: playletmod.FN.Playlet.CountCollect, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, limit: homepagePlayletCandidateLimit, }, { filter: homepagePlayletFilter(nil), sort: bson.D{ {Key: playletmod.FN.Playlet.UpdatedAt, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, limit: homepagePlayletCandidateLimit, }, { filter: homepagePlayletFilter(nil), sort: bson.D{ {Key: playletmod.FN.Playlet.CountBrowse, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, }, limit: homepagePlayletCandidateLimit, }, } for _, query := range queries { if serr := appendHomepagePlaylets(ctx, &playlets, seen, query.filter, query.sort, query.limit); serr != nil { return nil, serr } } return playlets, nil } func appendHomepageFeedThemePlaylets(ctx context.Context, playlets *playletmod.PlayletList, seen map[int64]struct{}) *srvdef.SrvErr { themes, serr := playletthememod.FindManyPlayletTheme(ctx, bson.M{ playletthememod.FN.PlayletTheme.Status: true, }, options.Find(). SetProjection(bson.D{{Key: playletthememod.FN.PlayletTheme.Id, Value: 1}}). SetSort(bson.D{ {Key: playletthememod.FN.PlayletTheme.Rank, Value: -1}, {Key: playletthememod.FN.PlayletTheme.Id, Value: -1}, }), ) if serr != nil { return serr } sort := bson.D{ {Key: playletmod.FN.Playlet.RecommendRank, Value: -1}, {Key: playletmod.FN.Playlet.MediaUpdatedAt, Value: -1}, {Key: playletmod.FN.Playlet.Id, Value: -1}, } for _, theme := range themes { if theme == nil || theme.Id <= 0 { continue } filter := homepagePlayletFilter(bson.M{ playletmod.FN.Playlet.ThemeIds: bson.M{"$in": []int64{theme.Id}}, }) if serr := appendHomepagePlaylets(ctx, playlets, seen, filter, sort, homepagePlayletFeedThemeLimit); serr != nil { return serr } } return nil } func appendHomepagePlaylets(ctx context.Context, playlets *playletmod.PlayletList, seen map[int64]struct{}, filter bson.M, sort bson.D, limit int64) *srvdef.SrvErr { list, serr := playletmod.FindManyPlaylet(ctx, filter, options.Find(). SetProjection(homepagePlayletProjection). SetSort(sort). SetLimit(limit), ) if serr != nil { return serr } for _, playlet := range list { if playlet == nil || playlet.Id <= 0 { continue } if _, ok := seen[playlet.Id]; ok { continue } seen[playlet.Id] = struct{}{} *playlets = append(*playlets, playlet) } return nil } func buildHomepagePlayletVideoItems(ctx context.Context, playlets playletmod.PlayletList) ([]VideoItem, *srvdef.SrvErr) { tagNames := loadPlayletTagNames(ctx, playlets) videos := make([]VideoItem, 0, len(playlets)) for _, playlet := range playlets { if playlet == nil { continue } videos = append(videos, VideoItem{ VideoId: strconv.FormatInt(playlet.Id, 10), Title: playlet.Title, Tags: playletTagNames(playlet.TagIds, tagNames), Description: playlet.Desc, CommentCount: randomCommentLengthLimit(), ObjType: objectTypePlaylet, CommentTotal: aiCommentPlayletTotal, CurrentCount: playlet.CountComment, }) } pending, serr := preparePendingAICommentVideos(ctx, videos) if serr != nil { return nil, serr } return pending, nil } func homepagePlayletFilter(extra bson.M) bson.M { filter := bson.M{playletmod.FN.Playlet.Status: playletmod.StatusUp} for key, value := range extra { filter[key] = value } return filter } func loadPlayletTagNames(ctx context.Context, playlets playletmod.PlayletList) map[int64]string { tagIDs := make([]int64, 0) seen := make(map[int64]struct{}) for _, playlet := range playlets { if playlet == nil { continue } for _, tagID := range playlet.TagIds { if tagID <= 0 { continue } if _, ok := seen[tagID]; ok { continue } seen[tagID] = struct{}{} tagIDs = append(tagIDs, tagID) } } if len(tagIDs) == 0 { return map[int64]string{} } tags, serr := playlettagmod.FindManyPlayletTag(ctx, bson.M{ playlettagmod.FN.PlayletTag.Id: bson.M{"$in": tagIDs}, }, options.Find().SetProjection(bson.D{ {Key: playlettagmod.FN.PlayletTag.Id, Value: 1}, {Key: playlettagmod.FN.PlayletTag.Name, Value: 1}, })) if serr != nil { log.Warn("自动评论查询短剧标签失败", log.E(serr), log.Any("tagIDs", tagIDs)) return map[int64]string{} } result := make(map[int64]string, len(tags)) for _, tag := range tags { if tag != nil { result[tag.Id] = tag.Name } } return result } func playletTagNames(tagIDs []int64, tagNames map[int64]string) []string { result := make([]string, 0, len(tagIDs)) seen := make(map[string]struct{}, len(tagIDs)) for _, tagID := range tagIDs { name := strings.TrimSpace(tagNames[tagID]) if name == "" { continue } if _, ok := seen[name]; ok { continue } seen[name] = struct{}{} result = append(result, name) if len(result) >= maxVideoTags { break } } return result }