package playletser import ( "fmt" "net/url" "strconv" "strings" "videoweb/app/service/authser" "videoweb/app/service/upstream" "videoweb/pub/common/log" "github.com/gin-gonic/gin" ) const ( detailPath = "/api/app/playlet/detail" defaultEpisodeRange = 30 ) // EpisodeRange 剧集分段(仅 UI 分页展示,非接口字段映射)。 type EpisodeRange struct { Start int End int Label string } // FetchDetail 根据短剧 ID 查询详情。 func FetchDetail(c *gin.Context, id int64) (*PlayletInfoResp, error) { if id <= 0 { return nil, fmt.Errorf("playlet detail invalid id") } return fetchDetail(c, "id", strconv.FormatInt(id, 10)) } // FetchDetailByKey 优先按 slug 查询;纯数字路由段按 ID 查询。 func FetchDetailByKey(c *gin.Context, key string) (*PlayletInfoResp, error) { key = strings.TrimSpace(key) if key == "" { return nil, fmt.Errorf("playlet detail missing route key") } if id := ParseDetailID(key); id > 0 { return FetchDetail(c, id) } return fetchDetail(c, "slug", key) } func fetchDetail(c *gin.Context, queryKey, value string) (*PlayletInfoResp, error) { authser.EnsureGuestOnEnter(c) env, err := fetchDetailOnce(c, queryKey, value) if err != nil { return nil, err } if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { if authser.RecoverContentSession(c, env.Code) { env, err = fetchDetailOnce(c, queryKey, value) if err != nil { return nil, err } } } if env.Code != 200 { return nil, fmt.Errorf("playlet detail code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } env.Data.SeoInfo.Normalize() return &env.Data, nil } func fetchDetailOnce(c *gin.Context, queryKey, value string) (*APIEnvelope[PlayletInfoResp], error) { params := url.Values{} params.Set(queryKey, value) var env APIEnvelope[PlayletInfoResp] if err := upstream.GetJSON(c, detailPath+"?"+params.Encode(), &env); err != nil { return nil, err } return &env, nil } // FetchDetailOrNilByKey 失败或无效时返回 nil。 func FetchDetailOrNilByKey(c *gin.Context, key string) *PlayletInfoResp { data, err := FetchDetailByKey(c, key) if err != nil { log.Error("fetch playlet detail by route key failed", log.E(err)) return nil } if data == nil || data.ID <= 0 { return nil } return data } // ParseDetailID 解析纯数字路由段。 func ParseDetailID(raw string) int64 { id, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) if err != nil || id <= 0 { return 0 } return id } // BuildEpisodeRanges 按集数分段(默认每段 30 集)。 func BuildEpisodeRanges(chapters []PlayletChapterData, totalEpisode, size int) []EpisodeRange { if size <= 0 { size = defaultEpisodeRange } maxEp := 0 for _, ch := range chapters { if ch.CurrentEpisode > maxEp { maxEp = ch.CurrentEpisode } } if totalEpisode > maxEp { maxEp = totalEpisode } if maxEp <= 0 { maxEp = len(chapters) } if maxEp <= 0 { return nil } out := make([]EpisodeRange, 0, (maxEp+size-1)/size) for start := 1; start <= maxEp; start += size { end := start + size - 1 if end > maxEp { end = maxEp } out = append(out, EpisodeRange{ Start: start, End: end, Label: fmt.Sprintf("%d–%d", start, end), }) } return out }