New
This commit is contained in:
167
internal/domain/upstream/handler.go
Normal file
167
internal/domain/upstream/handler.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Filename: internal/domain/upstream/handler.go
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"gemini-balancer/internal/errors"
|
||||
"gemini-balancer/internal/models"
|
||||
"gemini-balancer/internal/response"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
// ------ DTOs and Validation ------
|
||||
type CreateUpstreamRequest struct {
|
||||
URL string `json:"url" binding:"required"`
|
||||
Weight int `json:"weight" binding:"omitempty,gte=1,lte=1000"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type UpdateUpstreamRequest struct {
|
||||
URL *string `json:"url"`
|
||||
Weight *int `json:"weight" binding:"omitempty,gte=1,lte=1000"`
|
||||
Status *string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
func isValidURL(rawURL string) bool {
|
||||
u, err := url.ParseRequestURI(rawURL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Scheme == "http" || u.Scheme == "https"
|
||||
}
|
||||
|
||||
// --- Handler ---
|
||||
|
||||
func (h *Handler) CreateUpstream(c *gin.Context) {
|
||||
var req CreateUpstreamRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error()))
|
||||
return
|
||||
}
|
||||
if !isValidURL(req.URL) {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrValidation, "Invalid URL format"))
|
||||
return
|
||||
}
|
||||
|
||||
upstream := models.UpstreamEndpoint{
|
||||
URL: req.URL,
|
||||
Weight: req.Weight,
|
||||
Status: req.Status,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
if err := h.service.Create(&upstream); err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
response.Created(c, upstream)
|
||||
}
|
||||
|
||||
func (h *Handler) ListUpstreams(c *gin.Context) {
|
||||
upstreams, err := h.service.List()
|
||||
if err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
response.Success(c, upstreams)
|
||||
}
|
||||
|
||||
func (h *Handler) GetUpstream(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
upstream, err := h.service.GetByID(id)
|
||||
if err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
response.Success(c, upstream)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateUpstream(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 UpdateUpstreamRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrInvalidJSON, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
upstream, err := h.service.GetByID(id)
|
||||
if err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
|
||||
if req.URL != nil {
|
||||
if !isValidURL(*req.URL) {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrValidation, "Invalid URL format"))
|
||||
return
|
||||
}
|
||||
upstream.URL = *req.URL
|
||||
}
|
||||
if req.Weight != nil {
|
||||
upstream.Weight = *req.Weight
|
||||
}
|
||||
if req.Status != nil {
|
||||
upstream.Status = *req.Status
|
||||
}
|
||||
if req.Description != nil {
|
||||
upstream.Description = *req.Description
|
||||
}
|
||||
|
||||
if err := h.service.Update(upstream); err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
response.Success(c, upstream)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteUpstream(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
response.Error(c, errors.NewAPIError(errors.ErrBadRequest, "Invalid ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
rowsAffected, err := h.service.Delete(id)
|
||||
if err != nil {
|
||||
response.Error(c, errors.ParseDBError(err))
|
||||
return
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
response.Error(c, errors.ErrResourceNotFound)
|
||||
return
|
||||
}
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// RegisterRoutes
|
||||
|
||||
func (h *Handler) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
upstreamRoutes := rg.Group("/upstreams")
|
||||
{
|
||||
upstreamRoutes.POST("/", h.CreateUpstream)
|
||||
upstreamRoutes.GET("/", h.ListUpstreams)
|
||||
upstreamRoutes.GET("/:id", h.GetUpstream)
|
||||
upstreamRoutes.PUT("/:id", h.UpdateUpstream)
|
||||
upstreamRoutes.DELETE("/:id", h.DeleteUpstream)
|
||||
}
|
||||
}
|
||||
36
internal/domain/upstream/module.go
Normal file
36
internal/domain/upstream/module.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Filename: internal/domain/upstream/module.go
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"gemini-balancer/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
service *Service
|
||||
handler *Handler
|
||||
}
|
||||
|
||||
func NewModule(db *gorm.DB) *Module {
|
||||
service := NewService(db)
|
||||
handler := NewHandler(service)
|
||||
|
||||
return &Module{
|
||||
service: service,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// === 领域暴露的公共API ===
|
||||
|
||||
// SelectActiveWeighted
|
||||
|
||||
func (m *Module) SelectActiveWeighted(upstreams []*models.UpstreamEndpoint) (*models.UpstreamEndpoint, error) {
|
||||
return m.service.SelectActiveWeighted(upstreams)
|
||||
}
|
||||
|
||||
func (m *Module) RegisterRoutes(router *gin.RouterGroup) {
|
||||
m.handler.RegisterRoutes(router)
|
||||
}
|
||||
84
internal/domain/upstream/service.go
Normal file
84
internal/domain/upstream/service.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Filename: internal/domain/upstream/service.go
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gemini-balancer/internal/models"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewService(db *gorm.DB) *Service {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
func (s *Service) SelectActiveWeighted(upstreams []*models.UpstreamEndpoint) (*models.UpstreamEndpoint, error) {
|
||||
activeUpstreams := make([]*models.UpstreamEndpoint, 0)
|
||||
totalWeight := 0
|
||||
for _, u := range upstreams {
|
||||
if u.Status == "active" {
|
||||
activeUpstreams = append(activeUpstreams, u)
|
||||
totalWeight += u.Weight
|
||||
}
|
||||
}
|
||||
if len(activeUpstreams) == 0 {
|
||||
return nil, errors.New("no active upstream endpoints available")
|
||||
}
|
||||
if totalWeight <= 0 || len(activeUpstreams) == 1 {
|
||||
return activeUpstreams[0], nil
|
||||
}
|
||||
randomWeight := rand.Intn(totalWeight)
|
||||
for _, u := range activeUpstreams {
|
||||
randomWeight -= u.Weight
|
||||
if randomWeight < 0 {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
return activeUpstreams[len(activeUpstreams)-1], nil
|
||||
}
|
||||
|
||||
// CRUD,供Handler调用
|
||||
|
||||
func (s *Service) Create(upstream *models.UpstreamEndpoint) error {
|
||||
if upstream.Weight == 0 {
|
||||
upstream.Weight = 100 // 默认权重
|
||||
}
|
||||
if upstream.Status == "" {
|
||||
upstream.Status = "active" // 默认状态
|
||||
}
|
||||
return s.db.Create(upstream).Error
|
||||
}
|
||||
|
||||
// List Service层只做数据库查询
|
||||
func (s *Service) List() ([]models.UpstreamEndpoint, error) {
|
||||
var upstreams []models.UpstreamEndpoint
|
||||
err := s.db.Find(&upstreams).Error
|
||||
return upstreams, err
|
||||
}
|
||||
|
||||
// GetByID Service层只做数据库查询
|
||||
func (s *Service) GetByID(id int) (*models.UpstreamEndpoint, error) {
|
||||
var upstream models.UpstreamEndpoint
|
||||
if err := s.db.First(&upstream, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &upstream, nil
|
||||
}
|
||||
|
||||
// Update Service层只做数据库更新
|
||||
func (s *Service) Update(upstream *models.UpstreamEndpoint) error {
|
||||
return s.db.Save(upstream).Error
|
||||
}
|
||||
|
||||
// Delete Service层只做数据库删除
|
||||
func (s *Service) Delete(id int) (int64, error) {
|
||||
result := s.db.Delete(&models.UpstreamEndpoint{}, id)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
Reference in New Issue
Block a user