219 lines
5.7 KiB
Go
219 lines
5.7 KiB
Go
package playletser
|
||
|
||
import (
|
||
"fmt"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"videoweb/app/service/upstream"
|
||
"videoweb/pub/common/log"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
const (
|
||
topicDetailPath = "/api/app/playlet/topic/detail"
|
||
|
||
defaultTopicDetailPageSize = 24
|
||
|
||
// topic/detail sortType:1编辑排序 2最新 3最热
|
||
topicDetailSortEditor = 1
|
||
topicDetailSortNewest = 2
|
||
topicDetailSortHeat = 3
|
||
)
|
||
|
||
// TopicDetailQuery 对应 GET /api/app/playlet/topic/detail 查询参数。
|
||
// 上游必填 id;路径优先 slug,服务端解析后再请求。
|
||
type TopicDetailQuery struct {
|
||
ID int64
|
||
Slug string
|
||
PageNum int
|
||
PageSize int
|
||
SortType int // 1编辑排序 2最新 3最热
|
||
}
|
||
|
||
// Normalize 校正分页与排序默认值。
|
||
func (q *TopicDetailQuery) Normalize() {
|
||
if q.PageNum <= 0 {
|
||
q.PageNum = 1
|
||
}
|
||
if q.PageSize <= 0 {
|
||
q.PageSize = defaultTopicDetailPageSize
|
||
}
|
||
if q.PageSize > 50 {
|
||
q.PageSize = 50
|
||
}
|
||
switch q.SortType {
|
||
case topicDetailSortNewest, topicDetailSortHeat:
|
||
default:
|
||
q.SortType = topicDetailSortEditor
|
||
}
|
||
if q.ID < 0 {
|
||
q.ID = 0
|
||
}
|
||
q.Slug = strings.TrimSpace(q.Slug)
|
||
}
|
||
|
||
// ParseTopicDetailQuery 从路径参数解析专题详情筛选。
|
||
// 路由:/topic-detail/:slug/ 、/topic-detail/:slug/page/:page/ 、
|
||
//
|
||
// /topic-detail/:slug/sort/:sortType/ 、/topic-detail/:slug/sort/:sortType/page/:page/
|
||
func ParseTopicDetailQuery(c *gin.Context) TopicDetailQuery {
|
||
q := TopicDetailQuery{PageSize: defaultTopicDetailPageSize}
|
||
ident := strings.TrimSpace(c.Param("slug"))
|
||
if ident != "" {
|
||
if decoded, err := url.PathUnescape(ident); err == nil {
|
||
ident = strings.TrimSpace(decoded)
|
||
}
|
||
}
|
||
if id, err := strconv.ParseInt(ident, 10, 64); err == nil && id > 0 {
|
||
q.ID = id
|
||
} else {
|
||
q.Slug = ident
|
||
}
|
||
if n, err := strconv.Atoi(strings.TrimSpace(c.Param("sortType"))); err == nil {
|
||
q.SortType = n
|
||
}
|
||
if n, err := strconv.Atoi(strings.TrimSpace(c.Param("page"))); err == nil {
|
||
q.PageNum = n
|
||
}
|
||
q.Normalize()
|
||
return q
|
||
}
|
||
|
||
// TopicDetailURL 生成专题详情筛选链接(纯路径,无 query)。
|
||
func TopicDetailURL(slug string, id int64, sortType, pageNum int) string {
|
||
q := TopicDetailQuery{
|
||
ID: id,
|
||
Slug: slug,
|
||
PageNum: pageNum,
|
||
PageSize: defaultTopicDetailPageSize,
|
||
SortType: sortType,
|
||
}
|
||
q.Normalize()
|
||
return q.URL()
|
||
}
|
||
|
||
// URL 输出:/topic-detail/{slug|id}/[sort/{s}/][page/{n}/]
|
||
func (q TopicDetailQuery) URL() string {
|
||
q.Normalize()
|
||
segment := pathSegment(q.Slug, q.ID)
|
||
if segment == "" {
|
||
return "/topic"
|
||
}
|
||
base := "/topic-detail/" + segment
|
||
if q.SortType == topicDetailSortEditor {
|
||
if q.PageNum <= 1 {
|
||
return base
|
||
}
|
||
return base + "/page/" + strconv.Itoa(q.PageNum)
|
||
}
|
||
path := base + "/sort/" + strconv.Itoa(q.SortType)
|
||
if q.PageNum > 1 {
|
||
path += "/page/" + strconv.Itoa(q.PageNum)
|
||
}
|
||
return path
|
||
}
|
||
|
||
// BuildTopicDetailPagination 专题详情作品分页。
|
||
func BuildTopicDetailPagination(q TopicDetailQuery, totalCount int64) PaginationVM {
|
||
q.Normalize()
|
||
return BuildPagination(func(p int) string {
|
||
nq := q
|
||
nq.PageNum = p
|
||
return nq.URL()
|
||
}, q.PageNum, q.PageSize, totalCount)
|
||
}
|
||
|
||
// ResolveTopicDetailID 路径为 slug 时,用专题列表解析上游必填 id。
|
||
func ResolveTopicDetailID(c *gin.Context, q *TopicDetailQuery) {
|
||
if q == nil || q.ID > 0 {
|
||
return
|
||
}
|
||
q.Normalize()
|
||
slug := q.Slug
|
||
if slug == "" {
|
||
return
|
||
}
|
||
for page := 1; page <= 5; page++ {
|
||
raw := FetchTopicListOrEmpty(c, TopicListQuery{
|
||
PageNum: page,
|
||
PageSize: 50,
|
||
SortType: topicSortNewest,
|
||
})
|
||
for _, item := range raw.List {
|
||
if strings.EqualFold(strings.TrimSpace(item.Slug), slug) {
|
||
q.ID = item.ID
|
||
q.Slug = strings.TrimSpace(item.Slug)
|
||
return
|
||
}
|
||
}
|
||
if len(raw.List) == 0 || int64(page*50) >= raw.TotalCount {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// FetchTopicDetail 拉取 GET /api/app/playlet/topic/detail。
|
||
// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_topic_detail
|
||
func FetchTopicDetail(c *gin.Context, q TopicDetailQuery) (*PlayletTopicDetailResp, error) {
|
||
q.Normalize()
|
||
if q.ID <= 0 {
|
||
return nil, fmt.Errorf("playlet topic detail missing id")
|
||
}
|
||
params := url.Values{}
|
||
params.Set("id", strconv.FormatInt(q.ID, 10))
|
||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||
params.Set("sortType", strconv.Itoa(q.SortType))
|
||
|
||
var env APIEnvelope[PlayletTopicDetailResp]
|
||
if err := upstream.GetPublicJSON(c, topicDetailPath+"?"+params.Encode(), &env); err != nil {
|
||
return nil, err
|
||
}
|
||
if env.Code != 200 {
|
||
return nil, fmt.Errorf("playlet topic detail code=%d msg=%s", env.Code, env.Msg)
|
||
}
|
||
return &env.Data, nil
|
||
}
|
||
|
||
// FetchTopicDetailOrEmpty 失败时返回空详情。
|
||
func FetchTopicDetailOrEmpty(c *gin.Context, q TopicDetailQuery) *PlayletTopicDetailResp {
|
||
data, err := FetchTopicDetail(c, q)
|
||
if err != nil {
|
||
log.Error("fetch playlet topic detail failed", log.E(err))
|
||
return &PlayletTopicDetailResp{ID: q.ID, Slug: q.Slug, PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
if data == nil {
|
||
return &PlayletTopicDetailResp{ID: q.ID, Slug: q.Slug, PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
data.SeoInfo.Normalize()
|
||
return data
|
||
}
|
||
|
||
// TopicDetailCoverURLs 专题详情顶部封面,最多 3 张。
|
||
func TopicDetailCoverURLs(detail PlayletTopicDetailResp) []string {
|
||
return TopicCoverURLs(PlayletTopicPageItem{Covers: detail.Covers, List: detail.List})
|
||
}
|
||
|
||
// TopicDetailHeroCover 头图背景用第一张封面。
|
||
func TopicDetailHeroCover(detail PlayletTopicDetailResp) string {
|
||
covers := TopicDetailCoverURLs(detail)
|
||
if len(covers) == 0 {
|
||
return ""
|
||
}
|
||
return covers[0]
|
||
}
|
||
|
||
// TopicDetailSortLabel 排序文案。
|
||
func TopicDetailSortLabel(sortType int) string {
|
||
switch sortType {
|
||
case topicDetailSortNewest:
|
||
return "最新"
|
||
case topicDetailSortHeat:
|
||
return "最热"
|
||
default:
|
||
return "编辑排序"
|
||
}
|
||
}
|