270 lines
7.2 KiB
Go
270 lines
7.2 KiB
Go
// Filename: internal/domain/proxy/handler.go
|
|
package proxy
|
|
|
|
import (
|
|
"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)
|
|
|
|
}
|
|
}
|
|
|
|
// --- 请求 DTO ---
|
|
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"`
|
|
}
|
|
|
|
// 单个检测的请求体 (与前端JS对齐)
|
|
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)
|
|
}
|
|
|
|
// publishAndInvalidate 统一事件发布和缓存失效逻辑
|
|
func (h *handler) publishAndInvalidate(proxyID uint, action string) {
|
|
go h.manager.invalidate()
|
|
go func() {
|
|
event := models.ProxyStatusChangedEvent{ProxyID: proxyID, Action: action}
|
|
eventData, _ := json.Marshal(event)
|
|
_ = h.store.Publish(models.TopicProxyStatusChanged, eventData)
|
|
}()
|
|
}
|
|
|
|
// 新的 Handler 方法和 DTO
|
|
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(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)
|
|
}
|