spark-server/internal/transport/http/handler/music/handler.go
DESKTOP-L8OUL9F\gfl0034 53426af122 feat: 多广场 channel、音乐公开 API 与订阅 package_name
支持 square/squarenovel/squaresteps/squarehealth 实例与 category 筛选;音乐增加免登录 grouped/track;订阅套餐按 package_name 区分;补 migration 0030/0031 与 OpenAPI。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 09:02:28 +08:00

135 lines
3.3 KiB
Go

package musichandler
import (
"strconv"
"github.com/gin-gonic/gin"
musiclogic "spark-server/internal/module/music/logic"
"spark-server/internal/pkg/errs"
"spark-server/internal/transport/http/respond"
)
type Handler struct {
svc *musiclogic.Service
}
func New(svc *musiclogic.Service) *Handler {
return &Handler{svc: svc}
}
// Playlist GET /v1/music/playlist — public curated list + category tree.
// Query categoryId>0 filters tracks belonging to that category or its descendants.
// Query requireMember=true|false filters member-only / free tracks.
func (h *Handler) Playlist(c *gin.Context) {
if h == nil || h.svc == nil || !h.svc.Ready() {
respond.JSONError(c, errs.ErrAvailable)
return
}
q, err := parsePlaylistQuery(c)
if err != nil {
respond.JSONError(c, err)
return
}
out, err := h.svc.Playlist(c.Request.Context(), q)
if err != nil {
respond.JSONError(c, err)
return
}
respond.JSON(c, out)
}
// PlaylistGrouped GET /v1/music/playlist/grouped — leaf categories with nested items.
func (h *Handler) PlaylistGrouped(c *gin.Context) {
if h == nil || h.svc == nil || !h.svc.Ready() {
respond.JSONError(c, errs.ErrAvailable)
return
}
var parentID int64
if raw := c.Query("parentId"); raw != "" {
n, err := strconv.ParseInt(raw, 10, 64)
if err != nil || n < 0 {
respond.JSONError(c, errs.ErrInvalidArgument.WithMsg("parentId 无效"))
return
}
parentID = n
}
out, err := h.svc.PlaylistGrouped(c.Request.Context(), parentID)
if err != nil {
respond.JSONError(c, err)
return
}
respond.JSON(c, out)
}
// GetTrack GET /v1/music/tracks/:id — single enabled track (public).
func (h *Handler) GetTrack(c *gin.Context) {
if h == nil || h.svc == nil || !h.svc.Ready() {
respond.JSONError(c, errs.ErrAvailable)
return
}
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil || id <= 0 {
respond.JSONError(c, errs.ErrInvalidArgument.WithMsg("id 无效"))
return
}
out, err := h.svc.GetPublic(c.Request.Context(), id)
if err != nil {
respond.JSONError(c, err)
return
}
respond.JSON(c, out)
}
// Categories GET /v1/music/categories — enabled category tree.
func (h *Handler) Categories(c *gin.Context) {
if h == nil || h.svc == nil || !h.svc.Ready() {
respond.JSONError(c, errs.ErrAvailable)
return
}
out, err := h.svc.CategoryTree(c.Request.Context(), true)
if err != nil {
respond.JSONError(c, err)
return
}
respond.JSON(c, gin.H{"categories": out})
}
// Settings GET /v1/music/settings — global member preview seconds only.
func (h *Handler) Settings(c *gin.Context) {
if h == nil || h.svc == nil || !h.svc.Ready() {
respond.JSONError(c, errs.ErrAvailable)
return
}
out, err := h.svc.Settings(c.Request.Context())
if err != nil {
respond.JSONError(c, err)
return
}
respond.JSON(c, out)
}
func parsePlaylistQuery(c *gin.Context) (musiclogic.PlaylistQuery, error) {
var q musiclogic.PlaylistQuery
if raw := c.Query("categoryId"); raw != "" {
n, err := strconv.ParseInt(raw, 10, 64)
if err != nil || n < 0 {
return q, errs.ErrInvalidArgument.WithMsg("categoryId 无效")
}
q.CategoryID = n
}
if raw := c.Query("requireMember"); raw != "" {
switch raw {
case "true", "1":
v := true
q.RequireMember = &v
case "false", "0":
v := false
q.RequireMember = &v
default:
return q, errs.ErrInvalidArgument.WithMsg("requireMember 须为 true|false")
}
}
return q, nil
}