356 lines
9.5 KiB
Go
356 lines
9.5 KiB
Go
package playletser
|
||
|
||
import (
|
||
"fmt"
|
||
"net/url"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"videoweb/app/service/upstream"
|
||
"videoweb/pub/common/log"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
const (
|
||
listPath = "/api/app/playlet/list"
|
||
allListPath = "/api/app/playlet/all/list"
|
||
themePath = "/api/app/theme/list"
|
||
|
||
defaultListPageSize = 24
|
||
|
||
// all/list sortType:1最新 2最热 3收藏最多
|
||
allSortNewest = 1
|
||
allSortHeat = 2
|
||
allSortCollect = 3
|
||
|
||
// all/list updateStatus:0全部 1连载中 2已完结
|
||
allUpdateAll = 0
|
||
allUpdateOngoing = 1
|
||
allUpdateDone = 2
|
||
|
||
sortHeat = 1 // 热度优先(旧 playlet/list)
|
||
sortTodayUpdate = 2 // 今日更新(旧 playlet/list)
|
||
// 站点统一类型:1-非成人 2-成人(与 themeType 一致)。
|
||
contentTypeNonAdult = 1
|
||
contentTypeAdult = 2
|
||
// playlet/list 上游 type 与站点定义相反:1-成人 2-非成人。
|
||
upstreamListTypeAdult = 1
|
||
upstreamListTypeNonAdult = 2
|
||
)
|
||
|
||
// ListQuery 短剧列表查询参数(旧 playlet/list)。
|
||
type ListQuery struct {
|
||
PageNum int
|
||
PageSize int
|
||
SortOrder int
|
||
ThemeID int64
|
||
ThemeSlug string
|
||
Type int
|
||
}
|
||
|
||
// AllListQuery 对应 GET /api/app/playlet/all/list 查询参数。
|
||
type AllListQuery struct {
|
||
PageNum int
|
||
PageSize int
|
||
SortType int // 1最新 2最热 3收藏最多
|
||
ThemeID int64
|
||
ThemeSlug string
|
||
UpdateStatus int // 0全部 1连载中 2已完结
|
||
}
|
||
|
||
// Normalize 校正分页与排序默认值。
|
||
func (q *ListQuery) Normalize() {
|
||
if q.PageNum <= 0 {
|
||
q.PageNum = 1
|
||
}
|
||
if q.PageSize <= 0 {
|
||
q.PageSize = defaultListPageSize
|
||
}
|
||
if q.PageSize > 50 {
|
||
q.PageSize = 50
|
||
}
|
||
if q.SortOrder != sortTodayUpdate {
|
||
q.SortOrder = sortHeat
|
||
}
|
||
if q.ThemeID < 0 {
|
||
q.ThemeID = 0
|
||
}
|
||
q.Type = normalizeContentType(q.Type)
|
||
}
|
||
|
||
// Normalize 校正 all/list 查询参数。
|
||
func (q *AllListQuery) Normalize() {
|
||
if q.PageNum <= 0 {
|
||
q.PageNum = 1
|
||
}
|
||
if q.PageSize <= 0 {
|
||
q.PageSize = defaultListPageSize
|
||
}
|
||
if q.PageSize > 50 {
|
||
q.PageSize = 50
|
||
}
|
||
switch q.SortType {
|
||
case allSortHeat, allSortCollect:
|
||
default:
|
||
q.SortType = allSortNewest
|
||
}
|
||
switch q.UpdateStatus {
|
||
case allUpdateOngoing, allUpdateDone:
|
||
default:
|
||
q.UpdateStatus = allUpdateAll
|
||
}
|
||
if q.ThemeID < 0 {
|
||
q.ThemeID = 0
|
||
}
|
||
q.ThemeSlug = strings.TrimSpace(q.ThemeSlug)
|
||
}
|
||
|
||
// ParseAllListQuery 从路径参数解析 all/list 筛选。
|
||
// 路由:/list/ 、/list/:theme/ 、/list/:theme/:updateStatus/:sortType/ 、
|
||
//
|
||
// /list/:theme/:updateStatus/:sortType/page/:page/
|
||
func ParseAllListQuery(c *gin.Context) AllListQuery {
|
||
q := AllListQuery{PageSize: defaultListPageSize}
|
||
theme := strings.TrimSpace(c.Param("theme"))
|
||
if theme != "" {
|
||
if decoded, err := url.PathUnescape(theme); err == nil {
|
||
theme = strings.TrimSpace(decoded)
|
||
}
|
||
}
|
||
if theme != "" && !strings.EqualFold(theme, "all") {
|
||
if id, err := strconv.ParseInt(theme, 10, 64); err == nil && id > 0 {
|
||
q.ThemeID = id
|
||
} else {
|
||
q.ThemeSlug = theme
|
||
}
|
||
}
|
||
if n, err := strconv.Atoi(strings.TrimSpace(c.Param("updateStatus"))); err == nil {
|
||
q.UpdateStatus = n
|
||
}
|
||
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
|
||
}
|
||
|
||
// AllListURL 生成列表页筛选链接(纯路径,无 query)。
|
||
func AllListURL(themeSlug string, themeID int64, updateStatus, sortType, pageNum int) string {
|
||
q := AllListQuery{
|
||
PageNum: pageNum,
|
||
PageSize: defaultListPageSize,
|
||
SortType: sortType,
|
||
ThemeID: themeID,
|
||
ThemeSlug: themeSlug,
|
||
UpdateStatus: updateStatus,
|
||
}
|
||
q.Normalize()
|
||
return q.URL()
|
||
}
|
||
|
||
// URL 输出路径模式:/list/{theme}/{updateStatus}/{sortType}/[page/{n}/]
|
||
// theme 优先 slug,无 slug 时才用 id;都没有则为 all。
|
||
func (q AllListQuery) URL() string {
|
||
q.Normalize()
|
||
slug := strings.TrimSpace(q.ThemeSlug)
|
||
id := q.ThemeID
|
||
if slug != "" {
|
||
id = 0
|
||
}
|
||
segment := pathSegment(slug, id)
|
||
if segment == "" {
|
||
segment = "all"
|
||
}
|
||
defaults := q.UpdateStatus == allUpdateAll && q.SortType == allSortNewest
|
||
if defaults && q.PageNum <= 1 {
|
||
if segment == "all" {
|
||
return "/list"
|
||
}
|
||
return "/list/" + segment
|
||
}
|
||
path := "/list/" + segment + "/" + strconv.Itoa(q.UpdateStatus) + "/" + strconv.Itoa(q.SortType)
|
||
if q.PageNum > 1 {
|
||
path += "/page/" + strconv.Itoa(q.PageNum)
|
||
}
|
||
return path
|
||
}
|
||
|
||
// ResolveAllListTheme 用题材列表补全 slug / id:路径与跳转优先 slug。
|
||
func ResolveAllListTheme(q *AllListQuery, themes []PlayletThemeItem) {
|
||
if q == nil {
|
||
return
|
||
}
|
||
q.Normalize()
|
||
slug := strings.TrimSpace(q.ThemeSlug)
|
||
if slug != "" {
|
||
for _, t := range themes {
|
||
if strings.TrimSpace(t.Slug) == slug {
|
||
q.ThemeID = t.ID
|
||
q.ThemeSlug = strings.TrimSpace(t.Slug)
|
||
return
|
||
}
|
||
}
|
||
return
|
||
}
|
||
if q.ThemeID > 0 {
|
||
if found := FindThemeSlugByID(themes, q.ThemeID); found != "" {
|
||
q.ThemeSlug = found
|
||
}
|
||
}
|
||
}
|
||
|
||
// BuildAllListPagination 列表分页。
|
||
func BuildAllListPagination(q AllListQuery, totalCount int64) PaginationVM {
|
||
q.Normalize()
|
||
return BuildPagination(func(p int) string {
|
||
nq := q
|
||
nq.PageNum = p
|
||
return nq.URL()
|
||
}, q.PageNum, q.PageSize, totalCount)
|
||
}
|
||
|
||
// FetchAllList 拉取 GET /api/app/playlet/all/list。
|
||
// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_all_list
|
||
func FetchAllList(c *gin.Context, q AllListQuery) (*PlayletAllListResp, error) {
|
||
q.Normalize()
|
||
params := url.Values{}
|
||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||
params.Set("sortType", strconv.Itoa(q.SortType))
|
||
params.Set("updateStatus", strconv.Itoa(q.UpdateStatus))
|
||
if q.ThemeSlug != "" {
|
||
params.Set("themeSlug", q.ThemeSlug)
|
||
} else if q.ThemeID > 0 {
|
||
params.Set("themeId", strconv.FormatInt(q.ThemeID, 10))
|
||
}
|
||
|
||
var env APIEnvelope[PlayletAllListResp]
|
||
if err := upstream.GetPublicJSON(c, allListPath+"?"+params.Encode(), &env); err != nil {
|
||
return nil, err
|
||
}
|
||
if env.Code != 200 {
|
||
return nil, fmt.Errorf("playlet all list code=%d msg=%s", env.Code, env.Msg)
|
||
}
|
||
return &env.Data, nil
|
||
}
|
||
|
||
// FetchAllListOrEmpty 失败时返回空列表。
|
||
func FetchAllListOrEmpty(c *gin.Context, q AllListQuery) *PlayletAllListResp {
|
||
data, err := FetchAllList(c, q)
|
||
if err != nil {
|
||
log.Error("fetch playlet all list failed", log.E(err))
|
||
return &PlayletAllListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
if data == nil {
|
||
return &PlayletAllListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
data.SeoInfo.Normalize()
|
||
return data
|
||
}
|
||
|
||
// FetchList 分页查询已上架短剧。
|
||
func FetchList(c *gin.Context, q ListQuery) (*PlayletVideoResp, error) {
|
||
q.Normalize()
|
||
params := url.Values{}
|
||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||
params.Set("sortOrder", strconv.Itoa(q.SortOrder))
|
||
if strings.TrimSpace(q.ThemeSlug) != "" {
|
||
params.Set("themeSlug", strings.TrimSpace(q.ThemeSlug))
|
||
} else if q.ThemeID > 0 {
|
||
params.Set("themeId", strconv.FormatInt(q.ThemeID, 10))
|
||
} else {
|
||
params.Set("type", strconv.Itoa(listTypeForUpstream(q.Type)))
|
||
}
|
||
|
||
var env APIEnvelope[PlayletVideoResp]
|
||
if err := upstream.GetPublicJSON(c, listPath+"?"+params.Encode(), &env); err != nil {
|
||
return nil, err
|
||
}
|
||
if env.Code != 200 {
|
||
return nil, fmt.Errorf("playlet list code=%d msg=%s", env.Code, env.Msg)
|
||
}
|
||
return &env.Data, nil
|
||
}
|
||
|
||
// FetchListOrEmpty 失败时返回空列表。
|
||
func FetchListOrEmpty(c *gin.Context, q ListQuery) *PlayletVideoResp {
|
||
data, err := FetchList(c, q)
|
||
if err != nil {
|
||
log.Error("fetch playlet list failed", log.E(err))
|
||
return &PlayletVideoResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
if data == nil {
|
||
return &PlayletVideoResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||
}
|
||
data.SeoInfo.Normalize()
|
||
return data
|
||
}
|
||
|
||
// FetchThemes 拉取题材列表。
|
||
func FetchThemes(c *gin.Context, contentType int) ([]PlayletThemeItem, error) {
|
||
contentType = normalizeContentType(contentType)
|
||
params := url.Values{}
|
||
params.Set("themeType", strconv.Itoa(contentType))
|
||
var env APIEnvelope[PlayletThemeListResponse]
|
||
if err := upstream.GetPublicJSON(c, themePath+"?"+params.Encode(), &env); err != nil {
|
||
return nil, err
|
||
}
|
||
if env.Code != 200 {
|
||
return nil, fmt.Errorf("theme list code=%d msg=%s", env.Code, env.Msg)
|
||
}
|
||
return env.Data.List, nil
|
||
}
|
||
|
||
// FetchThemesOrEmpty 失败时返回空题材。
|
||
func FetchThemesOrEmpty(c *gin.Context, contentType int) []PlayletThemeItem {
|
||
list, err := FetchThemes(c, contentType)
|
||
if err != nil {
|
||
log.Error("fetch theme list failed", log.E(err))
|
||
return nil
|
||
}
|
||
return ActiveThemes(list)
|
||
}
|
||
|
||
// ActiveThemes 仅保留上架题材,按 rank 降序(越大越靠前)。
|
||
func ActiveThemes(list []PlayletThemeItem) []PlayletThemeItem {
|
||
out := make([]PlayletThemeItem, 0, len(list))
|
||
for _, item := range list {
|
||
if !item.Status {
|
||
continue
|
||
}
|
||
item.Name = strings.TrimSpace(item.Name)
|
||
item.Slug = strings.TrimSpace(item.Slug)
|
||
if item.Name == "" || item.ID <= 0 {
|
||
continue
|
||
}
|
||
out = append(out, item)
|
||
}
|
||
sort.SliceStable(out, func(i, j int) bool {
|
||
if out[i].Rank != out[j].Rank {
|
||
return out[i].Rank > out[j].Rank
|
||
}
|
||
return out[i].ID < out[j].ID
|
||
})
|
||
return out
|
||
}
|
||
|
||
func normalizeContentType(contentType int) int {
|
||
if contentType == contentTypeAdult {
|
||
return contentTypeAdult
|
||
}
|
||
return contentTypeNonAdult
|
||
}
|
||
|
||
// listTypeForUpstream 将站点统一类型转为 playlet/list 的 type(该接口 1=成人 2=非成人)。
|
||
func listTypeForUpstream(contentType int) int {
|
||
if normalizeContentType(contentType) == contentTypeAdult {
|
||
return upstreamListTypeAdult
|
||
}
|
||
return upstreamListTypeNonAdult
|
||
}
|