Files
kenaito-media-picker/internal/api/media_controller.go
T

168 lines
5.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"
"os"
"path/filepath"
"strings"
"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"`
NsfwOnly interface{} `json:"nsfwOnly"`
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,
NsfwOnly: req.NsfwOnly == true, // interface{} 转 bool:只有明确的 true 才算
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)
}
// Preview 处理 GET /api/media/preview?path=xxx:预览图片文件。
// 只允许预览图片类型(jpeg/jpg/png/gif/webp/bmp),其他类型返回 403。
func (c *MediaController) Preview(ctx *gin.Context) {
path := strings.TrimSpace(ctx.Query("path"))
if path == "" {
Error(ctx, http.StatusBadRequest, "缺少 path 参数")
return
}
// 检查文件是否存在
info, err := os.Stat(path)
if err != nil {
Error(ctx, http.StatusNotFound, "文件不存在: "+path)
return
}
// 只允许预览图片
ext := strings.ToLower(filepath.Ext(info.Name()))
allowedExts := map[string]bool{
".jpg": true, ".jpeg": true, ".png": true,
".gif": true, ".webp": true, ".bmp": true,
}
if !allowedExts[ext] {
Error(ctx, http.StatusForbidden, "仅支持预览图片文件")
return
}
// 返回图片文件
ctx.File(path)
}
// 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)
}