package playletser import ( "fmt" "net/url" "path" "strings" "sync" "time" "videoweb/app/service/upstream" "videoweb/pub/common/log" "github.com/gin-gonic/gin" ) const homePath = "/api/app/playlet/home" const homeCacheTTL = 30 * time.Second var ( homeMu sync.Mutex homeCache *HomePlayletResponse homeAt time.Time ) // FetchHome 拉取首页短剧聚合数据。 func FetchHome(c *gin.Context) (*HomePlayletResponse, error) { var env APIEnvelope[HomePlayletResponse] if err := upstream.GetPublicJSON(c, homePath, &env); err != nil { return nil, err } if env.Code != 200 { return nil, fmt.Errorf("playlet home code=%d msg=%s", env.Code, env.Msg) } return &env.Data, nil } // FetchHomeOrEmpty 带短缓存;失败时打日志并返回空结构,保证 SSR 页面可渲染。 func FetchHomeOrEmpty(c *gin.Context) *HomePlayletResponse { homeMu.Lock() defer homeMu.Unlock() if homeCache != nil && time.Since(homeAt) < homeCacheTTL { return cloneHome(homeCache) } data, err := FetchHome(c) if err != nil { log.Error("fetch playlet home failed", log.E(err)) if homeCache != nil { return cloneHome(homeCache) } return &HomePlayletResponse{} } if data == nil { if homeCache != nil { return cloneHome(homeCache) } return &HomePlayletResponse{} } data.SeoInfo.Normalize() homeCache = data homeAt = time.Now() return cloneHome(homeCache) } func cloneHome(src *HomePlayletResponse) *HomePlayletResponse { if src == nil { return &HomePlayletResponse{} } out := *src if src.Announcements != nil { out.Announcements = append([]AnnouncementItem(nil), src.Announcements...) } if src.HomeBannerData != nil { out.HomeBannerData = append([]HomeBannerData(nil), src.HomeBannerData...) } if src.HomeTodayUpdateData != nil { out.HomeTodayUpdateData = append([]CommonData(nil), src.HomeTodayUpdateData...) } if src.HomeCategoryData != nil { out.HomeCategoryData = make([]HomeCategoryData, len(src.HomeCategoryData)) for i, cat := range src.HomeCategoryData { out.HomeCategoryData[i] = cat if cat.List != nil { out.HomeCategoryData[i].List = append([]CommonData(nil), cat.List...) } } } if src.HomeRecommendThemes != nil { out.HomeRecommendThemes = append([]HomeRecommendTheme(nil), src.HomeRecommendThemes...) } if src.SeoInfo.Keywords != nil { out.SeoInfo.Keywords = append([]string(nil), src.SeoInfo.Keywords...) } return &out } // ActiveHomeBanners 过滤未启用项,保留接口返回顺序。 func ActiveHomeBanners(list []HomeBannerData) []HomeBannerData { if len(list) == 0 { return list } out := make([]HomeBannerData, 0, len(list)) for _, item := range list { if !item.Status { continue } item.URL = normalizeBannerURL(item.URL) out = append(out, item) } return out } // normalizeBannerURL 站内页面链接补尾斜杠,避免后台漏填 "/" 时每次点击都多一次 301。 // 外链、协议相对地址、带扩展名的文件路径保持原样。 func normalizeBannerURL(raw string) string { raw = strings.TrimSpace(raw) if !strings.HasPrefix(raw, "/") || strings.HasPrefix(raw, "//") { return raw } u, err := url.Parse(raw) if err != nil || u.Path == "" || strings.HasSuffix(u.Path, "/") { return raw } if strings.Contains(path.Base(u.Path), ".") { return raw } u.Path += "/" return u.String() }