// Filename: internal/domain/proxy/handler.go package proxy import ( "context" "encoding/json" "gemini-balancer/internal/errors" "gemini-balancer/internal/models" "gemini-balancer/internal/response" "gemini-balancer/internal/settings" "gemini-balancer/internal/store" "strconv" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type handler struct { db *gorm.DB manager *manager store store.Store settings *settings.SettingsManager } func newHandler(db *gorm.DB, m *manager, s store.Store, sp *settings.SettingsManager) *handler { return &handler{ db: db, manager: m, store: s, settings: sp, } } // === 领域暴露的公共API === func (h *handler) registerRoutes(rg *gin.RouterGroup) { proxyRoutes := rg.Group("/proxies") { proxyRoutes.PUT("/sync", h.SyncProxies) proxyRoutes.POST("/check", h.CheckSingleProxy) proxyRoutes.POST("/check-all", h.CheckAllProxies) proxyRoutes.POST("/", h.CreateProxyConfig) proxyRoutes.GET("/", h.ListProxyConfigs) proxyRoutes.GET("/:id", h.GetProxyConfig) proxyRoutes.PUT("/:id", h.UpdateProxyConfig) proxyRoutes.DELETE("/:id", h.DeleteProxyConfig) } } type CreateProxyConfigRequest struct { Address string `json:"address" binding:"required"` Protocol string `json:"protocol" binding:"required,oneof=http https socks5"` Status string `json:"status" binding:"omitempty,oneof=active inactive"` Description string `json:"description"` } type UpdateProxyConfigRequest struct { Address *string `json:"address"` Protocol *string `json:"protocol" binding:"omitempty,oneof=http https socks5"` Status *string `json:"status" binding:"omitempty,oneof=active inactive"` Description *string `json:"description"` } type CheckSingleProxyRequest struct { Proxy string `json:"proxy" binding:"required"` } type CheckAllProxiesRequest struct { Proxies []string `json:"proxies" binding:"required"` } // --- Handler 方法 --- func (h *handler) CreateProxyConfig(c *gin.Context) { var req CreateProxyConfigRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } if req.Status == "" { req.Status = "active" } proxyConfig := models.ProxyConfig{ Address: req.Address, Protocol: req.Protocol, Status: req.Status, Description: req.Description, } if err := h.db.Create(&proxyConfig).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } h.publishAndInvalidate(proxyConfig.ID, "created") response.Created(c, proxyConfig) } func (h *handler) ListProxyConfigs(c *gin.Context) { var proxyConfigs []models.ProxyConfig if err := h.db.Find(&proxyConfigs).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Success(c, proxyConfigs) } func (h *handler) GetProxyConfig(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } var proxyConfig models.ProxyConfig if err := h.db.First(&proxyConfig, id).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } response.Success(c, proxyConfig) } func (h *handler) UpdateProxyConfig(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } var req UpdateProxyConfigRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } var proxyConfig models.ProxyConfig if err := h.db.First(&proxyConfig, id).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } if req.Address != nil { proxyConfig.Address = *req.Address } if req.Protocol != nil { proxyConfig.Protocol = *req.Protocol } if req.Status != nil { proxyConfig.Status = *req.Status } if req.Description != nil { proxyConfig.Description = *req.Description } if err := h.db.Save(&proxyConfig).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } h.publishAndInvalidate(uint(id), "updated") response.Success(c, proxyConfig) } func (h *handler) DeleteProxyConfig(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format")) return } var count int64 if err := h.db.Model(&models.APIKey{}).Where("proxy_id = ?", id).Count(&count).Error; err != nil { response.Error(c, errors.ParseDBError(err)) return } if count > 0 { response.Error(c, errors.NewAPIError(errors.ErrDuplicateResource, "Cannot delete proxy config that is still in use by API keys")) return } result := h.db.Delete(&models.ProxyConfig{}, id) if result.Error != nil { response.Error(c, errors.ParseDBError(result.Error)) return } if result.RowsAffected == 0 { response.Error(c, errors.ErrResourceNotFound) return } h.publishAndInvalidate(uint(id), "deleted") response.NoContent(c) } func (h *handler) publishAndInvalidate(proxyID uint, action string) { go h.manager.invalidate() go func() { ctx := context.Background() event := models.ProxyStatusChangedEvent{ProxyID: proxyID, Action: action} eventData, _ := json.Marshal(event) _ = h.store.Publish(ctx, models.TopicProxyStatusChanged, eventData) }() } type SyncProxiesRequest struct { Proxies []string `json:"proxies"` } func (h *handler) SyncProxies(c *gin.Context) { var req SyncProxiesRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } taskStatus, err := h.manager.SyncProxiesInBackground(c.Request.Context(), req.Proxies) if err != nil { if errors.Is(err, ErrTaskConflict) { response.Error(c, errors.NewAPIError(errors.ErrTaskInProgress, err.Error())) } else { response.Error(c, errors.NewAPIError(errors.ErrInternalServer, err.Error())) } return } response.Success(c, gin.H{ "message": "Proxy synchronization task started.", "task": taskStatus, }) } func (h *handler) CheckSingleProxy(c *gin.Context) { var req CheckSingleProxyRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } cfg := h.settings.GetSettings() timeout := time.Duration(cfg.ProxyCheckTimeoutSeconds) * time.Second result := h.manager.CheckSingleProxy(req.Proxy, timeout) response.Success(c, result) } func (h *handler) CheckAllProxies(c *gin.Context) { var req CheckAllProxiesRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error())) return } cfg := h.settings.GetSettings() timeout := time.Duration(cfg.ProxyCheckTimeoutSeconds) * time.Second concurrency := cfg.ProxyCheckConcurrency if concurrency <= 0 { concurrency = 5 } results := h.manager.CheckMultipleProxies(req.Proxies, timeout, concurrency) response.Success(c, results) }