优化页面、修复BUG
This commit is contained in:
@@ -30,7 +30,7 @@ func GetVideoAlbums(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoAlbumsResponse(list))
|
||||
utils.Success(c, repositories.BuildVideoAlbumsResponse(list, true))
|
||||
}
|
||||
|
||||
// GetVideoAlbumByID 获取专辑详情
|
||||
@@ -49,7 +49,7 @@ func GetVideoAlbumByID(c *gin.Context) {
|
||||
utils.Error(c, 404, "Album not found")
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(album))
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(album, true))
|
||||
}
|
||||
|
||||
// GetAlbumVideos 获取专辑内视频
|
||||
@@ -90,7 +90,7 @@ func GetVideoByID(c *gin.Context) {
|
||||
utils.Error(c, 404, "Video not found")
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoResponse(video))
|
||||
utils.Success(c, repositories.BuildVideoDetailResponse(video))
|
||||
}
|
||||
|
||||
// ========== 管理 API — 分类 ==========
|
||||
@@ -158,7 +158,7 @@ func AdminGetVideoAlbums(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoAlbumsResponse(list))
|
||||
utils.Success(c, repositories.BuildVideoAlbumsResponse(list, false))
|
||||
}
|
||||
|
||||
func AdminCreateVideoAlbum(c *gin.Context) {
|
||||
@@ -171,7 +171,7 @@ func AdminCreateVideoAlbum(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(&item))
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(&item, false))
|
||||
}
|
||||
|
||||
func AdminUpdateVideoAlbum(c *gin.Context) {
|
||||
@@ -199,7 +199,7 @@ func AdminUpdateVideoAlbum(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(&req.VideoAlbum))
|
||||
utils.Success(c, repositories.BuildVideoAlbumResponse(&req.VideoAlbum, false))
|
||||
}
|
||||
|
||||
func AdminDeleteVideoAlbum(c *gin.Context) {
|
||||
@@ -277,38 +277,61 @@ func AdminGetVideos(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideosResponse(list))
|
||||
utils.Success(c, repositories.BuildAdminVideosResponse(list))
|
||||
}
|
||||
|
||||
func AdminCreateVideo(c *gin.Context) {
|
||||
var item models.Video
|
||||
if err := c.ShouldBindJSON(&item); err != nil {
|
||||
var req struct {
|
||||
models.Video
|
||||
AlbumIDs []uint `json:"albumIds"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, err.Error())
|
||||
return
|
||||
}
|
||||
if item.ID == "" {
|
||||
item.ID = "video_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
if req.Video.ID == "" {
|
||||
req.Video.ID = "video_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
if err := repositories.CreateVideo(&item); err != nil {
|
||||
if err := repositories.CreateVideo(&req.Video); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoResponse(&item))
|
||||
if req.AlbumIDs != nil {
|
||||
if err := repositories.SyncVideoAlbums(req.Video.ID, req.AlbumIDs); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
utils.Success(c, repositories.BuildAdminVideoResponse(&req.Video))
|
||||
}
|
||||
|
||||
func AdminUpdateVideo(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var item models.Video
|
||||
if err := c.ShouldBindJSON(&item); err != nil {
|
||||
var req struct {
|
||||
models.Video
|
||||
AlbumIDs []uint `json:"albumIds"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, err.Error())
|
||||
return
|
||||
}
|
||||
item.ID = id
|
||||
if err := repositories.UpdateVideo(&item); err != nil {
|
||||
req.Video.ID = id
|
||||
if err := repositories.UpdateVideo(&req.Video); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoResponse(&item))
|
||||
if req.AlbumIDs != nil {
|
||||
if err := repositories.SyncVideoAlbums(id, req.AlbumIDs); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
video, _ := repositories.GetVideoByID(id)
|
||||
if video == nil {
|
||||
utils.Error(c, 404, "Video not found")
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildAdminVideoResponse(video))
|
||||
}
|
||||
|
||||
func AdminDeleteVideo(c *gin.Context) {
|
||||
@@ -331,5 +354,5 @@ func AdminGetVideoByID(c *gin.Context) {
|
||||
utils.Error(c, 404, "Video not found")
|
||||
return
|
||||
}
|
||||
utils.Success(c, repositories.BuildVideoResponse(video))
|
||||
utils.Success(c, repositories.BuildAdminVideoResponse(video))
|
||||
}
|
||||
|
||||
@@ -83,17 +83,39 @@ func (va *VideoAlbum) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// VideoAlbumResponse 视频专辑 API 响应
|
||||
type VideoAlbumResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName,omitempty"`
|
||||
IsActive int `json:"isActive"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
VideoCount int64 `json:"videoCount,omitempty"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName,omitempty"`
|
||||
IsActive int `json:"isActive"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
VideoCount int64 `json:"videoCount,omitempty"`
|
||||
LatestVideoUpdatedAt string `json:"latestVideoUpdatedAt,omitempty"`
|
||||
PreviewVideos []VideoResponse `json:"previewVideos,omitempty"`
|
||||
}
|
||||
|
||||
// VideoAlbumNavContext 视频详情页专辑内前后导航
|
||||
type VideoAlbumNavContext struct {
|
||||
AlbumID uint `json:"albumId"`
|
||||
AlbumName string `json:"albumName"`
|
||||
PrevVideos []VideoResponse `json:"prevVideos"`
|
||||
NextVideos []VideoResponse `json:"nextVideos"`
|
||||
}
|
||||
|
||||
// VideoDetailResponse 视频详情 API 响应(含专辑导航)
|
||||
type VideoDetailResponse struct {
|
||||
VideoResponse
|
||||
AlbumContext *VideoAlbumNavContext `json:"albumContext,omitempty"`
|
||||
}
|
||||
|
||||
// AdminVideoResponse 管理端视频响应(含所属专辑 ID)
|
||||
type AdminVideoResponse struct {
|
||||
VideoResponse
|
||||
AlbumIDs []uint `json:"albumIds,omitempty"`
|
||||
}
|
||||
|
||||
// Video 视频模型
|
||||
|
||||
@@ -139,21 +139,57 @@ func DeleteVideoAlbum(id uint) error {
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
}
|
||||
|
||||
func GetAlbumVideoCount(albumID uint) int64 {
|
||||
// GetAlbumVideoCount 统计专辑内视频数量;publishedOnly 为 true 时仅统计已发布视频
|
||||
func GetAlbumVideoCount(albumID uint, publishedOnly bool) int64 {
|
||||
var count int64
|
||||
config.DB.Model(&models.AlbumVideo{}).
|
||||
q := config.DB.Model(&models.AlbumVideo{}).
|
||||
Joins("JOIN videos v ON v.id = album_videos.video_id").
|
||||
Where("album_videos.album_id = ? AND v.deleted_at = ? AND v.is_published = ?", albumID, 0, 1).
|
||||
Count(&count)
|
||||
Where("album_videos.album_id = ? AND v.deleted_at = ?", albumID, 0)
|
||||
if publishedOnly {
|
||||
q = q.Where("v.is_published = ?", 1)
|
||||
}
|
||||
q.Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
func BuildVideoAlbumResponse(item *models.VideoAlbum) models.VideoAlbumResponse {
|
||||
// GetAlbumLatestVideoUpdatedAt 获取专辑内视频最新更新时间戳
|
||||
func GetAlbumLatestVideoUpdatedAt(albumID uint, publishedOnly bool) int64 {
|
||||
var ts int64
|
||||
q := config.DB.Model(&models.Video{}).
|
||||
Select("COALESCE(MAX(videos.updated_at), 0)").
|
||||
Joins("JOIN album_videos av ON av.video_id = videos.id").
|
||||
Where("av.album_id = ? AND videos.deleted_at = ?", albumID, 0)
|
||||
if publishedOnly {
|
||||
q = q.Where("videos.is_published = ?", 1)
|
||||
}
|
||||
q.Scan(&ts)
|
||||
return ts
|
||||
}
|
||||
|
||||
// GetAlbumPreviewVideos 获取专辑预览视频列表(按 sort_order,最多 limit 条)
|
||||
func GetAlbumPreviewVideos(albumID uint, limit int, publishedOnly bool) ([]models.Video, error) {
|
||||
if limit <= 0 {
|
||||
limit = 5
|
||||
}
|
||||
var list []models.Video
|
||||
q := config.DB.Model(&models.Video{}).
|
||||
Joins("JOIN album_videos av ON av.video_id = videos.id").
|
||||
Where("av.album_id = ? AND videos.deleted_at = ?", albumID, 0)
|
||||
if publishedOnly {
|
||||
q = q.Where("videos.is_published = ?", 1)
|
||||
}
|
||||
err := q.Order("av.sort_order ASC, av.created_at ASC").Limit(limit).Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
// BuildVideoAlbumResponse 构建专辑响应;publishedOnly 控制统计与预览是否仅含已发布视频
|
||||
func BuildVideoAlbumResponse(item *models.VideoAlbum, publishedOnly bool) models.VideoAlbumResponse {
|
||||
categoryName := ""
|
||||
if cat, _ := GetVideoCategoryByID(item.CategoryID); cat != nil {
|
||||
categoryName = cat.Name
|
||||
}
|
||||
return models.VideoAlbumResponse{
|
||||
latestTs := GetAlbumLatestVideoUpdatedAt(item.ID, publishedOnly)
|
||||
resp := models.VideoAlbumResponse{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
@@ -164,14 +200,21 @@ func BuildVideoAlbumResponse(item *models.VideoAlbum) models.VideoAlbumResponse
|
||||
SortOrder: item.SortOrder,
|
||||
CreatedAt: formatTimestamp(item.CreatedAt),
|
||||
UpdatedAt: formatTimestamp(item.UpdatedAt),
|
||||
VideoCount: GetAlbumVideoCount(item.ID),
|
||||
VideoCount: GetAlbumVideoCount(item.ID, publishedOnly),
|
||||
}
|
||||
if latestTs > 0 {
|
||||
resp.LatestVideoUpdatedAt = formatTimestamp(latestTs)
|
||||
}
|
||||
if previews, err := GetAlbumPreviewVideos(item.ID, 5, publishedOnly); err == nil && len(previews) > 0 {
|
||||
resp.PreviewVideos = BuildVideosResponse(previews)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func BuildVideoAlbumsResponse(list []models.VideoAlbum) []models.VideoAlbumResponse {
|
||||
func BuildVideoAlbumsResponse(list []models.VideoAlbum, publishedOnly bool) []models.VideoAlbumResponse {
|
||||
res := make([]models.VideoAlbumResponse, 0, len(list))
|
||||
for i := range list {
|
||||
res = append(res, BuildVideoAlbumResponse(&list[i]))
|
||||
res = append(res, BuildVideoAlbumResponse(&list[i], publishedOnly))
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -318,3 +361,120 @@ func SyncAlbumVideos(albumID uint, videoIDs []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlbumIDsByVideoID 查询视频所属专辑 ID 列表
|
||||
func GetAlbumIDsByVideoID(videoID string) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := config.DB.Model(&models.AlbumVideo{}).
|
||||
Where("video_id = ?", videoID).
|
||||
Pluck("album_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
// GetPrimaryActiveAlbumForVideo 取视频关联的第一个启用专辑(按专辑 sort_order)
|
||||
func GetPrimaryActiveAlbumForVideo(videoID string) (*models.VideoAlbum, error) {
|
||||
var album models.VideoAlbum
|
||||
err := config.DB.Model(&models.VideoAlbum{}).
|
||||
Joins("JOIN album_videos av ON av.album_id = video_albums.id").
|
||||
Where("av.video_id = ? AND video_albums.deleted_at = ? AND video_albums.is_active = ?", videoID, 0, 1).
|
||||
Order("video_albums.sort_order ASC, video_albums.id ASC").
|
||||
First(&album).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return &album, err
|
||||
}
|
||||
|
||||
// GetAlbumVideoNeighbors 获取专辑内当前视频的前后邻居(按 sort_order)
|
||||
func GetAlbumVideoNeighbors(albumID uint, videoID string, before, after int, publishedOnly bool) (prev, next []models.Video, err error) {
|
||||
list, err := GetVideosByAlbumID(albumID, publishedOnly)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
idx := -1
|
||||
for i, v := range list {
|
||||
if v.ID == videoID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
start := idx - before
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
prev = list[start:idx]
|
||||
end := idx + after + 1
|
||||
if end > len(list) {
|
||||
end = len(list)
|
||||
}
|
||||
next = list[idx+1 : end]
|
||||
return prev, next, nil
|
||||
}
|
||||
|
||||
// BuildVideoDetailResponse 构建视频详情响应(含专辑前后导航)
|
||||
func BuildVideoDetailResponse(item *models.Video) models.VideoDetailResponse {
|
||||
resp := models.VideoDetailResponse{
|
||||
VideoResponse: BuildVideoResponse(item),
|
||||
}
|
||||
album, err := GetPrimaryActiveAlbumForVideo(item.ID)
|
||||
if err != nil || album == nil {
|
||||
return resp
|
||||
}
|
||||
prev, next, err := GetAlbumVideoNeighbors(album.ID, item.ID, 2, 2, true)
|
||||
if err != nil {
|
||||
return resp
|
||||
}
|
||||
resp.AlbumContext = &models.VideoAlbumNavContext{
|
||||
AlbumID: album.ID,
|
||||
AlbumName: album.Name,
|
||||
PrevVideos: BuildVideosResponse(prev),
|
||||
NextVideos: BuildVideosResponse(next),
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// GetAlbumIDsByVideoIDList 供管理端读取视频所属专辑
|
||||
func GetAlbumIDsByVideoIDList(videoID string) []uint {
|
||||
ids, err := GetAlbumIDsByVideoID(videoID)
|
||||
if err != nil {
|
||||
return []uint{}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// BuildAdminVideoResponse 构建管理端视频响应(含 albumIds)
|
||||
func BuildAdminVideoResponse(item *models.Video) models.AdminVideoResponse {
|
||||
return models.AdminVideoResponse{
|
||||
VideoResponse: BuildVideoResponse(item),
|
||||
AlbumIDs: GetAlbumIDsByVideoIDList(item.ID),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildAdminVideosResponse 批量构建管理端视频响应
|
||||
func BuildAdminVideosResponse(list []models.Video) []models.AdminVideoResponse {
|
||||
res := make([]models.AdminVideoResponse, 0, len(list))
|
||||
for i := range list {
|
||||
res = append(res, BuildAdminVideoResponse(&list[i]))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SyncVideoAlbums 同步视频所属专辑(先删后建)
|
||||
func SyncVideoAlbums(videoID string, albumIDs []uint) error {
|
||||
if err := config.DB.Where("video_id = ?", videoID).Delete(&models.AlbumVideo{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for i, albumID := range albumIDs {
|
||||
if albumID == 0 {
|
||||
continue
|
||||
}
|
||||
if err := AddVideoToAlbum(albumID, videoID, uint(i+1)); err != nil {
|
||||
log.Printf("SyncVideoAlbums error: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user