Files
web_huangguo2_h5/app/api/web/history.go
T
谢宇宁andClaude Opus 5.5 0fbdba5ccf Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:47:47 +07:00

376 lines
12 KiB
Go

package web
import (
"fmt"
"net/http"
"sort"
"strings"
"time"
"videoweb/app/service/authser"
"videoweb/app/service/playletser"
"videoweb/app/service/videoser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type historyEntry struct {
ID int64
Title string
Current int
Total int
EpisodeText string
Genre string
Time string
Image string
Progress string
Kind string
URL string
sortAt time.Time
}
type historyGroup struct {
Title string
Entries []historyEntry
}
// History 观看历史页:短剧 GET /api/app/playlet/history/list + 剧集 GET /api/app/video/history/list。
// 文档:
// http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_history_list
// http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_history_list
func History(c *gin.Context) {
page, validPage := accountPageNum(c)
if !validPage {
NotFound(c)
return
}
data := gin.H{"title": "观看历史 - 黄果短剧", "navActive": "history", "accountTab": "history"}
responses := gin.H{}
loadAccountSummary(c, data, responses)
pageSize := 20
var entries []historyEntry
var totalCount int64
if useragent.TokenFromRequest(c.Request) == "" {
responses["playlet/history/list"] = gin.H{"error": "暂无登录会话"}
responses["video/history/list"] = gin.H{"error": "暂无登录会话"}
} else {
playletQuery := playletser.HistoryListQuery{PageNum: page, PageSize: pageSize}
playletList, playletCode, playletErr := playletser.FetchHistoryList(c, playletQuery)
if retry, _ := authser.OnSessionExpired(c, playletCode); retry {
playletList, playletCode, playletErr = playletser.FetchHistoryList(c, playletQuery)
}
if playletErr == nil && playletList != nil {
responses["playlet/history/list"] = gin.H{"code": 200, "data": playletList}
entries = append(entries, playletHistoryEntries(playletList.List)...)
totalCount += playletList.TotalCount
} else if playletErr != nil {
responses["playlet/history/list"] = gin.H{"error": "加载失败", "msg": playletErr.Error()}
log.Error("fetch playlet history page failed", log.E(playletErr))
} else {
responses["playlet/history/list"] = gin.H{"code": playletCode, "data": playletList}
}
videoQuery := videoser.HistoryListQuery{PageNum: page, PageSize: pageSize}
videoList, videoCode, videoErr := videoser.FetchHistoryList(c, videoQuery)
if retry, _ := authser.OnSessionExpired(c, videoCode); retry {
videoList, videoCode, videoErr = videoser.FetchHistoryList(c, videoQuery)
}
if videoErr == nil && videoList != nil {
responses["video/history/list"] = gin.H{"code": 200, "data": videoList}
entries = append(entries, videoHistoryEntries(videoList.List)...)
totalCount += videoList.TotalCount
} else if videoErr != nil {
responses["video/history/list"] = gin.H{"error": "加载失败", "msg": videoErr.Error()}
log.Error("fetch video history page failed", log.E(videoErr))
} else {
responses["video/history/list"] = gin.H{"code": videoCode, "data": videoList}
}
if playletErr != nil && videoErr != nil {
data["historyUnavailable"] = true
}
}
data["paging"] = makeAccountPage("/history", page, pageSize, totalCount)
data["groups"] = groupHistoryEntries(entries)
data["historyResponse"] = responses["playlet/history/list"]
data["playletHistoryResponse"] = responses["playlet/history/list"]
data["videoHistoryResponse"] = responses["video/history/list"]
data["historyDebug"] = responses
HTMLOK(c, "history.html", data)
}
// HistoryClear BFF:代理 POST /api/app/playlet/history/clear。
// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_history_clear
func HistoryClear(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
env, err := playletser.HistoryClear(c)
if err != nil {
log.Error("clear playlet history failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "清空失败,请稍后重试", "data": nil})
return
}
if env == nil {
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "清空失败,请稍后重试", "data": nil})
return
}
if env.Code != 200 {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "清空失败,请稍后重试"
}
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data})
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data})
}
// VideoHistoryClear BFF:代理 POST /api/app/video/history/clear。
// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_history_clear
func VideoHistoryClear(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
env, err := videoser.HistoryClear(c)
if err != nil {
log.Error("clear video history failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "清空失败,请稍后重试", "data": nil})
return
}
if env == nil {
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "清空失败,请稍后重试", "data": nil})
return
}
if env.Code != 200 {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "清空失败,请稍后重试"
}
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data})
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data})
}
// HistoryDelete BFF:代理 POST /api/app/playlet/history/delete。
// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_history_delete
func HistoryDelete(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
var body playletser.PlayletHistoryDeleteReq
if err := c.ShouldBindJSON(&body); err != nil || body.PlayletID <= 0 {
c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "playletId required", "tip": "参数错误", "data": nil})
return
}
env, err := playletser.HistoryDelete(c, body)
if err != nil {
log.Error("delete playlet history failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "删除失败,请稍后重试", "data": nil})
return
}
if env == nil {
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "删除失败,请稍后重试", "data": nil})
return
}
if env.Code != 200 {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "删除失败,请稍后重试"
}
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data})
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data})
}
// VideoHistoryDelete BFF:代理 POST /api/app/video/history/delete。
// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_history_delete
func VideoHistoryDelete(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
var body videoser.VideoHistoryDeleteReq
if err := c.ShouldBindJSON(&body); err != nil || body.VideoID <= 0 {
c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "videoId required", "tip": "参数错误", "data": nil})
return
}
env, err := videoser.HistoryDelete(c, body)
if err != nil {
log.Error("delete video history failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "删除失败,请稍后重试", "data": nil})
return
}
if env == nil {
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "删除失败,请稍后重试", "data": nil})
return
}
if env.Code != 200 {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "删除失败,请稍后重试"
}
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data})
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data})
}
func playletHistoryEntries(items []playletser.PlayletHistoryData) []historyEntry {
out := make([]historyEntry, 0, len(items))
for _, item := range items {
title := strings.TrimSpace(item.PlayName)
if title == "" {
title = strings.TrimSpace(item.Title)
}
if title == "" {
continue
}
progress := 0
if item.TotalEpisode > 0 {
progress = item.WatchedCount * 100 / item.TotalEpisode
}
if progress < 0 {
progress = 0
}
if progress > 100 {
progress = 100
}
out = append(out, historyEntry{
ID: item.ID,
Title: title,
Current: item.LastEpisode,
Total: item.TotalEpisode,
Genre: strings.Join(item.ThemeNames, " "),
Time: playletser.FormatHistoryListTime(item.HistoryAt),
Image: profileCover(item.Cover),
Progress: fmt.Sprintf("%d%%", progress),
Kind: "short",
URL: playletser.ChapterPlayPath(item.Slug, item.ID, item.LastChapterID),
sortAt: historySortAt(item.HistoryAt),
})
}
return out
}
func videoHistoryEntries(items []videoser.VideoHistoryData) []historyEntry {
out := make([]historyEntry, 0, len(items))
for _, item := range items {
title := strings.TrimSpace(item.Title)
if title == "" {
continue
}
out = append(out, historyEntry{
ID: item.ID,
Title: title,
EpisodeText: videoHistoryEpisodeText(item),
Genre: strings.Join(item.Keyworld, " "),
Time: playletser.FormatHistoryListTime(item.HistoryAt),
Image: profileCover(item.CoverImg),
Progress: "",
Kind: "series",
URL: videoser.VideoPlayURL(item.Slug, item.ID),
sortAt: historySortAt(item.HistoryAt),
})
}
return out
}
func videoHistoryEpisodeText(item videoser.VideoHistoryData) string {
if item.VideoTime > 0 {
return formatVideoDuration(item.VideoTime)
}
return "剧集"
}
func formatVideoDuration(sec int) string {
if sec <= 0 {
return ""
}
h := sec / 3600
m := (sec % 3600) / 60
s := sec % 60
if h > 0 {
return fmt.Sprintf("%d:%02d:%02d", h, m, s)
}
return fmt.Sprintf("%d:%02d", m, s)
}
func historySortAt(raw string) time.Time {
t, ok := playletser.ParseHistoryTime(raw)
if !ok {
return time.Time{}
}
return t
}
func groupHistoryEntries(entries []historyEntry) []historyGroup {
if len(entries) == 0 {
return nil
}
sort.SliceStable(entries, func(i, j int) bool {
if entries[i].sortAt.Equal(entries[j].sortAt) {
return entries[i].ID > entries[j].ID
}
return entries[i].sortAt.After(entries[j].sortAt)
})
order := []string{"今天", "昨天", "更早"}
buckets := map[string][]historyEntry{
"今天": {},
"昨天": {},
"更早": {},
}
for _, entry := range entries {
bucket := historyBucketFromTime(entry.sortAt)
buckets[bucket] = append(buckets[bucket], entry)
}
groups := make([]historyGroup, 0, 3)
for _, title := range order {
if len(buckets[title]) == 0 {
continue
}
groups = append(groups, historyGroup{Title: title, Entries: buckets[title]})
}
return groups
}
func historyBucketFromTime(t time.Time) string {
if t.IsZero() {
return "更早"
}
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)
switch {
case dayDiff <= 0:
return "今天"
case dayDiff == 1:
return "昨天"
default:
return "更早"
}
}