356 lines
10 KiB
Go
356 lines
10 KiB
Go
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
|
|
}
|