// Package handler — 下载客户端管理 HTTP 端点。 package handler import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" "go.uber.org/zap" "github.com/ShukeBta/MediaStationGo/internal/model" "github.com/ShukeBta/MediaStationGo/internal/service" ) // DownloadClientHandler 处理下载客户端的 CRUD 操作。 type DownloadClientHandler struct { svc *service.Container log *zap.Logger } // NewDownloadClientHandler 创建下载客户端处理器。 func NewDownloadClientHandler(svc *service.Container, log *zap.Logger) *DownloadClientHandler { return &DownloadClientHandler{svc: svc, log: log} } // downloadClientCreateRequest 创建下载客户端请求体。 type downloadClientCreateRequest struct { Name string `json:"name" binding:"required"` Type string `json:"type" binding:"required,oneof=qbittorrent transmission aria2"` Host string `json:"host" binding:"required"` Username string `json:"username"` Password string `json:"password"` IsDefault bool `json:"is_default"` Extra map[string]string `json:"extra,omitempty"` } // downloadClientUpdateRequest 更新下载客户端请求体。 type downloadClientUpdateRequest struct { Name string `json:"name"` Type string `json:"type" binding:"omitempty,oneof=qbittorrent transmission aria2"` Host string `json:"host"` Username string `json:"username"` Password string `json:"password"` IsDefault *bool `json:"is_default"` Enabled *bool `json:"enabled"` Extra map[string]string `json:"extra,omitempty"` } // Create 创建新的下载客户端。 func (h *DownloadClientHandler) Create(c *gin.Context) { var req downloadClientCreateRequest if err := c.ShouldBindJSON(&req); err != nil { Error(c, http.StatusBadRequest, ErrInvalidParams, err.Error()) return } ctx := c.Request.Context() // 加密密码 password := req.Password if password != "" && h.svc.Crypto != nil { password = h.svc.Crypto.Encrypt(password) } // 加密 Extra 配置 extraStr := "" if len(req.Extra) > 0 { extraJSON, _ := json.Marshal(req.Extra) extraStr = string(extraJSON) if h.svc.Crypto != nil { extraStr = h.svc.Crypto.Encrypt(extraStr) } } // 如果设为默认,先清除其他默认 if req.IsDefault { _ = h.svc.Repo.DownloadClient.ClearDefault(ctx) } client := &model.DownloadClient{ Name: req.Name, Type: req.Type, Host: req.Host, Username: req.Username, Password: password, IsDefault: req.IsDefault, Enabled: true, Extra: extraStr, } if err := h.svc.Repo.DownloadClient.Create(ctx, client); err != nil { Error(c, http.StatusInternalServerError, ErrInternal, "创建失败: "+err.Error()) return } // 热插拔:加载新客户端 go func() { if initErr := h.svc.DownloadMgr.AddClient(ctx, client); initErr != nil { h.log.Warn("failed to hot-add download client", zap.Error(initErr)) } }() Success(c, client) } // List 返回所有下载客户端。 func (h *DownloadClientHandler) List(c *gin.Context) { ctx := c.Request.Context() clients, err := h.svc.Repo.DownloadClient.List(ctx) if err != nil { Error(c, http.StatusInternalServerError, ErrInternal, "查询失败") return } Success(c, clients) } // Get 返回指定下载客户端详情。 func (h *DownloadClientHandler) Get(c *gin.Context) { id := c.Param("id") ctx := c.Request.Context() client, err := h.svc.Repo.DownloadClient.FindByID(ctx, id) if err != nil || client == nil { Error(c, http.StatusNotFound, ErrNotFound, "客户端不存在") return } Success(c, client) } // Update 更新下载客户端。 func (h *DownloadClientHandler) Update(c *gin.Context) { id := c.Param("id") var req downloadClientUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { Error(c, http.StatusBadRequest, ErrInvalidParams, err.Error()) return } ctx := c.Request.Context() client, err := h.svc.Repo.DownloadClient.FindByID(ctx, id) if err != nil || client == nil { Error(c, http.StatusNotFound, ErrNotFound, "客户端不存在") return } if req.Name != "" { client.Name = req.Name } if req.Type != "" { client.Type = req.Type } if req.Host != "" { client.Host = req.Host } if req.Username != "" { client.Username = req.Username } if req.Password != "" { if h.svc.Crypto != nil { client.Password = h.svc.Crypto.Encrypt(req.Password) } else { client.Password = req.Password } } if req.IsDefault != nil && *req.IsDefault { _ = h.svc.Repo.DownloadClient.ClearDefault(ctx) client.IsDefault = *req.IsDefault } if req.Enabled != nil { client.Enabled = *req.Enabled } if len(req.Extra) > 0 { extraJSON, _ := json.Marshal(req.Extra) extraStr := string(extraJSON) if h.svc.Crypto != nil { client.Extra = h.svc.Crypto.Encrypt(extraStr) } else { client.Extra = extraStr } } if err := h.svc.Repo.DownloadClient.Update(ctx, client); err != nil { Error(c, http.StatusInternalServerError, ErrInternal, "更新失败") return } // 热更新适配器 go func() { if updateErr := h.svc.DownloadMgr.UpdateClient(ctx, client); updateErr != nil { h.log.Warn("failed to hot-update download client", zap.Error(updateErr)) } }() Success(c, client) } // Delete 删除下载客户端。 func (h *DownloadClientHandler) Delete(c *gin.Context) { id := c.Param("id") ctx := c.Request.Context() _, err := h.svc.Repo.DownloadClient.FindByID(ctx, id) if err != nil { Error(c, http.StatusNotFound, ErrNotFound, "客户端不存在") return } if delErr := h.svc.Repo.DownloadClient.Delete(ctx, id); delErr != nil { Error(c, http.StatusInternalServerError, ErrInternal, "删除失败") return } // 热移除 h.svc.DownloadMgr.RemoveClient(id) SuccessWithMessage(c, "已删除", nil) } // Test 测试下载客户端连接。 func (h *DownloadClientHandler) Test(c *gin.Context) { id := c.Param("id") ctx := c.Request.Context() client, err := h.svc.Repo.DownloadClient.FindByID(ctx, id) if err != nil || client == nil { Error(c, http.StatusNotFound, ErrNotFound, "客户端不存在") return } if err := h.svc.DownloadMgr.TestConnection(ctx, client); err != nil { Error(c, http.StatusBadRequest, ErrExternal, "连接测试失败: "+err.Error()) return } SuccessWithMessage(c, "连接成功", nil) }