Files
kenaito-media-picker/internal/api/media_controller.go
T
odboy 8979c744a8 refactor: 全面重构代码并加白话注释,移动全局并行,去掉去向/重复记录
- 重构后端与前端,全程大白话注释(不懂程序也能看懂)
- 移动阶段改为全局并行(MoveAll,工人数=CPU核心数)
- 去掉记录每个文件搬去哪了(删除去向回填,解决写库卡顿)
- 去掉重复文件标记与统计:重复判定改为 MD5+路径都相同才算,重复则原地忽略不移动;MD5 相同但路径不同仍照常搬
- 媒体库去掉归类目录列,看板去掉重复/归类分布统计
2025-05-11 21:05:35 +08:00

132 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 媒体库的两个接口:查列表、查统计。
// 「媒体库」页面查列表(带筛选和分页),「统计看板」页面查统计。
// 这两个接口只读数据库,不干别的活。
package api
import (
"fmt"
"net/http"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"kenaito-media-picker/internal/classify"
"kenaito-media-picker/internal/store"
)
// MediaController 媒体库相关接口的"窗口"。
type MediaController struct {
store *store.Store // 数据库(只读)
}
// NewMediaController 创建媒体库控制器,注入数据库。
func NewMediaController(st *store.Store) *MediaController {
return &MediaController{store: st}
}
// mediaListReq 媒体库列表的查询条件(前端「媒体库」页面传过来的)。
type mediaListReq struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
MediaType string `json:"mediaType"`
Orientation string `json:"orientation"`
Keyword string `json:"keyword"`
Codec string `json:"codec"`
Format string `json:"format"`
Resolution string `json:"resolution"`
SortField string `json:"sortField"`
SortOrder string `json:"sortOrder"`
}
// mediaView 列表里展示的一条记录:在数据库记录的基础上,
// 再补几个"现算"的字段(方向、分辨率档位、人类可读的体积和时长),方便页面直接显示。
type mediaView struct {
store.MediaInfo
Orientation string `json:"orientation"` // 横屏/竖屏/等比
Resolution string `json:"resolution"` // 分辨率档位(如 1080P)
SizeText string `json:"sizeText"` // 人类可读体积(如 1.5 MB)
DurationText string `json:"durationText"` // 人类可读时长(如 05:30)
}
// List 处理 POST /api/media/list:按条件分页查媒体库列表。
func (c *MediaController) List(ctx *gin.Context) {
var req mediaListReq
if err := ctx.ShouldBindJSON(&req); err != nil {
Error(ctx, http.StatusBadRequest, "请求参数错误:"+err.Error())
return
}
if req.Page < 1 {
req.Page = 1
}
q := store.MediaQuery{
MediaType: req.MediaType,
Orientation: req.Orientation,
Keyword: req.Keyword,
Codec: req.Codec,
Format: req.Format,
Resolution: req.Resolution,
Page: req.Page,
PageSize: req.PageSize,
SortField: req.SortField,
SortOrder: req.SortOrder,
}
// 查这一页的数据,同时查总条数(分页条要用)
list, err := c.store.ListMedia(q)
if err != nil {
Error(ctx, http.StatusInternalServerError, "查询媒体列表失败:"+err.Error())
return
}
total, err := c.store.CountMedia(q)
if err != nil {
Error(ctx, http.StatusInternalServerError, "统计媒体数量失败:"+err.Error())
return
}
// 每条记录补上"现算"的展示字段再返回
views := make([]interface{}, 0, len(list))
for _, m := range list {
views = append(views, toMediaView(m))
}
Success(ctx, map[string]interface{}{"list": views, "total": total})
}
// Stats 处理 POST /api/media/stats:统计看板要用的全部数据。
func (c *MediaController) Stats(ctx *gin.Context) {
st, err := c.store.Stats()
if err != nil {
Error(ctx, http.StatusInternalServerError, "统计失败:"+err.Error())
return
}
Success(ctx, st)
}
// toMediaView 给一条数据库记录补上展示用的派生字段。
func toMediaView(m store.MediaInfo) mediaView {
v := mediaView{MediaInfo: m}
// 宽高有效才算方向和分辨率档位
if m.Width > 0 && m.Height > 0 {
v.Orientation = classify.OrientationOf(m.Width, m.Height).String()
v.Resolution = classify.ResLabel(m.Width, m.Height)
}
if m.FileSize > 0 {
v.SizeText = humanize.Bytes(uint64(m.FileSize))
}
if m.Duration > 0 {
v.DurationText = formatDuration(m.Duration)
}
return v
}
// formatDuration 把秒数转成人类可读的时:分:秒。
func formatDuration(sec float64) string {
s := int(sec)
h := s / 3600
m := (s % 3600) / 60
secs := s % 60
if h > 0 {
return fmt.Sprintf("%d:%02d:%02d", h, m, secs)
}
return fmt.Sprintf("%02d:%02d", m, secs)
}