package playletthemeser import ( "context" "strings" "videoweb/app/api/proto" "videoweb/app/service/configser" "videoweb/app/service/playletcache" "videoweb/pub/common/netutil" "videoweb/pub/common/srvdef" "videoweb/pub/common/utils" "videoweb/pub/pubdef" ) // List 查询 app 端短剧题材列表(读题材缓存,含 SEO) func List(ctx context.Context) (*proto.PlayletThemeListResponse, *srvdef.SrvErr) { list := playletcache.ListEnabledThemes() imgCdn := configser.GetAppImageCdn(configser.ClientIPFromCtx(ctx)) items := make([]*proto.PlayletThemeItem, 0, len(list)) for _, item := range list { if item == nil { continue } items = append(items, &proto.PlayletThemeItem{ Id: item.Id, Name: item.Name, Slug: item.Slug, Icon: mergeThemeIcon(imgCdn, item.Icon), Rank: item.Rank, Status: item.Status, IsRecommend: item.IsRecommend, PlayletCount: item.PlayletCount, SeoInfo: buildThemeSeoInfo(item), }) } return &proto.PlayletThemeListResponse{List: items}, nil } // buildThemeSeoInfo 将题材后台 SEO 转为页面 SeoInfo,缺失字段回退题材名/站点默认。 func buildThemeSeoInfo(item *playletcache.ThemeItem) *proto.SeoInfo { seo := configser.GetSiteSeoInfo() if item == nil { return &seo } custom := normalizeThemeSeoValue(item.SeoInfo) if title := strings.TrimSpace(custom.Title); title != "" { seo.Title = title } else if name := strings.TrimSpace(item.Name); name != "" { seo.Title = name } if desc := strings.TrimSpace(custom.Description); desc != "" { seo.Description = desc } if len(custom.Keyworld) > 0 { seo.Keywords = append([]string{}, custom.Keyworld...) } seo.CanonicalURL = joinCanonicalURL(seo.CanonicalURL, buildThemeCanonicalPath(item.Slug, item.Id)) return &seo } // buildThemeCanonicalPath 专题详情页规范相对路径(对齐 sitemap /topic/[slug]/,前端 /list?themeId=X 会被 301)。 func buildThemeCanonicalPath(slug string, id int64) string { if segment := utils.PathSegment(slug, id); segment != "" { return "/topic/" + segment + "/" } return "/topic/" } func normalizeThemeSeoValue(seo pubdef.SeoValue) pubdef.SeoValue { seo.Title = strings.TrimSpace(seo.Title) seo.Description = strings.TrimSpace(seo.Description) if seo.Keyworld == nil { seo.Keyworld = make([]string, 0) return seo } words := make([]string, 0, len(seo.Keyworld)) for _, word := range seo.Keyworld { word = strings.TrimSpace(word) if word == "" { continue } words = append(words, word) } seo.Keyworld = words return seo } func joinCanonicalURL(baseURL, pagePath string) string { pagePath = strings.TrimSpace(pagePath) if pagePath == "" { return strings.TrimSpace(baseURL) } if !strings.HasPrefix(pagePath, "/") { pagePath = "/" + pagePath } baseURL = strings.TrimSpace(baseURL) if baseURL == "" { return pagePath } return strings.TrimRight(baseURL, "/") + pagePath } // mergeThemeIcon 给题材图标拼接图片域名 func mergeThemeIcon(imgCdn string, icon string) string { icon = strings.TrimSpace(icon) if icon == "" { return "" } if strings.HasPrefix(icon, "http://") || strings.HasPrefix(icon, "https://") { return icon } if imgCdn == "" { return icon } return netutil.MergeURL(imgCdn, icon) }