Files
gemini-banlancer/internal/domain/proxy/handler.go
2025-11-22 14:20:05 +08:00

264 lines
6.9 KiB
Go

// 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)
}