Initial commit: web_hghl_server

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 16:28:14 +08:00
co-authored by Claude Opus 4.7
commit 65fca60622
653 changed files with 135901 additions and 0 deletions
+403
View File
@@ -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)
},
)
}