package srv_playlet import ( "context" "strings" "time" "videoweb/pub/common/playletcache" "videoweb/pub/common/srvdef" "videoweb/pub/common/utils" "videoweb/pub/models/playletmod" "videoweb/pub/models/playlettagmod" "videoweb/pub/models/playletthememod" "videoweb/web/api/proto" "videoweb/web/service/srv_homeconfig" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) const ( syncSlugDefaultSize = int64(500) syncSlugMaxSize = int64(2000) ) func emptySlugFilter() bson.M { return bson.M{ "$or": []bson.M{ {"slug": ""}, {"slug": bson.M{"$exists": false}}, }, } } func ensureThemeSlug(ctx context.Context, ac *playletthememod.PlayletTheme, excludeId int64) *srvdef.SrvErr { ac.Slug = utils.SanitizeSlug(ac.Slug) if ac.Slug != "" { if utils.IsSlugAllDigits(ac.Slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } filter := bson.M{playletthememod.FN.PlayletTheme.Slug: ac.Slug} if excludeId > 0 { filter[playletthememod.FN.PlayletTheme.Id] = bson.M{"$ne": excludeId} } exist, _ := playletthememod.FindOnePlayletTheme(ctx, filter) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } return nil } source := utils.SlugSourceFromKeywordsOrTitle(ac.SeoInfo.Keyworld, ac.Name) ac.Slug = utils.MakeUniqueSlug(source, func(slug string) bool { filter := bson.M{playletthememod.FN.PlayletTheme.Slug: slug} if excludeId > 0 { filter[playletthememod.FN.PlayletTheme.Id] = bson.M{"$ne": excludeId} } exist, _ := playletthememod.FindOnePlayletTheme(ctx, filter) return exist != nil }) return nil } func prepareThemeSlugOnUpdate(ctx context.Context, id int64, info *playletthememod.PlayletTheme, req *proto.PlayletThemeUpdateReq, setDoc bson.M) *srvdef.SrvErr { slug := info.Slug if req.Slug != nil { slug = utils.SanitizeSlug(*req.Slug) } if slug != "" { if req.Slug != nil { if utils.IsSlugAllDigits(slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } exist, _ := playletthememod.FindOnePlayletTheme(ctx, bson.M{ playletthememod.FN.PlayletTheme.Slug: slug, playletthememod.FN.PlayletTheme.Id: bson.M{"$ne": id}, }) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } setDoc[playletthememod.FN.PlayletTheme.Slug] = slug } return nil } name := info.Name if req.Name != nil { name = strings.TrimSpace(*req.Name) } keywords := info.SeoInfo.Keyworld if req.SeoInfo != nil { keywords = normalizeSeoValue(*req.SeoInfo).Keyworld } source := utils.SlugSourceFromKeywordsOrTitle(keywords, name) newSlug := utils.MakeUniqueSlug(source, func(s string) bool { exist, _ := playletthememod.FindOnePlayletTheme(ctx, bson.M{ playletthememod.FN.PlayletTheme.Slug: s, playletthememod.FN.PlayletTheme.Id: bson.M{"$ne": id}, }) return exist != nil }) setDoc[playletthememod.FN.PlayletTheme.Slug] = newSlug return nil } func ensureTagSlug(ctx context.Context, ac *playlettagmod.PlayletTag, excludeId int64) *srvdef.SrvErr { ac.Slug = utils.SanitizeSlug(ac.Slug) if ac.Slug != "" { if utils.IsSlugAllDigits(ac.Slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } filter := bson.M{playlettagmod.FN.PlayletTag.Slug: ac.Slug} if excludeId > 0 { filter[playlettagmod.FN.PlayletTag.Id] = bson.M{"$ne": excludeId} } exist, _ := playlettagmod.FindOnePlayletTag(ctx, filter) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } return nil } source := utils.SlugSourceFromKeywordsOrTitle(ac.SeoInfo.Keyworld, ac.Name) ac.Slug = utils.MakeUniqueSlug(source, func(slug string) bool { filter := bson.M{playlettagmod.FN.PlayletTag.Slug: slug} if excludeId > 0 { filter[playlettagmod.FN.PlayletTag.Id] = bson.M{"$ne": excludeId} } exist, _ := playlettagmod.FindOnePlayletTag(ctx, filter) return exist != nil }) return nil } func prepareTagSlugOnUpdate(ctx context.Context, id int64, info *playlettagmod.PlayletTag, req *proto.PlayletTagUpdateReq, setDoc bson.M) *srvdef.SrvErr { slug := info.Slug if req.Slug != nil { slug = utils.SanitizeSlug(*req.Slug) } if slug != "" { if req.Slug != nil { if utils.IsSlugAllDigits(slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } exist, _ := playlettagmod.FindOnePlayletTag(ctx, bson.M{ playlettagmod.FN.PlayletTag.Slug: slug, playlettagmod.FN.PlayletTag.Id: bson.M{"$ne": id}, }) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } setDoc[playlettagmod.FN.PlayletTag.Slug] = slug } return nil } name := info.Name if req.Name != nil { name = strings.TrimSpace(*req.Name) } keywords := info.SeoInfo.Keyworld if req.SeoInfo != nil { keywords = normalizeSeoValue(*req.SeoInfo).Keyworld } source := utils.SlugSourceFromKeywordsOrTitle(keywords, name) newSlug := utils.MakeUniqueSlug(source, func(s string) bool { exist, _ := playlettagmod.FindOnePlayletTag(ctx, bson.M{ playlettagmod.FN.PlayletTag.Slug: s, playlettagmod.FN.PlayletTag.Id: bson.M{"$ne": id}, }) return exist != nil }) setDoc[playlettagmod.FN.PlayletTag.Slug] = newSlug return nil } func ensurePlayletSlug(ctx context.Context, ac *playletmod.Playlet, excludeId int64) *srvdef.SrvErr { ac.Slug = utils.SanitizeSlug(ac.Slug) if ac.Slug != "" { if utils.IsSlugAllDigits(ac.Slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } filter := bson.M{playletmod.FN.Playlet.Slug: ac.Slug} if excludeId > 0 { filter[playletmod.FN.Playlet.Id] = bson.M{"$ne": excludeId} } exist, _ := playletmod.FindOnePlaylet(ctx, filter) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } return nil } source := utils.SlugSourceFromKeywordsOrTitle(ac.SeoInfo.Keyworld, ac.Title) ac.Slug = utils.MakeUniqueSlug(source, func(slug string) bool { filter := bson.M{playletmod.FN.Playlet.Slug: slug} if excludeId > 0 { filter[playletmod.FN.Playlet.Id] = bson.M{"$ne": excludeId} } exist, _ := playletmod.FindOnePlaylet(ctx, filter) return exist != nil }) return nil } func preparePlayletSlugOnUpdate(ctx context.Context, id int64, info *playletmod.Playlet, req *proto.PlayletUpdateReq, setDoc bson.M) *srvdef.SrvErr { slug := info.Slug if req.Slug != nil { slug = utils.SanitizeSlug(*req.Slug) } if slug != "" { if req.Slug != nil { if utils.IsSlugAllDigits(slug) { return srvdef.New(srvdef.ErrParamError, "Slug不能全是数字", "Slug不能全是数字") } exist, _ := playletmod.FindOnePlaylet(ctx, bson.M{ playletmod.FN.Playlet.Slug: slug, playletmod.FN.Playlet.Id: bson.M{"$ne": id}, }) if exist != nil { return srvdef.New(srvdef.ErrDbInputExist, "Slug已存在", "Slug已存在") } setDoc[playletmod.FN.Playlet.Slug] = slug } return nil } title := info.Title if req.Title != nil { title = strings.TrimSpace(*req.Title) } keywords := info.SeoInfo.Keyworld if req.SeoInfo != nil { keywords = normalizeSeoValue(*req.SeoInfo).Keyworld } source := utils.SlugSourceFromKeywordsOrTitle(keywords, title) newSlug := utils.MakeUniqueSlug(source, func(s string) bool { exist, _ := playletmod.FindOnePlaylet(ctx, bson.M{ playletmod.FN.Playlet.Slug: s, playletmod.FN.Playlet.Id: bson.M{"$ne": id}, }) return exist != nil }) setDoc[playletmod.FN.Playlet.Slug] = newSlug return nil } // SyncEmptySlugs 为题材、标签、短剧中空 slug 自动生成。可重复调用。 func SyncEmptySlugs(ctx context.Context, req *proto.PlayletSlugSyncReq) (*proto.PlayletSlugSyncResp, *srvdef.SrvErr) { maxSize := syncSlugDefaultSize if req != nil && req.MaxSize > 0 { maxSize = req.MaxSize } if maxSize > syncSlugMaxSize { maxSize = syncSlugMaxSize } resp := &proto.PlayletSlugSyncResp{} remain := maxSize themeUpdated, themeMore, err := syncThemeEmptySlugs(ctx, remain) if err != nil { return nil, err } resp.ThemeUpdated = themeUpdated remain -= themeUpdated if remain <= 0 { resp.HasMore = themeMore || hasEmptySlug(ctx, "tag") || hasEmptySlug(ctx, "playlet") || srv_homeconfig.HasEmptySlug(ctx) playletcache.ClearHomeCache() return resp, nil } tagUpdated, tagMore, err := syncTagEmptySlugs(ctx, remain) if err != nil { return nil, err } resp.TagUpdated = tagUpdated remain -= tagUpdated if remain <= 0 { resp.HasMore = tagMore || hasEmptySlug(ctx, "playlet") || srv_homeconfig.HasEmptySlug(ctx) playletcache.ClearHomeCache() return resp, nil } playletUpdated, playletMore, err := syncPlayletEmptySlugs(ctx, remain) if err != nil { return nil, err } resp.PlayletUpdated = playletUpdated remain -= playletUpdated if remain <= 0 { resp.HasMore = playletMore || srv_homeconfig.HasEmptySlug(ctx) playletcache.ClearHomeCache() return resp, nil } homeUpdated, homeMore, err := srv_homeconfig.SyncEmptySlugs(ctx, remain) if err != nil { return nil, err } resp.HomeConfigUpdated = homeUpdated resp.HasMore = playletMore || homeMore playletcache.ClearHomeCache() return resp, nil } func hasEmptySlug(ctx context.Context, kind string) bool { filter := emptySlugFilter() switch kind { case "theme": count, _ := playletthememod.CountPlayletTheme(ctx, filter) return count > 0 case "tag": count, _ := playlettagmod.CountPlayletTag(ctx, filter) return count > 0 case "playlet": count, _ := playletmod.CountPlaylet(ctx, filter) return count > 0 default: return false } } func syncThemeEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playletthememod.FindManyPlayletTheme(ctx, emptySlugFilter(), options.Find(). SetSort(bson.D{{Key: playletthememod.FN.PlayletTheme.Id, Value: 1}}). SetLimit(maxSize+1)) if err != nil { return 0, false, err } hasMore := int64(len(list)) > maxSize if hasMore { list = list[:maxSize] } var updated int64 now := time.Now() for _, item := range list { if item == nil { continue } if serr := ensureThemeSlug(ctx, item, item.Id); serr != nil { return updated, hasMore, serr } if err := playletthememod.UpdateOnePlayletTheme(ctx, bson.M{playletthememod.FN.PlayletTheme.Id: item.Id}, bson.M{"$set": bson.M{ playletthememod.FN.PlayletTheme.Slug: item.Slug, playletthememod.FN.PlayletTheme.UpdatedAt: now, }}, ); err != nil { return updated, hasMore, err } updated++ } return updated, hasMore, nil } func syncTagEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playlettagmod.FindManyPlayletTag(ctx, emptySlugFilter(), options.Find(). SetSort(bson.D{{Key: playlettagmod.FN.PlayletTag.Id, Value: 1}}). SetLimit(maxSize+1)) if err != nil { return 0, false, err } hasMore := int64(len(list)) > maxSize if hasMore { list = list[:maxSize] } var updated int64 now := time.Now() for _, item := range list { if item == nil { continue } if serr := ensureTagSlug(ctx, item, item.Id); serr != nil { return updated, hasMore, serr } if err := playlettagmod.UpdateOnePlayletTag(ctx, bson.M{playlettagmod.FN.PlayletTag.Id: item.Id}, bson.M{"$set": bson.M{ playlettagmod.FN.PlayletTag.Slug: item.Slug, playlettagmod.FN.PlayletTag.UpdatedAt: now, }}, ); err != nil { return updated, hasMore, err } updated++ } return updated, hasMore, nil } func syncPlayletEmptySlugs(ctx context.Context, maxSize int64) (int64, bool, *srvdef.SrvErr) { list, err := playletmod.FindManyPlaylet(ctx, emptySlugFilter(), options.Find(). SetSort(bson.D{{Key: playletmod.FN.Playlet.Id, Value: 1}}). SetLimit(maxSize+1)) if err != nil { return 0, false, err } hasMore := int64(len(list)) > maxSize if hasMore { list = list[:maxSize] } var updated int64 now := time.Now() for _, item := range list { if item == nil { continue } if serr := ensurePlayletSlug(ctx, item, item.Id); serr != nil { return updated, hasMore, serr } if err := playletmod.UpdateOnePlaylet(ctx, bson.M{playletmod.FN.Playlet.Id: item.Id}, bson.M{"$set": bson.M{ playletmod.FN.Playlet.Slug: item.Slug, playletmod.FN.Playlet.UpdatedAt: now, }}, ); err != nil { return updated, hasMore, err } updated++ } return updated, hasMore, nil }