Initial commit: web_hghl_server
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
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)
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user