短剧 SEO 描述改为「自定义 + 自动模板」拼接

- SeoInfo.Description 现在只存管理员纯自定义文本(不再被覆盖)
- Playlet.Description 存合并后的展示值: {自定义} + 黄豆短剧《X》全集在线观看,
  已更新至第N集,免费高清持续更新。前台 buildPlayletDetailSeoInfo 读这个。
- mergePlayletSeoDescription 处理重复、历史尾部剥离、空值回退
- stripPlayletSeoAutoTemplate 用于把 merged 字符串回写成纯自定义,保证
  老库里把模板当自定义存的记录在下次保存时自动迁移到新结构。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 17:21:04 +08:00
co-authored by Claude Opus 4.7
parent 2f8b0918e6
commit bf623eaea4
+60 -8
View File
@@ -1613,16 +1613,66 @@ func normalizeSeoValue(seo pubdef.SeoValue) pubdef.SeoValue {
return seo
}
// mergePlayletSeoDescription 把管理员自定义的 SEO 描述和自动模板拼在一起。
// 规则:
// - custom 为空 → 返回 autoDesc
// - custom 已经包含当前版本的 autoDesc → 直接返回 custom,不重复追加
// - custom 结尾带有旧版本的「黄豆短剧《…》…免费高清持续更新。」模板
// (集数/剧名变更后产生的历史尾部) → 剥离旧尾部再拼接新模板
// - 其它情况 → custom + autoDesc
func mergePlayletSeoDescription(custom, autoDesc string) string {
custom = strings.TrimSpace(custom)
autoDesc = strings.TrimSpace(autoDesc)
if custom == "" {
return autoDesc
}
if autoDesc == "" {
return custom
}
if strings.Contains(custom, autoDesc) {
return custom
}
if idx := strings.LastIndex(custom, "黄豆短剧《"); idx >= 0 && strings.HasSuffix(custom, "免费高清持续更新。") {
prefix := strings.TrimSpace(custom[:idx])
if prefix == "" {
return autoDesc
}
return prefix + autoDesc
}
return custom + autoDesc
}
// stripPlayletSeoAutoTemplate 从 SeoInfo.Description 中剥离自动模板部分,只保留管理员自定义。
// 用于把展示用的 merged 字符串回写成「纯自定义」存库。
func stripPlayletSeoAutoTemplate(merged, autoDesc string) string {
merged = strings.TrimSpace(merged)
autoDesc = strings.TrimSpace(autoDesc)
if merged == "" || autoDesc == "" {
return merged
}
if merged == autoDesc {
return ""
}
if strings.HasSuffix(merged, autoDesc) {
return strings.TrimSpace(strings.TrimSuffix(merged, autoDesc))
}
if idx := strings.LastIndex(merged, "黄豆短剧《"); idx >= 0 && strings.HasSuffix(merged, "免费高清持续更新。") {
return strings.TrimSpace(merged[:idx])
}
return merged
}
func applyPlayletDescription(doc *playletmod.Playlet) {
if doc == nil {
return
}
description := playletmod.BuildPlayletDescription(doc.PlayName, doc.Title, doc.TotalEpisode)
if description == "" {
autoDesc := playletmod.BuildPlayletDescription(doc.PlayName, doc.Title, doc.TotalEpisode)
if autoDesc == "" {
return
}
doc.Description = description
doc.SeoInfo.Description = description
// SeoInfo.Description 存纯自定义(用户没传时为空);Playlet.Description 存合并后的展示值。
doc.SeoInfo.Description = stripPlayletSeoAutoTemplate(doc.SeoInfo.Description, autoDesc)
doc.Description = mergePlayletSeoDescription(doc.SeoInfo.Description, autoDesc)
}
func applyPlayletDescriptionOnUpdate(exist *playletmod.Playlet, req *proto.PlayletUpdateReq, setDoc bson.M) {
@@ -1641,11 +1691,10 @@ func applyPlayletDescriptionOnUpdate(exist *playletmod.Playlet, req *proto.Playl
if req.TotalEpisode != nil {
totalEpisode = *req.TotalEpisode
}
description := playletmod.BuildPlayletDescription(playName, title, totalEpisode)
if description == "" {
autoDesc := playletmod.BuildPlayletDescription(playName, title, totalEpisode)
if autoDesc == "" {
return
}
setDoc[playletmod.FN.Playlet.Description] = description
seo := exist.SeoInfo
if req.SeoInfo != nil {
if current, ok := setDoc["seoInfo"].(pubdef.SeoValue); ok {
@@ -1655,8 +1704,11 @@ func applyPlayletDescriptionOnUpdate(exist *playletmod.Playlet, req *proto.Playl
}
}
seo = normalizeSeoValue(seo)
seo.Description = description
// 剥离历史/当前版本的自动模板,仅保留纯自定义部分落到 SeoInfo.Description。
seo.Description = stripPlayletSeoAutoTemplate(seo.Description, autoDesc)
setDoc["seoInfo"] = seo
// Playlet.Description = 自定义 + 自动模板,供前台 SEO 页面渲染。
setDoc[playletmod.FN.Playlet.Description] = mergePlayletSeoDescription(seo.Description, autoDesc)
}
func playletToWebResp(item *playletmod.Playlet, themeNameMap map[int64]string, tagNameMap map[int64]string, categoryId *int64) *proto.PlayletListResp {