package srv_slug import ( "context" "strings" "time" "videoweb/pub/common/slugx" "videoweb/pub/common/srvdef" "videoweb/pub/common/utils" "videoweb/pub/models/actress" "videoweb/pub/models/contenttagmod" "videoweb/pub/models/playletcategorymod" "videoweb/pub/models/playletchaptermod" "videoweb/pub/models/playletmod" "videoweb/pub/models/playletthememod" "videoweb/pub/models/videocategorymod" "videoweb/pub/models/videocreatermod" "videoweb/pub/models/videoinformationmod" "videoweb/web/api/proto" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) const ( syncSlugDefaultSize = int64(500) syncSlugMaxSize = int64(2000) TypeVideo = "video" TypeVideoCategory = "videoCategory" TypeActress = "actress" TypeVideoCreater = "videoCreater" TypePlaylet = "playlet" TypePlayletCategory = "playletCategory" TypePlayletTheme = "playletTheme" TypeContentTag = "contentTag" ) type slugJob struct { kind string set func(*proto.SlugSyncResp, int64) run func(context.Context, int64) (int64, bool, *srvdef.SrvErr) hasMore func(context.Context) bool } // SyncEmptySlugs 为视频、分类、女优、作者、短剧、统一标签中空 slug 自动生成。可重复调用,hasMore 为 true 时继续请求。 func SyncEmptySlugs(ctx context.Context, req *proto.SlugSyncReq) (*proto.SlugSyncResp, *srvdef.SrvErr) { maxSize := syncSlugDefaultSize if req != nil && req.MaxSize > 0 { maxSize = req.MaxSize } if maxSize > syncSlugMaxSize { maxSize = syncSlugMaxSize } kind := "" if req != nil { kind = strings.TrimSpace(req.Type) } jobs := allSlugJobs() if kind != "" { job, ok := jobByKind(jobs, kind) if !ok { return nil, srvdef.New(srvdef.ErrParamError, "不支持的type", "不支持的type") } jobs = []slugJob{job} } resp := &proto.SlugSyncResp{} remain := maxSize for i, job := range jobs { if remain <= 0 { resp.HasMore = anyHasEmpty(ctx, jobs[i:]) resp.TotalUpdated = sumUpdated(resp) return resp, nil } updated, more, err := job.run(ctx, remain) if err != nil { return nil, err } job.set(resp, updated) remain -= updated if more { resp.HasMore = true resp.TotalUpdated = sumUpdated(resp) return resp, nil } } resp.TotalUpdated = sumUpdated(resp) return resp, nil } func allSlugJobs() []slugJob { return []slugJob{ {TypeVideoCategory, func(r *proto.SlugSyncResp, n int64) { r.VideoCategoryUpdated = n }, syncVideoCategoryEmptySlugs, hasVideoCategoryEmpty}, {TypePlayletCategory, func(r *proto.SlugSyncResp, n int64) { r.PlayletCategoryUpdated = n }, syncPlayletCategoryEmptySlugs, hasPlayletCategoryEmpty}, {TypePlayletTheme, func(r *proto.SlugSyncResp, n int64) { r.PlayletThemeUpdated = n }, syncPlayletThemeEmptySlugs, hasPlayletThemeEmpty}, {TypeContentTag, func(r *proto.SlugSyncResp, n int64) { r.ContentTagUpdated = n }, syncContentTagEmptySlugs, hasContentTagEmpty}, {TypeActress, func(r *proto.SlugSyncResp, n int64) { r.ActressUpdated = n }, syncActressEmptySlugs, hasActressEmpty}, {TypeVideoCreater, func(r *proto.SlugSyncResp, n int64) { r.VideoCreaterUpdated = n }, syncVideoCreaterEmptySlugs, hasVideoCreaterEmpty}, {TypePlaylet, func(r *proto.SlugSyncResp, n int64) { r.PlayletUpdated = n }, syncPlayletEmptySlugs, hasPlayletEmpty}, {TypeVideo, func(r *proto.SlugSyncResp, n int64) { r.VideoUpdated = n }, syncVideoEmptySlugs, hasVideoEmpty}, } } func jobByKind(jobs []slugJob, kind string) (slugJob, bool) { for _, job := range jobs { if job.kind == kind { return job, true } } return slugJob{}, false } func anyHasEmpty(ctx context.Context, jobs []slugJob) bool { for _, job := range jobs { if job.hasMore(ctx) { return true } } return false } func sumUpdated(resp *proto.SlugSyncResp) int64 { return resp.VideoUpdated + resp.VideoCategoryUpdated + resp.ActressUpdated + resp.VideoCreaterUpdated + resp.PlayletUpdated + resp.PlayletCategoryUpdated + resp.PlayletThemeUpdated + resp.ContentTagUpdated } func findLimit(maxSize int64) *options.FindOptions { return options.Find().SetSort(bson.D{{Key: "_id", Value: 1}}).SetLimit(maxSize + 1) } func fillEmptySlugs[T any]( ctx context.Context, list []T, maxSize int64, idOf func(T) int64, sourceOf func(T) string, exists func(slug string, excludeId int64) bool, update func(context.Context, int64, string) *srvdef.SrvErr, ) (int64, bool, *srvdef.SrvErr) { hasMore := int64(len(list)) > maxSize if hasMore { list = list[:maxSize] } var updated int64 for _, item := range list { if any(item) == nil { continue } id := idOf(item) if id <= 0 { continue } slug := "" if serr := slugx.Ensure(&slug, sourceOf(item), func(s string) bool { return exists(s, id) }); serr != nil { return updated, hasMore, serr } if serr := update(ctx, id, slug); serr != nil { return updated, hasMore, serr } updated++ } return updated, hasMore, nil } func setSlug(ctx context.Context, updateOne func(context.Context, bson.M, bson.M) *srvdef.SrvErr, idField, slugField, updatedAtField string, id int64, slug string) *srvdef.SrvErr { setDoc := bson.M{slugField: slug} if updatedAtField != "" { setDoc[updatedAtField] = time.Now() } return updateOne(ctx, bson.M{idField: id}, bson.M{"$set": setDoc}) } func videoEmptySlugFilter(ctx context.Context) bson.M { filter := slugx.EmptyFilter() playletIDs := playletchaptermod.ListLinkedMediaIDs(ctx) if len(playletIDs) == 0 { return filter } return bson.M{ "$and": []bson.M{ filter, { "$nor": []bson.M{ {videoinformationmod.FN.VideoInformation.SyncId: bson.M{"$in": playletIDs}}, {videoinformationmod.FN.VideoInformation.Video.FileId: bson.M{"$in": playletIDs}}, }, }, }, } } func hasVideoEmpty(ctx context.Context) bool { n, _ := videoinformationmod.CountVideoInformations(ctx, videoEmptySlugFilter(ctx)) return n > 0 } func hasVideoCategoryEmpty(ctx context.Context) bool { n, _ := videocategorymod.CountVideoCategorys(ctx, slugx.EmptyFilter()) return n > 0 } func hasActressEmpty(ctx context.Context) bool { n, _ := actress.CountActresss(ctx, slugx.EmptyFilter()) return n > 0 } func hasVideoCreaterEmpty(ctx context.Context) bool { n, _ := videocreatermod.CountVideoCreaterInfos(ctx, slugx.EmptyFilter()) return n > 0 } func hasPlayletEmpty(ctx context.Context) bool { n, _ := playletmod.CountPlaylet(ctx, slugx.EmptyFilter()) return n > 0 } func hasPlayletCategoryEmpty(ctx context.Context) bool { n, _ := playletcategorymod.CountPlayletCategory(ctx, slugx.EmptyFilter()) return n > 0 } func hasPlayletThemeEmpty(ctx context.Context) bool { n, _ := playletthememod.CountPlayletTheme(ctx, slugx.EmptyFilter()) return n > 0 } func hasContentTagEmpty(ctx context.Context) bool { n, _ := contenttagmod.CountContentTag(ctx, slugx.EmptyFilter()) return n > 0 } func syncVideoEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := videoinformationmod.FindManyVideoInformations(ctx, videoEmptySlugFilter(ctx), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*videoinformationmod.VideoInformation(list), maxSize, func(item *videoinformationmod.VideoInformation) int64 { return item.Id }, func(item *videoinformationmod.VideoInformation) string { return utils.SlugSourceFromKeywordsOrTitle(item.Keyworld, item.Title) }, func(s string, excludeId int64) bool { doc, _ := videoinformationmod.FindOneVideoInformation(ctx, slugx.Filter( videoinformationmod.FN.VideoInformation.Slug, videoinformationmod.FN.VideoInformation.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, videoinformationmod.UpdateOneVideoInformation, videoinformationmod.FN.VideoInformation.Id, videoinformationmod.FN.VideoInformation.Slug, videoinformationmod.FN.VideoInformation.UpdatedAt, id, slug) }, ) } func syncVideoCategoryEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := videocategorymod.FindManyVideoCategorys(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*videocategorymod.VideoCategory(list), maxSize, func(item *videocategorymod.VideoCategory) int64 { return item.Id }, func(item *videocategorymod.VideoCategory) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Name) }, func(s string, excludeId int64) bool { doc, _ := videocategorymod.FindOneVideoCategory(ctx, slugx.Filter( videocategorymod.FN.VideoCategory.Slug, videocategorymod.FN.VideoCategory.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, videocategorymod.UpdateOneVideoCategory, videocategorymod.FN.VideoCategory.Id, videocategorymod.FN.VideoCategory.Slug, videocategorymod.FN.VideoCategory.UpdatedAt, id, slug) }, ) } func syncActressEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := actress.FindManyActresss(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*actress.Actress(list), maxSize, func(item *actress.Actress) int64 { return item.Id }, func(item *actress.Actress) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Alias) }, func(s string, excludeId int64) bool { doc, _ := actress.FindOneActress(ctx, slugx.Filter(actress.FN.Actress.Slug, actress.FN.Actress.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, actress.UpdateOneActress, actress.FN.Actress.Id, actress.FN.Actress.Slug, actress.FN.Actress.UpdatedAt, id, slug) }, ) } func syncVideoCreaterEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := videocreatermod.FindManyVideoCreaterInfos(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*videocreatermod.VideoCreaterInfo(list), maxSize, func(item *videocreatermod.VideoCreaterInfo) int64 { return item.Id }, func(item *videocreatermod.VideoCreaterInfo) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Name) }, func(s string, excludeId int64) bool { doc, _ := videocreatermod.FindOneVideoCreaterInfo(ctx, slugx.Filter( videocreatermod.FN.VideoCreaterInfo.Slug, videocreatermod.FN.VideoCreaterInfo.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, videocreatermod.UpdateOneVideoCreaterInfo, videocreatermod.FN.VideoCreaterInfo.Id, videocreatermod.FN.VideoCreaterInfo.Slug, videocreatermod.FN.VideoCreaterInfo.UpdatedAt, id, slug) }, ) } func syncPlayletEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playletmod.FindManyPlaylet(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*playletmod.Playlet(list), maxSize, func(item *playletmod.Playlet) int64 { return item.Id }, func(item *playletmod.Playlet) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Title) }, func(s string, excludeId int64) bool { doc, _ := playletmod.FindOnePlaylet(ctx, slugx.Filter(playletmod.FN.Playlet.Slug, playletmod.FN.Playlet.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, playletmod.UpdateOnePlaylet, playletmod.FN.Playlet.Id, playletmod.FN.Playlet.Slug, playletmod.FN.Playlet.UpdatedAt, id, slug) }, ) } func syncPlayletCategoryEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playletcategorymod.FindManyPlayletCategory(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*playletcategorymod.PlayletCategory(list), maxSize, func(item *playletcategorymod.PlayletCategory) int64 { return item.Id }, func(item *playletcategorymod.PlayletCategory) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Name) }, func(s string, excludeId int64) bool { doc, _ := playletcategorymod.FindOnePlayletCategory(ctx, slugx.Filter( playletcategorymod.FN.PlayletCategory.Slug, playletcategorymod.FN.PlayletCategory.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, playletcategorymod.UpdateOnePlayletCategory, playletcategorymod.FN.PlayletCategory.Id, playletcategorymod.FN.PlayletCategory.Slug, playletcategorymod.FN.PlayletCategory.UpdatedAt, id, slug) }, ) } func syncPlayletThemeEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playletthememod.FindManyPlayletTheme(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*playletthememod.PlayletTheme(list), maxSize, func(item *playletthememod.PlayletTheme) int64 { return item.Id }, func(item *playletthememod.PlayletTheme) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Name) }, func(s string, excludeId int64) bool { doc, _ := playletthememod.FindOnePlayletTheme(ctx, slugx.Filter( playletthememod.FN.PlayletTheme.Slug, playletthememod.FN.PlayletTheme.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, playletthememod.UpdateOnePlayletTheme, playletthememod.FN.PlayletTheme.Id, playletthememod.FN.PlayletTheme.Slug, playletthememod.FN.PlayletTheme.UpdatedAt, id, slug) }, ) } func syncContentTagEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := contenttagmod.FindManyContentTag(ctx, slugx.EmptyFilter(), findLimit(maxSize)) if err != nil { return 0, false, err } return fillEmptySlugs(ctx, []*contenttagmod.ContentTag(list), maxSize, func(item *contenttagmod.ContentTag) int64 { return item.Id }, func(item *contenttagmod.ContentTag) string { return utils.SlugSourceFromKeywordsOrTitle(item.SeoInfo.Keyworld, item.Name) }, func(s string, excludeId int64) bool { doc, _ := contenttagmod.FindOneContentTag(ctx, slugx.Filter( contenttagmod.FN.ContentTag.Slug, contenttagmod.FN.ContentTag.Id, s, excludeId)) return doc != nil }, func(ctx context.Context, id int64, slug string) *srvdef.SrvErr { return setSlug(ctx, contenttagmod.UpdateOneContentTag, contenttagmod.FN.ContentTag.Id, contenttagmod.FN.ContentTag.Slug, contenttagmod.FN.ContentTag.UpdatedAt, id, slug) }, ) }