logo和配置修改
This commit is contained in:
@@ -139,20 +139,6 @@ func (c *PlayletChapterInfoResp) AllowWatch() bool {
|
||||
return strings.TrimSpace(c.VideoURL) != "" && c.PayType == 0
|
||||
}
|
||||
|
||||
// UnlockCost 金币解锁消耗:优先 costCoins。
|
||||
func (c *PlayletChapterInfoResp) UnlockCost() int64 {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
if c.CostCoins > 0 {
|
||||
return c.CostCoins
|
||||
}
|
||||
if c.SingleCoins > 0 {
|
||||
return int64(c.SingleCoins)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// AllowWatch 详情选集是否可直达播放(优先 canWatch)。
|
||||
func (c PlayletChapterData) AllowWatch() bool {
|
||||
if c.CanWatch {
|
||||
@@ -166,33 +152,3 @@ func (c PlayletChapterData) AllowWatch() bool {
|
||||
}
|
||||
return c.PayType == 0
|
||||
}
|
||||
|
||||
// NeedCoinGate 是否弹出金币解锁。
|
||||
func (c PlayletChapterData) NeedCoinGate() bool {
|
||||
if c.AllowWatch() {
|
||||
return false
|
||||
}
|
||||
if c.NeedUnlock {
|
||||
return true
|
||||
}
|
||||
return c.PayType == 1
|
||||
}
|
||||
|
||||
// NeedVipGate 是否弹出 VIP 开通。
|
||||
func (c PlayletChapterData) NeedVipGate() bool {
|
||||
if c.AllowWatch() || c.NeedCoinGate() {
|
||||
return false
|
||||
}
|
||||
if c.NeedVip {
|
||||
return true
|
||||
}
|
||||
return c.PayType == 2
|
||||
}
|
||||
|
||||
// UnlockCost 金币解锁消耗:优先 costCoins。
|
||||
func (c PlayletChapterData) UnlockCost() int {
|
||||
if c.CostCoins > 0 {
|
||||
return int(c.CostCoins)
|
||||
}
|
||||
return c.SingleCoins
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"videoweb/app/service/authser"
|
||||
"videoweb/app/service/upstream"
|
||||
"videoweb/pub/common/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const chapterCollectListPath = "/api/app/playlet/chapter/collect/list"
|
||||
|
||||
// PlayletChapterCollectData 对应 swagger proto.PlayletChapterCollectData
|
||||
type PlayletChapterCollectData struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
ChapterTitle string `json:"chapterTitle"`
|
||||
PlayletID int64 `json:"playletId"`
|
||||
PlayletSlug string `json:"playletSlug"`
|
||||
PlayletTitle string `json:"playletTitle"`
|
||||
PlayletCover string `json:"playletCover"`
|
||||
CurrentEpisode int `json:"currentEpisode"`
|
||||
TotalEpisode int `json:"totalEpisode"`
|
||||
UpdateStatus int `json:"updateStatus"`
|
||||
CountBrowse int64 `json:"countBrowse"`
|
||||
CountCollect int64 `json:"countCollect"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
IsCollect int `json:"isCollect"`
|
||||
CollectedAt string `json:"collectedAt"`
|
||||
MediaTime int `json:"mediaTime"`
|
||||
PayType int `json:"payType"`
|
||||
SingleCoins int `json:"singleCoins"`
|
||||
VideoURL string `json:"videoUrl"`
|
||||
}
|
||||
|
||||
// PlayletChapterCollectListResp 对应 swagger proto.PlayletChapterCollectListResp
|
||||
type PlayletChapterCollectListResp struct {
|
||||
List []PlayletChapterCollectData `json:"list"`
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
}
|
||||
|
||||
// FetchChapterCollectList 拉取当前用户剧集收藏列表。
|
||||
// 第二个返回值为上游业务 code(网络错误时为 0)。
|
||||
func FetchChapterCollectList(c *gin.Context, q CollectListQuery) (*PlayletChapterCollectListResp, int, error) {
|
||||
q.Normalize()
|
||||
params := url.Values{}
|
||||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||||
|
||||
var env APIEnvelope[PlayletChapterCollectListResp]
|
||||
if err := upstream.GetJSON(c, chapterCollectListPath+"?"+params.Encode(), &env); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if env.Code != 200 {
|
||||
return nil, env.Code, fmt.Errorf("playlet chapter collect list code=%d msg=%s", env.Code, env.Msg)
|
||||
}
|
||||
return &env.Data, env.Code, nil
|
||||
}
|
||||
|
||||
// FetchChapterCollectListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
|
||||
func FetchChapterCollectListOrEmpty(c *gin.Context, q CollectListQuery) *PlayletChapterCollectListResp {
|
||||
data, code, err := FetchChapterCollectList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
if retry, _ := authser.OnSessionExpired(c, code); retry {
|
||||
data, _, err = FetchChapterCollectList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
log.Error("fetch playlet chapter collect list failed", log.E(err))
|
||||
return &PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"videoweb/app/service/upstream"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
chapterLikeSetPath = "/api/app/playlet/chapter/like/set"
|
||||
chapterCollectSetPath = "/api/app/playlet/chapter/collect/set"
|
||||
)
|
||||
|
||||
// PlayletChapterLikeSetReq 对应 swagger proto.PlayletChapterLikeSetReq
|
||||
type PlayletChapterLikeSetReq struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
IsLike bool `json:"isLike"`
|
||||
}
|
||||
|
||||
// PlayletChapterLikeSetResp 对应 swagger proto.PlayletChapterLikeSetResp
|
||||
type PlayletChapterLikeSetResp struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
IsLike int `json:"isLike"`
|
||||
}
|
||||
|
||||
// ChapterLikeSet 设置剧集点赞/取消点赞。
|
||||
func ChapterLikeSet(c *gin.Context, req PlayletChapterLikeSetReq) (*APIEnvelope[PlayletChapterLikeSetResp], error) {
|
||||
if req.ChapterID <= 0 {
|
||||
return nil, fmt.Errorf("chapter like invalid chapterId")
|
||||
}
|
||||
var env APIEnvelope[PlayletChapterLikeSetResp]
|
||||
if _, err := upstream.PostJSON(c, chapterLikeSetPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
// PlayletChapterCollectSetReq 对应 swagger proto.PlayletChapterCollectSetReq
|
||||
type PlayletChapterCollectSetReq struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
IsCollect bool `json:"isCollect"`
|
||||
}
|
||||
|
||||
// PlayletChapterCollectSetResp 对应 swagger proto.PlayletChapterCollectSetResp
|
||||
type PlayletChapterCollectSetResp struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
CountCollect int64 `json:"countCollect"`
|
||||
IsCollect int `json:"isCollect"`
|
||||
}
|
||||
|
||||
// ChapterCollectSet 设置剧集收藏/取消收藏。
|
||||
func ChapterCollectSet(c *gin.Context, req PlayletChapterCollectSetReq) (*APIEnvelope[PlayletChapterCollectSetResp], error) {
|
||||
if req.ChapterID <= 0 {
|
||||
return nil, fmt.Errorf("chapter collect invalid chapterId")
|
||||
}
|
||||
var env APIEnvelope[PlayletChapterCollectSetResp]
|
||||
if _, err := upstream.PostJSON(c, chapterCollectSetPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"videoweb/app/service/upstream"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const chapterUnlockPath = "/api/app/playlet/chapter/unlock"
|
||||
|
||||
// PlayletChapterUnlockReq 对应 swagger proto.PlayletChapterUnlockReq
|
||||
// POST /api/app/playlet/chapter/unlock
|
||||
type PlayletChapterUnlockReq struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
PlayletID int64 `json:"playletId"`
|
||||
}
|
||||
|
||||
// PlayletChapterUnlockResp 对应 swagger proto.PlayletChapterUnlockResp
|
||||
type PlayletChapterUnlockResp struct {
|
||||
Balance int64 `json:"balance"`
|
||||
CanWatch bool `json:"canWatch"`
|
||||
ChangeCoin int64 `json:"changeCoin"`
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
CostCoins int64 `json:"costCoins"`
|
||||
PlayletID int64 `json:"playletId"`
|
||||
Unlocked bool `json:"unlocked"`
|
||||
}
|
||||
|
||||
// ChapterUnlock 使用金币解锁剧集(幂等)。
|
||||
func ChapterUnlock(c *gin.Context, req PlayletChapterUnlockReq) (*APIEnvelope[PlayletChapterUnlockResp], error) {
|
||||
if req.ChapterID <= 0 || req.PlayletID <= 0 {
|
||||
return nil, fmt.Errorf("chapter unlock invalid chapterId/playletId")
|
||||
}
|
||||
var env APIEnvelope[PlayletChapterUnlockResp]
|
||||
if _, err := upstream.PostJSON(c, chapterUnlockPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"videoweb/app/service/upstream"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const chapterWatchReportPath = "/api/app/playlet/chapter/watch/report"
|
||||
|
||||
// PlayletChapterWatchReportReq 对应 swagger proto.PlayletChapterWatchReportReq
|
||||
type PlayletChapterWatchReportReq struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
PlayletID int64 `json:"playletId,omitempty"`
|
||||
}
|
||||
|
||||
// PlayletChapterWatchReportResp 对应 swagger proto.PlayletChapterWatchReportResp
|
||||
type PlayletChapterWatchReportResp struct {
|
||||
ChapterID int64 `json:"chapterId"`
|
||||
PlayletID int64 `json:"playletId"`
|
||||
CurrentEpisode int `json:"currentEpisode"`
|
||||
LastChapterID int64 `json:"lastChapterId"`
|
||||
LastEpisode int `json:"lastEpisode"`
|
||||
WatchedCount int `json:"watchedCount"`
|
||||
UnwatchedCount int `json:"unwatchedCount"`
|
||||
}
|
||||
|
||||
// ChapterWatchReport 上报短剧剧集观看。
|
||||
func ChapterWatchReport(c *gin.Context, req PlayletChapterWatchReportReq) (*APIEnvelope[PlayletChapterWatchReportResp], error) {
|
||||
if req.ChapterID <= 0 {
|
||||
return nil, fmt.Errorf("chapter watch report invalid chapterId")
|
||||
}
|
||||
var env APIEnvelope[PlayletChapterWatchReportResp]
|
||||
if _, err := upstream.PostJSON(c, chapterWatchReportPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
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 (
|
||||
collectSetPath = "/api/app/playlet/collect/set"
|
||||
collectListPath = "/api/app/playlet/collect/list"
|
||||
defaultCollectPageSize = 40
|
||||
|
||||
CollectTypePlaylet = "playlet"
|
||||
CollectTypeChapter = "chapter"
|
||||
)
|
||||
|
||||
// PlayletCollectSetReq 对应 swagger proto.PlayletCollectSetReq
|
||||
type PlayletCollectSetReq struct {
|
||||
ID int64 `json:"id"`
|
||||
IsCollect bool `json:"isCollect"`
|
||||
}
|
||||
|
||||
// PlayletCollectSetResp 对应 swagger proto.PlayletCollectSetResp
|
||||
type PlayletCollectSetResp struct {
|
||||
ID int64 `json:"id"`
|
||||
CountCollect int64 `json:"countCollect"`
|
||||
IsCollect int `json:"isCollect"`
|
||||
}
|
||||
|
||||
// PlayletCollectData 对应 swagger proto.PlayletCollectData
|
||||
type PlayletCollectData struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
Cover string `json:"cover"`
|
||||
Desc string `json:"desc"`
|
||||
TagIDs []int64 `json:"tagIds"`
|
||||
TagNames []string `json:"tagNames"`
|
||||
TagSlugs []string `json:"tagSlugs"`
|
||||
ThemeIDs []int64 `json:"themeIds"`
|
||||
ThemeNames []string `json:"themeNames"`
|
||||
ThemeSlugs []string `json:"themeSlugs"`
|
||||
TotalEpisode int `json:"totalEpisode"`
|
||||
UpdateStatus int `json:"updateStatus"`
|
||||
CountBrowse int64 `json:"countBrowse"`
|
||||
CountCollect int64 `json:"countCollect"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
IsFeatured bool `json:"isFeatured"`
|
||||
IsNew bool `json:"isNew"`
|
||||
IsRecommend bool `json:"isRecommend"`
|
||||
IsCollect int `json:"isCollect"`
|
||||
CollectedAt string `json:"collectedAt"`
|
||||
WatchedCount int `json:"watchedCount"`
|
||||
UnwatchedCount int `json:"unwatchedCount"`
|
||||
LastChapterID int64 `json:"lastChapterId"`
|
||||
LastEpisode int `json:"lastEpisode"`
|
||||
}
|
||||
|
||||
// PlayletCollectListResp 对应 swagger proto.PlayletCollectListResp
|
||||
type PlayletCollectListResp struct {
|
||||
List []PlayletCollectData `json:"list"`
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
}
|
||||
|
||||
// CollectListQuery 收藏列表分页参数。
|
||||
type CollectListQuery struct {
|
||||
PageNum int
|
||||
PageSize int
|
||||
Type string // playlet | chapter
|
||||
}
|
||||
|
||||
// NormalizeCollectType 归一化收藏类型。
|
||||
func NormalizeCollectType(raw string) string {
|
||||
switch strings.TrimSpace(strings.ToLower(raw)) {
|
||||
case CollectTypeChapter, "2", "episode", "ep":
|
||||
return CollectTypeChapter
|
||||
default:
|
||||
return CollectTypePlaylet
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize 校正分页默认值。
|
||||
func (q *CollectListQuery) Normalize() {
|
||||
if q.PageNum <= 0 {
|
||||
q.PageNum = 1
|
||||
}
|
||||
if q.PageSize <= 0 {
|
||||
q.PageSize = defaultCollectPageSize
|
||||
}
|
||||
if q.PageSize > 50 {
|
||||
q.PageSize = 50
|
||||
}
|
||||
q.Type = NormalizeCollectType(q.Type)
|
||||
}
|
||||
|
||||
// CollectSet 设置短剧收藏/取消收藏。
|
||||
func CollectSet(c *gin.Context, req PlayletCollectSetReq) (*APIEnvelope[PlayletCollectSetResp], error) {
|
||||
if req.ID <= 0 {
|
||||
return nil, fmt.Errorf("playlet collect invalid id")
|
||||
}
|
||||
var env APIEnvelope[PlayletCollectSetResp]
|
||||
if _, err := upstream.PostJSON(c, collectSetPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
// FetchCollectList 拉取当前用户短剧收藏列表。
|
||||
// 第二个返回值为上游业务 code(网络错误时为 0)。
|
||||
func FetchCollectList(c *gin.Context, q CollectListQuery) (*PlayletCollectListResp, int, error) {
|
||||
q.Normalize()
|
||||
params := url.Values{}
|
||||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||||
|
||||
var env APIEnvelope[PlayletCollectListResp]
|
||||
if err := upstream.GetJSON(c, collectListPath+"?"+params.Encode(), &env); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if env.Code != 200 {
|
||||
return nil, env.Code, fmt.Errorf("playlet collect list code=%d msg=%s", env.Code, env.Msg)
|
||||
}
|
||||
return &env.Data, env.Code, nil
|
||||
}
|
||||
|
||||
// FetchCollectListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
|
||||
func FetchCollectListOrEmpty(c *gin.Context, q CollectListQuery) *PlayletCollectListResp {
|
||||
data, code, err := FetchCollectList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
if retry, _ := authser.OnSessionExpired(c, code); retry {
|
||||
data, _, err = FetchCollectList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
log.Error("fetch playlet collect list failed", log.E(err))
|
||||
return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func collectPageURL(pageNum int, collectType string) string {
|
||||
collectType = NormalizeCollectType(collectType)
|
||||
path := "/collect"
|
||||
if collectType != CollectTypePlaylet {
|
||||
path += "/" + url.PathEscape(collectType)
|
||||
}
|
||||
if pageNum > 1 {
|
||||
path += "/page/" + strconv.Itoa(pageNum)
|
||||
}
|
||||
return path + "/"
|
||||
}
|
||||
|
||||
func CollectCanonicalPath(q CollectListQuery) string {
|
||||
q.Normalize()
|
||||
return collectPageURL(q.PageNum, q.Type)
|
||||
}
|
||||
|
||||
// BuildCollectPagination 收藏列表分页。
|
||||
func BuildCollectPagination(q CollectListQuery, totalCount int64) PaginationVM {
|
||||
q.Normalize()
|
||||
return BuildPagination(func(p int) string {
|
||||
return collectPageURL(p, q.Type)
|
||||
}, q.PageNum, q.PageSize, totalCount)
|
||||
}
|
||||
|
||||
// ParseCollectListQuery 从 query 解析分页与类型(playlet 短剧 / chapter 剧集)。
|
||||
func ParseCollectListQuery(pageStr, typeStr string) CollectListQuery {
|
||||
q := CollectListQuery{
|
||||
PageSize: defaultCollectPageSize,
|
||||
Type: NormalizeCollectType(typeStr),
|
||||
}
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(pageStr)); err == nil {
|
||||
q.PageNum = n
|
||||
}
|
||||
q.Normalize()
|
||||
return q
|
||||
}
|
||||
|
||||
// collectProgressText 进度文案:优先最近观看集,其次未看/已看完。
|
||||
func collectProgressText(item PlayletCollectData) string {
|
||||
if item.LastEpisode > 0 {
|
||||
return fmt.Sprintf("第%d集", item.LastEpisode)
|
||||
}
|
||||
if item.TotalEpisode > 0 && item.UnwatchedCount <= 0 {
|
||||
return "已看完"
|
||||
}
|
||||
if item.UnwatchedCount > 0 {
|
||||
return fmt.Sprintf("%d集未看", item.UnwatchedCount)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// collectTagText 收藏卡片标签:仅展示 TagNames 第一项,无标签时不显示。
|
||||
func collectTagText(item PlayletCollectData) string {
|
||||
return firstName(item.TagNames)
|
||||
}
|
||||
|
||||
// chapterCollectEpisodeLabel 剧集收藏角标:总集数。
|
||||
func chapterCollectEpisodeLabel(item PlayletChapterCollectData) string {
|
||||
return episodeText(item.TotalEpisode)
|
||||
}
|
||||
|
||||
// chapterCollectDesc 剧集收藏副标题。
|
||||
func chapterCollectDesc(item PlayletChapterCollectData) string {
|
||||
if t := strings.TrimSpace(item.ChapterTitle); t != "" {
|
||||
return t
|
||||
}
|
||||
if item.CurrentEpisode > 0 {
|
||||
return fmt.Sprintf("第%d集", item.CurrentEpisode)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// chapterCollectMeta 剧集收藏右侧文案:第 N 集。
|
||||
func chapterCollectMeta(item PlayletChapterCollectData) string {
|
||||
if item.CurrentEpisode > 0 {
|
||||
return fmt.Sprintf("第%d集", item.CurrentEpisode)
|
||||
}
|
||||
return updateStatusText(item.UpdateStatus)
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"videoweb/app/service/authser"
|
||||
"videoweb/app/service/upstream"
|
||||
"videoweb/pub/common/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
historyListPath = "/api/app/playlet/history/list"
|
||||
historyClearPath = "/api/app/playlet/history/clear"
|
||||
defaultHistoryPageSize = 40
|
||||
)
|
||||
|
||||
// PlayletHistoryData 对应 swagger proto.PlayletHistoryData
|
||||
// GET /api/app/playlet/history/list
|
||||
type PlayletHistoryData struct {
|
||||
ID int64 `json:"id"`
|
||||
Status int `json:"status"`
|
||||
IsFeatured bool `json:"isFeatured"`
|
||||
IsNew bool `json:"isNew"`
|
||||
IsRecommend bool `json:"isRecommend"`
|
||||
ShowOnBanner bool `json:"showOnBanner"`
|
||||
Banner string `json:"banner"`
|
||||
OnlineAt string `json:"onlineAt"`
|
||||
MediaCreatedAt string `json:"mediaCreatedAt"`
|
||||
MediaUpdatedAt string `json:"mediaUpdatedAt"`
|
||||
ThemeIDs []int64 `json:"themeIds"`
|
||||
ThemeNames []string `json:"themeNames"`
|
||||
ThemeSlugs []string `json:"themeSlugs"`
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
Desc string `json:"desc"`
|
||||
Anchor string `json:"anchor"`
|
||||
Cover string `json:"cover"`
|
||||
TagIDs []int64 `json:"tagIds"`
|
||||
TagNames []string `json:"tagNames"`
|
||||
TagSlugs []string `json:"tagSlugs"`
|
||||
TotalEpisode int `json:"totalEpisode"`
|
||||
UpdateStatus int `json:"updateStatus"`
|
||||
CountBrowse int64 `json:"countBrowse"`
|
||||
CountCollect int64 `json:"countCollect"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
WatchedCount int `json:"watchedCount"`
|
||||
UnwatchedCount int `json:"unwatchedCount"`
|
||||
LastChapterID int64 `json:"lastChapterId"`
|
||||
LastEpisode int `json:"lastEpisode"`
|
||||
IsFinished bool `json:"isFinished"`
|
||||
HistoryAt string `json:"historyAt"`
|
||||
}
|
||||
|
||||
// PlayletHistoryListResp 对应 swagger proto.PlayletHistoryListResp
|
||||
type PlayletHistoryListResp struct {
|
||||
List []PlayletHistoryData `json:"list"`
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
}
|
||||
|
||||
// PlayletHistoryClearResp 对应 swagger proto.PlayletHistoryClearResp
|
||||
type PlayletHistoryClearResp struct {
|
||||
DeletedCount int `json:"deletedCount"`
|
||||
}
|
||||
|
||||
// HistoryListQuery 观看历史分页参数。
|
||||
type HistoryListQuery struct {
|
||||
PageNum int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// Normalize 校正分页默认值。
|
||||
func (q *HistoryListQuery) Normalize() {
|
||||
if q.PageNum <= 0 {
|
||||
q.PageNum = 1
|
||||
}
|
||||
if q.PageSize <= 0 {
|
||||
q.PageSize = defaultHistoryPageSize
|
||||
}
|
||||
if q.PageSize > 100 {
|
||||
q.PageSize = 100
|
||||
}
|
||||
}
|
||||
|
||||
// FetchHistoryList 分页查询当前用户观看历史。
|
||||
// 第二个返回值为上游业务 code(网络错误时为 0)。
|
||||
func FetchHistoryList(c *gin.Context, q HistoryListQuery) (*PlayletHistoryListResp, int, error) {
|
||||
q.Normalize()
|
||||
params := url.Values{}
|
||||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||||
|
||||
var env APIEnvelope[PlayletHistoryListResp]
|
||||
if err := upstream.GetJSON(c, historyListPath+"?"+params.Encode(), &env); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if env.Code != 200 {
|
||||
return nil, env.Code, fmt.Errorf("playlet history list code=%d msg=%s", env.Code, env.Msg)
|
||||
}
|
||||
return &env.Data, env.Code, nil
|
||||
}
|
||||
|
||||
// FetchHistoryListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。
|
||||
func FetchHistoryListOrEmpty(c *gin.Context, q HistoryListQuery) *PlayletHistoryListResp {
|
||||
data, code, err := FetchHistoryList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
if retry, _ := authser.OnSessionExpired(c, code); retry {
|
||||
data, _, err = FetchHistoryList(c, q)
|
||||
if err == nil {
|
||||
if data == nil {
|
||||
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
log.Error("fetch playlet history list failed", log.E(err))
|
||||
return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
|
||||
}
|
||||
|
||||
// HistoryClear 清空当前用户观看历史。
|
||||
// POST /api/app/playlet/history/clear,无请求体。
|
||||
func HistoryClear(c *gin.Context) (*APIEnvelope[PlayletHistoryClearResp], error) {
|
||||
var env APIEnvelope[PlayletHistoryClearResp]
|
||||
if _, err := upstream.PostJSON(c, historyClearPath, nil, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func historyPageURL(pageNum int) string {
|
||||
if pageNum <= 1 {
|
||||
return "/history/"
|
||||
}
|
||||
return "/history/page/" + strconv.Itoa(pageNum) + "/"
|
||||
}
|
||||
|
||||
func HistoryCanonicalPath(q HistoryListQuery) string {
|
||||
q.Normalize()
|
||||
return historyPageURL(q.PageNum)
|
||||
}
|
||||
|
||||
// BuildHistoryPagination 观看历史分页。
|
||||
func BuildHistoryPagination(q HistoryListQuery, totalCount int64) PaginationVM {
|
||||
q.Normalize()
|
||||
return BuildPagination(func(p int) string {
|
||||
return historyPageURL(p)
|
||||
}, q.PageNum, q.PageSize, totalCount)
|
||||
}
|
||||
|
||||
// ParseHistoryListQuery 从 query 解析分页。
|
||||
func ParseHistoryListQuery(pageStr string) HistoryListQuery {
|
||||
q := HistoryListQuery{PageSize: defaultHistoryPageSize}
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(pageStr)); err == nil {
|
||||
q.PageNum = n
|
||||
}
|
||||
q.Normalize()
|
||||
return q
|
||||
}
|
||||
|
||||
func historyFinished(item PlayletHistoryData) bool {
|
||||
if item.IsFinished {
|
||||
return true
|
||||
}
|
||||
return item.TotalEpisode > 0 && item.UnwatchedCount <= 0 && item.WatchedCount > 0
|
||||
}
|
||||
|
||||
// historyStatus 卡片筛选状态:finished / unfinished。
|
||||
func historyStatus(item PlayletHistoryData) string {
|
||||
if historyFinished(item) {
|
||||
return "finished"
|
||||
}
|
||||
return "unfinished"
|
||||
}
|
||||
|
||||
// historyProgressText PC 卡片未看文案,使用接口 unwatchedCount / isFinished。
|
||||
func historyProgressText(item PlayletHistoryData) string {
|
||||
if historyFinished(item) {
|
||||
return "已看完"
|
||||
}
|
||||
if item.UnwatchedCount > 0 {
|
||||
return fmt.Sprintf("%d集未看", item.UnwatchedCount)
|
||||
}
|
||||
if item.LastEpisode > 0 {
|
||||
return fmt.Sprintf("看到第%d集", item.LastEpisode)
|
||||
}
|
||||
return "未看完"
|
||||
}
|
||||
|
||||
// historyWatchLine H5 进度行。
|
||||
func historyWatchLine(item PlayletHistoryData) string {
|
||||
if historyFinished(item) {
|
||||
if item.LastEpisode > 0 {
|
||||
return fmt.Sprintf("第%d集 · 已看完", item.LastEpisode)
|
||||
}
|
||||
return "已看完"
|
||||
}
|
||||
ep := "未开始"
|
||||
if item.LastEpisode > 0 {
|
||||
ep = fmt.Sprintf("第%d集", item.LastEpisode)
|
||||
}
|
||||
if item.UnwatchedCount > 0 {
|
||||
return ep + fmt.Sprintf(" · 还有%d集未看", item.UnwatchedCount)
|
||||
}
|
||||
return ep + " · 未看完"
|
||||
}
|
||||
|
||||
// historyTagText 历史卡片标签:仅展示 TagNames 第一项,无标签时不显示。
|
||||
func historyTagText(item PlayletHistoryData) string {
|
||||
return firstName(item.TagNames)
|
||||
}
|
||||
|
||||
// historyPlayURL 继续播放:优先最近观看剧集。
|
||||
func historyPlayURL(item PlayletHistoryData) string {
|
||||
return chapterPlayURL(item.Slug, item.ID, item.LastChapterID)
|
||||
}
|
||||
|
||||
// formatHistoryAt 最近观看时间展示。
|
||||
func formatHistoryAt(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
t, ok := parseHistoryTime(raw)
|
||||
if !ok {
|
||||
return raw
|
||||
}
|
||||
now := time.Now()
|
||||
startToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
startThat := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
dayDiff := int(startToday.Sub(startThat).Hours() / 24)
|
||||
hm := t.Format("15:04")
|
||||
switch {
|
||||
case dayDiff <= 0:
|
||||
return "今天 " + hm
|
||||
case dayDiff == 1:
|
||||
return "昨天 " + hm
|
||||
case dayDiff == 2:
|
||||
return "前天 " + hm
|
||||
case dayDiff < 7:
|
||||
return fmt.Sprintf("%d天前 %s", dayDiff, hm)
|
||||
case dayDiff < 14:
|
||||
return "一周前 " + hm
|
||||
default:
|
||||
return t.Format("2006-01-02 15:04")
|
||||
}
|
||||
}
|
||||
|
||||
func parseHistoryTime(raw string) (time.Time, bool) {
|
||||
layouts := []string{
|
||||
time.RFC3339Nano,
|
||||
time.RFC3339,
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02 15:04:05.000",
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
if t, err := time.ParseInLocation(layout, raw, time.Local); err == nil {
|
||||
return t, true
|
||||
}
|
||||
if t, err := time.Parse(layout, raw); err == nil {
|
||||
return t.In(time.Local), true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package playletser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"videoweb/app/service/upstream"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const likeSetPath = "/api/app/playlet/like/set"
|
||||
|
||||
// PlayletLikeSetReq 对应 swagger proto.PlayletLikeSetReq
|
||||
type PlayletLikeSetReq struct {
|
||||
ID int64 `json:"id"`
|
||||
IsLike bool `json:"isLike"`
|
||||
}
|
||||
|
||||
// PlayletLikeSetResp 对应 swagger proto.PlayletLikeSetResp
|
||||
type PlayletLikeSetResp struct {
|
||||
ID int64 `json:"id"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
IsLike int `json:"isLike"`
|
||||
}
|
||||
|
||||
// LikeSet 设置短剧点赞/取消点赞。
|
||||
func LikeSet(c *gin.Context, req PlayletLikeSetReq) (*APIEnvelope[PlayletLikeSetResp], error) {
|
||||
if req.ID <= 0 {
|
||||
return nil, fmt.Errorf("playlet like invalid id")
|
||||
}
|
||||
var env APIEnvelope[PlayletLikeSetResp]
|
||||
if _, err := upstream.PostJSON(c, likeSetPath, req, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
@@ -11,62 +11,50 @@ import (
|
||||
// TemplateFuncs 模板辅助:仅做展示格式化 / URL,不把接口字段映射成另一套名字。
|
||||
func TemplateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"detailURL": detailURL,
|
||||
"playURL": playURL,
|
||||
"chapterPlayURL": chapterPlayURL,
|
||||
"categoryURL": categoryURL,
|
||||
"categoryDetailURL": categoryDetailURL,
|
||||
"themeListURL": themeListURLByKey,
|
||||
"coverURL": coverURL,
|
||||
"formatHeat": formatHeat,
|
||||
"formatCollect": formatCollectHeat,
|
||||
"formatLikeCount": formatLikeCount,
|
||||
"episodeLabel": episodeText,
|
||||
"displayTag": displayTag,
|
||||
"firstTagName": firstTagName,
|
||||
"themeTagText": themeTagText,
|
||||
"tagListURL": tagListURL,
|
||||
"cardTag": cardTag,
|
||||
"cardTags": cardTags,
|
||||
"namedTags": namedTags,
|
||||
"namedThemeTags": namedThemeTags,
|
||||
"firstName": firstName,
|
||||
"rankClass": rankClass,
|
||||
"add": addInt,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"footerLinkURL": footerLinkURL,
|
||||
"footerLinkIsExternal": footerLinkIsExternal,
|
||||
"footerContactURL": footerContactURL,
|
||||
"footerContactName": footerContactName,
|
||||
"footerContactValue": footerContactValue,
|
||||
"navDisplayURL": navDisplayURL,
|
||||
"urlquery": url.QueryEscape,
|
||||
"pathescape": url.PathEscape,
|
||||
"metaLine": metaLine,
|
||||
"updateStatusText": updateStatusText,
|
||||
"browseText": browseText,
|
||||
"usableAnchor": usableAnchor,
|
||||
"playletYearText": playletYearText,
|
||||
"playletDateText": playletDateText,
|
||||
"isPlayletAllFree": isPlayletAllFree,
|
||||
"collectProgressText": collectProgressText,
|
||||
"collectTagText": collectTagText,
|
||||
"chapterCollectEpisodeLabel": chapterCollectEpisodeLabel,
|
||||
"chapterCollectDesc": chapterCollectDesc,
|
||||
"chapterCollectMeta": chapterCollectMeta,
|
||||
"historyProgressText": historyProgressText,
|
||||
"historyWatchLine": historyWatchLine,
|
||||
"historyTagText": historyTagText,
|
||||
"historyPlayURL": historyPlayURL,
|
||||
"historyStatus": historyStatus,
|
||||
"formatHistoryAt": formatHistoryAt,
|
||||
"formatShortDateTime": formatShortDateTime,
|
||||
"absInt64": absInt64Tpl,
|
||||
"absURL": absURLTpl,
|
||||
"canonicalOr": canonicalOr,
|
||||
"joinStrings": joinStrings,
|
||||
"dict": dict,
|
||||
"detailURL": detailURL,
|
||||
"playURL": playURL,
|
||||
"chapterPlayURL": chapterPlayURL,
|
||||
"categoryURL": categoryURL,
|
||||
"categoryDetailURL": categoryDetailURL,
|
||||
"themeListURL": themeListURLByKey,
|
||||
"coverURL": coverURL,
|
||||
"formatHeat": formatHeat,
|
||||
"formatCollect": formatCollectHeat,
|
||||
"formatLikeCount": formatLikeCount,
|
||||
"episodeLabel": episodeText,
|
||||
"displayTag": displayTag,
|
||||
"firstTagName": firstTagName,
|
||||
"themeTagText": themeTagText,
|
||||
"tagListURL": tagListURL,
|
||||
"cardTag": cardTag,
|
||||
"cardTags": cardTags,
|
||||
"namedTags": namedTags,
|
||||
"namedThemeTags": namedThemeTags,
|
||||
"firstName": firstName,
|
||||
"rankClass": rankClass,
|
||||
"add": addInt,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"footerLinkURL": footerLinkURL,
|
||||
"footerLinkIsExternal": footerLinkIsExternal,
|
||||
"footerContactURL": footerContactURL,
|
||||
"footerContactName": footerContactName,
|
||||
"footerContactValue": footerContactValue,
|
||||
"navDisplayURL": navDisplayURL,
|
||||
"urlquery": url.QueryEscape,
|
||||
"pathescape": url.PathEscape,
|
||||
"metaLine": metaLine,
|
||||
"updateStatusText": updateStatusText,
|
||||
"browseText": browseText,
|
||||
"usableAnchor": usableAnchor,
|
||||
"playletYearText": playletYearText,
|
||||
"playletDateText": playletDateText,
|
||||
"isPlayletAllFree": isPlayletAllFree,
|
||||
"absInt64": absInt64Tpl,
|
||||
"absURL": absURLTpl,
|
||||
"canonicalOr": canonicalOr,
|
||||
"joinStrings": joinStrings,
|
||||
"dict": dict,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,25 +303,6 @@ func absInt64Tpl(n int64) int64 {
|
||||
return n
|
||||
}
|
||||
|
||||
func formatShortDateTime(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || strings.HasPrefix(raw, "0001") {
|
||||
return ""
|
||||
}
|
||||
raw = strings.ReplaceAll(raw, "T", " ")
|
||||
if i := strings.Index(raw, "."); i > 0 {
|
||||
raw = raw[:i]
|
||||
}
|
||||
if i := strings.IndexAny(raw, "Z+"); i > 10 {
|
||||
raw = strings.TrimSpace(raw[:i])
|
||||
}
|
||||
if len(raw) >= 16 {
|
||||
raw = raw[:16]
|
||||
}
|
||||
raw = strings.ReplaceAll(raw, "-", "/")
|
||||
return raw
|
||||
}
|
||||
|
||||
func metaLine(item CommonData) string {
|
||||
cat := firstName(item.ThemeNames)
|
||||
if cat == "" {
|
||||
|
||||
Reference in New Issue
Block a user