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 "更早" } }