浏览代码

feat(gameShield): 重构游戏盾后台逻辑

- 新增 GameShieldBackend 模型和相关服务- 优化 FormatBackendData 方法,支持数组格式请求
- 添加旧数据格式转换功能
- 实现游戏盾后台数据排序
- 新增游戏盾后台数据保存和编辑功能
fusu 3 月之前
父节点
当前提交
4f422d88b7

+ 1 - 0
api/v1/GameShield.go

@@ -37,6 +37,7 @@ type GetGameShieldRequiredResponse struct {
 	ExpiredAt string
 	ExpiredAt string
 	Backend   string
 	Backend   string
 	Cookie    string
 	Cookie    string
+	RuleId    int
 }
 }
 
 
 type KeyAndFieldResponse struct {
 type KeyAndFieldResponse struct {

+ 34 - 0
api/v1/gameShieldBackend.go

@@ -0,0 +1,34 @@
+package v1
+
+import "time"
+
+type GameShieldBackendRequest struct {
+	Id              int       `json:"id" form:"id"`
+	SourceMachineIP string    `json:"source_machineIP" form:"source_machineIP"`
+	Protocol        string    `json:"protocol" form:"protocol"`
+	ProxyAddr       string    `json:"proxy_addr" form:"proxy_addr"`
+	ConnectPort     string    `json:"connect_port" form:"connect_port" binding:"required"`
+	SdkPort         string    `json:"sdk_port" form:"sdk_port"`
+	PublicIp        string    `json:"public_ip" form:"public_ip"`
+	Type            string    `json:"type" form:"type"`
+	CreatedAt       time.Time `json:"created_at" form:"created_at"`
+	UpdatedAt       time.Time `json:"updated_at" form:"updated_at"`
+}
+
+type GameShieldBackendArrayRequest struct {
+	AppName string                     `json:"app_name" form:"app_name" binding:"required"`
+	Items   []GameShieldBackendRequest `json:"items" form:"items"`
+	Uid     int                        `json:"uid" form:"uid" binding:"required"`
+	Checked int                        `json:"checked" form:"checked"`
+	HostId  int                        `json:"host_id" form:"host_id" binding:"required"`
+}
+
+type SendGameShieldBackend struct {
+	Addr              []string `json:"addr" form:"addr"`
+	Protocol          string   `json:"protocol" form:"protocol"`
+	ProxyAddr         string   `json:"proxy_addr" form:"proxy_addr"`
+	SdkPort           int      `json:"sdk_port" form:"sdk_port"`
+	UdpSessionTimeout string   `json:"udp_session_timeout" form:"udp_session_timeout"`
+	SdkIp             string   `json:"sdk_ip" form:"sdk_ip"`
+	AgentAddr         string   `json:"agent_addr" form:"agent_addr"`
+}

+ 3 - 0
cmd/server/wire/wire.go

@@ -35,6 +35,7 @@ var repositorySet = wire.NewSet(
 	repository.NewWebLimitRepository,
 	repository.NewWebLimitRepository,
 	repository.NewTcpLimitRepository,
 	repository.NewTcpLimitRepository,
 	repository.NewUdpLimitRepository,
 	repository.NewUdpLimitRepository,
+	repository.NewGameShieldBackendRepository,
 )
 )
 
 
 var serviceSet = wire.NewSet(
 var serviceSet = wire.NewSet(
@@ -54,6 +55,7 @@ var serviceSet = wire.NewSet(
 	service.NewWebLimitService,
 	service.NewWebLimitService,
 	service.NewTcpLimitService,
 	service.NewTcpLimitService,
 	service.NewUdpLimitService,
 	service.NewUdpLimitService,
+	service.NewGameShieldBackendService,
 )
 )
 
 
 var handlerSet = wire.NewSet(
 var handlerSet = wire.NewSet(
@@ -68,6 +70,7 @@ var handlerSet = wire.NewSet(
 	handler.NewWebLimitHandler,
 	handler.NewWebLimitHandler,
 	handler.NewTcpLimitHandler,
 	handler.NewTcpLimitHandler,
 	handler.NewUdpLimitHandler,
 	handler.NewUdpLimitHandler,
+	handler.NewGameShieldBackendHandler,
 )
 )
 
 
 var jobSet = wire.NewSet(
 var jobSet = wire.NewSet(

+ 7 - 4
cmd/server/wire/wire_gen.go

@@ -49,6 +49,9 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	requiredService := service.NewRequiredService(serviceService, crawlerService, viperViper)
 	requiredService := service.NewRequiredService(serviceService, crawlerService, viperViper)
 	gameShieldService := service.NewGameShieldService(serviceService, gameShieldRepository, crawlerService, gameShieldPublicIpService, duedateService, formatterService, parserService, requiredService, viperViper)
 	gameShieldService := service.NewGameShieldService(serviceService, gameShieldRepository, crawlerService, gameShieldPublicIpService, duedateService, formatterService, parserService, requiredService, viperViper)
 	gameShieldHandler := handler.NewGameShieldHandler(handlerHandler, gameShieldService, crawlerService)
 	gameShieldHandler := handler.NewGameShieldHandler(handlerHandler, gameShieldService, crawlerService)
+	gameShieldBackendRepository := repository.NewGameShieldBackendRepository(repositoryRepository)
+	gameShieldBackendService := service.NewGameShieldBackendService(serviceService, gameShieldBackendRepository, gameShieldRepository, crawlerService, gameShieldPublicIpService, duedateService, formatterService, parserService, requiredService, viperViper)
+	gameShieldBackendHandler := handler.NewGameShieldBackendHandler(handlerHandler, gameShieldBackendService)
 	webForwardingRepository := repository.NewWebForwardingRepository(repositoryRepository)
 	webForwardingRepository := repository.NewWebForwardingRepository(repositoryRepository)
 	webForwardingService := service.NewWebForwardingService(serviceService, requiredService, webForwardingRepository, crawlerService, parserService)
 	webForwardingService := service.NewWebForwardingService(serviceService, requiredService, webForwardingRepository, crawlerService, parserService)
 	webForwardingHandler := handler.NewWebForwardingHandler(handlerHandler, webForwardingService)
 	webForwardingHandler := handler.NewWebForwardingHandler(handlerHandler, webForwardingService)
@@ -67,7 +70,7 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	udpLimitRepository := repository.NewUdpLimitRepository(repositoryRepository)
 	udpLimitRepository := repository.NewUdpLimitRepository(repositoryRepository)
 	udpLimitService := service.NewUdpLimitService(serviceService, udpLimitRepository, requiredService, crawlerService, parserService)
 	udpLimitService := service.NewUdpLimitService(serviceService, udpLimitRepository, requiredService, crawlerService, parserService)
 	udpLimitHandler := handler.NewUdpLimitHandler(handlerHandler, udpLimitService)
 	udpLimitHandler := handler.NewUdpLimitHandler(handlerHandler, udpLimitService)
-	httpServer := server.NewHTTPServer(logger, viperViper, jwtJWT, limiterLimiter, handlerFunc, userHandler, gameShieldHandler, webForwardingHandler, webLimitHandler, tcpforwardingHandler, udpForWardingHandler, tcpLimitHandler, udpLimitHandler)
+	httpServer := server.NewHTTPServer(logger, viperViper, jwtJWT, limiterLimiter, handlerFunc, userHandler, gameShieldHandler, gameShieldBackendHandler, webForwardingHandler, webLimitHandler, tcpforwardingHandler, udpForWardingHandler, tcpLimitHandler, udpLimitHandler)
 	jobJob := job.NewJob(transaction, logger, sidSid)
 	jobJob := job.NewJob(transaction, logger, sidSid)
 	userJob := job.NewUserJob(jobJob, userRepository)
 	userJob := job.NewUserJob(jobJob, userRepository)
 	jobServer := server.NewJobServer(logger, userJob)
 	jobServer := server.NewJobServer(logger, userJob)
@@ -78,11 +81,11 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 
 
 // wire.go:
 // wire.go:
 
 
-var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewGameShieldRepository, repository.NewGameShieldPublicIpRepository, repository.NewWebForwardingRepository, repository.NewTcpforwardingRepository, repository.NewUdpForWardingRepository, repository.NewGameShieldUserIpRepository, repository.NewWebLimitRepository, repository.NewTcpLimitRepository, repository.NewUdpLimitRepository)
+var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewGameShieldRepository, repository.NewGameShieldPublicIpRepository, repository.NewWebForwardingRepository, repository.NewTcpforwardingRepository, repository.NewUdpForWardingRepository, repository.NewGameShieldUserIpRepository, repository.NewWebLimitRepository, repository.NewTcpLimitRepository, repository.NewUdpLimitRepository, repository.NewGameShieldBackendRepository)
 
 
-var serviceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewGameShieldService, service.NewCrawlerService, service.NewGameShieldPublicIpService, service.NewDuedateService, service.NewFormatterService, service.NewParserService, service.NewRequiredService, service.NewWebForwardingService, service.NewTcpforwardingService, service.NewUdpForWardingService, service.NewGameShieldUserIpService, service.NewWebLimitService, service.NewTcpLimitService, service.NewUdpLimitService)
+var serviceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewGameShieldService, service.NewCrawlerService, service.NewGameShieldPublicIpService, service.NewDuedateService, service.NewFormatterService, service.NewParserService, service.NewRequiredService, service.NewWebForwardingService, service.NewTcpforwardingService, service.NewUdpForWardingService, service.NewGameShieldUserIpService, service.NewWebLimitService, service.NewTcpLimitService, service.NewUdpLimitService, service.NewGameShieldBackendService)
 
 
-var handlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewGameShieldHandler, handler.NewGameShieldPublicIpHandler, handler.NewWebForwardingHandler, handler.NewTcpforwardingHandler, handler.NewUdpForWardingHandler, handler.NewGameShieldUserIpHandler, handler.NewWebLimitHandler, handler.NewTcpLimitHandler, handler.NewUdpLimitHandler)
+var handlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewGameShieldHandler, handler.NewGameShieldPublicIpHandler, handler.NewWebForwardingHandler, handler.NewTcpforwardingHandler, handler.NewUdpForWardingHandler, handler.NewGameShieldUserIpHandler, handler.NewWebLimitHandler, handler.NewTcpLimitHandler, handler.NewUdpLimitHandler, handler.NewGameShieldBackendHandler)
 
 
 var jobSet = wire.NewSet(job.NewJob, job.NewUserJob)
 var jobSet = wire.NewSet(job.NewJob, job.NewUserJob)
 
 

+ 43 - 0
internal/handler/gameshieldbackend.go

@@ -0,0 +1,43 @@
+package handler
+
+import (
+	"github.com/gin-gonic/gin"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
+	"github.com/go-nunu/nunu-layout-advanced/internal/service"
+	"github.com/mcuadros/go-defaults"
+	"net/http"
+)
+
+type GameShieldBackendHandler struct {
+	*Handler
+	gameShieldBackendService service.GameShieldBackendService
+}
+
+func NewGameShieldBackendHandler(
+	handler *Handler,
+	gameShieldBackendService service.GameShieldBackendService,
+) *GameShieldBackendHandler {
+	return &GameShieldBackendHandler{
+		Handler:                  handler,
+		gameShieldBackendService: gameShieldBackendService,
+	}
+}
+
+func (h *GameShieldBackendHandler) GetGameShieldBackend(ctx *gin.Context) {
+
+}
+
+func (h *GameShieldBackendHandler) AddGameShieldBackend(ctx *gin.Context) {
+	req := new(v1.GameShieldBackendArrayRequest)
+	if err := ctx.ShouldBind(req); err != nil {
+		v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, err.Error())
+		return
+	}
+	defaults.SetDefaults(req)
+	res, err := h.gameShieldBackendService.AddGameShieldBackend(ctx, req)
+	if err != nil {
+		v1.HandleError(ctx, http.StatusInternalServerError, err, err.Error())
+		return
+	}
+	v1.HandleSuccess(ctx, res)
+}

+ 22 - 0
internal/model/gameshieldbackend.go

@@ -0,0 +1,22 @@
+package model
+
+import "time"
+
+type GameShieldBackend struct {
+	Id              int `gorm:"primary"`
+	HostId          int `gorm:"not null"`
+	Key             int
+	SourceMachineIP string ``
+	Protocol        string `gorm:"not null"`
+	ProxyAddr       string `gorm:"null"`
+	ConnectPort     string `gorm:"not null"`
+	SdkPort         string
+	PublicIp        string `gorm:"null"`
+	Type            string
+	CreatedAt       time.Time
+	UpdatedAt       time.Time
+}
+
+func (m *GameShieldBackend) TableName() string {
+	return "shd_game_shield_backend"
+}

+ 17 - 0
internal/repository/gameshield.go

@@ -17,6 +17,8 @@ type GameShieldRepository interface {
 	GetGameShieldNameByDunName(ctx context.Context, appName string) (string, error)
 	GetGameShieldNameByDunName(ctx context.Context, appName string) (string, error)
 	GetGameShieldIdByDunName(ctx context.Context, id int64) (string, error)
 	GetGameShieldIdByDunName(ctx context.Context, id int64) (string, error)
 	GetGameShieldRuleIdByAppName(ctx context.Context, appName string) (int, error)
 	GetGameShieldRuleIdByAppName(ctx context.Context, appName string) (int, error)
+	UpdateGameShieldByHostId(ctx context.Context, gameShield *model.GameShield) error
+	GetGameShieldByHostId(ctx context.Context, hostId int) (*model.GameShield, error)
 }
 }
 
 
 func NewGameShieldRepository(
 func NewGameShieldRepository(
@@ -133,3 +135,18 @@ func (r *gameShieldRepository) GetGameShieldRuleIdByAppName(ctx context.Context,
 	}
 	}
 	return res, nil
 	return res, nil
 }
 }
+
+func (r *gameShieldRepository) UpdateGameShieldByHostId(ctx context.Context, req *model.GameShield) error {
+	if err := r.DB(ctx).Where("host_id = ?", req.Id).Updates(&model.GameShield{}).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *gameShieldRepository) GetGameShieldByHostId(ctx context.Context, hostId int) (*model.GameShield, error) {
+	var res model.GameShield
+	if err := r.DB(ctx).Where("host_id = ?", hostId).First(&res).Error; err != nil {
+		return nil, err
+	}
+	return &res, nil
+}

+ 64 - 0
internal/repository/gameshieldbackend.go

@@ -0,0 +1,64 @@
+package repository
+
+import (
+	"context"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+)
+
+type GameShieldBackendRepository interface {
+	GetGameShieldBackendById(ctx context.Context, id int64) (*model.GameShieldBackend, error)
+	AddGameShieldBackend(ctx context.Context, req *model.GameShieldBackend) error
+	EditGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendRequest) error
+	DeleteGameShieldBackend(ctx context.Context, id int64) error
+	GetGameShieldBackendByHostId(ctx context.Context, hostId int) (*[]model.GameShieldBackend, error)
+}
+
+func NewGameShieldBackendRepository(
+	repository *Repository,
+) GameShieldBackendRepository {
+	return &gameShieldBackendRepository{
+		Repository: repository,
+	}
+}
+
+type gameShieldBackendRepository struct {
+	*Repository
+}
+
+func (r *gameShieldBackendRepository) GetGameShieldBackendById(ctx context.Context, id int64) (*model.GameShieldBackend, error) {
+	var gameShieldBackend model.GameShieldBackend
+	if err := r.DB(ctx).Where("id = ?", id).First(&gameShieldBackend).Error; err != nil {
+		return nil, err
+	}
+	return &gameShieldBackend, nil
+}
+
+func (r *gameShieldBackendRepository) AddGameShieldBackend(ctx context.Context, req *model.GameShieldBackend) error {
+	if err := r.DB(ctx).Create(&req).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *gameShieldBackendRepository) EditGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendRequest) error {
+	if err := r.DB(ctx).Where("id = ?", req.Id).Updates(req).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *gameShieldBackendRepository) DeleteGameShieldBackend(ctx context.Context, id int64) error {
+	if err := r.DB(ctx).Delete(&model.GameShieldBackend{}, id).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func (r *gameShieldBackendRepository) GetGameShieldBackendByHostId(ctx context.Context, hostId int) (*[]model.GameShieldBackend, error) {
+	var gameShieldBackend []model.GameShieldBackend
+	if err := r.DB(ctx).Where("host_id = ?", hostId).Find(&gameShieldBackend).Error; err != nil {
+		return nil, err
+	}
+	return &gameShieldBackend, nil
+}

+ 3 - 0
internal/server/http.go

@@ -23,12 +23,14 @@ func NewHTTPServer(
 	rateLimitMiddleware gin.HandlerFunc,
 	rateLimitMiddleware gin.HandlerFunc,
 	userHandler *handler.UserHandler,
 	userHandler *handler.UserHandler,
 	gameShieldHandler *handler.GameShieldHandler,
 	gameShieldHandler *handler.GameShieldHandler,
+	gameShieldBackendHandler *handler.GameShieldBackendHandler,
 	webForwardingHandler *handler.WebForwardingHandler,
 	webForwardingHandler *handler.WebForwardingHandler,
 	weblimitHandler *handler.WebLimitHandler,
 	weblimitHandler *handler.WebLimitHandler,
 	tcpForwardingHandler *handler.TcpforwardingHandler,
 	tcpForwardingHandler *handler.TcpforwardingHandler,
 	udpForwardingHandler *handler.UdpForWardingHandler,
 	udpForwardingHandler *handler.UdpForWardingHandler,
 	tcpLimitHandler *handler.TcpLimitHandler,
 	tcpLimitHandler *handler.TcpLimitHandler,
 	udpLimitHandler *handler.UdpLimitHandler,
 	udpLimitHandler *handler.UdpLimitHandler,
+
 ) *http.Server {
 ) *http.Server {
 	gin.SetMode(gin.DebugMode)
 	gin.SetMode(gin.DebugMode)
 	s := http.NewServer(
 	s := http.NewServer(
@@ -98,6 +100,7 @@ func NewHTTPServer(
 			noAuthRouter.POST("/udpLimit/add", udpLimitHandler.AddUdpLimit)
 			noAuthRouter.POST("/udpLimit/add", udpLimitHandler.AddUdpLimit)
 			noAuthRouter.POST("/udpLimit/edit", udpLimitHandler.EditUdpLimit)
 			noAuthRouter.POST("/udpLimit/edit", udpLimitHandler.EditUdpLimit)
 			noAuthRouter.POST("/udpLimit/delete", udpLimitHandler.DeleteUdpLimit)
 			noAuthRouter.POST("/udpLimit/delete", udpLimitHandler.DeleteUdpLimit)
+			noAuthRouter.POST("/gameShieldBackend/add", gameShieldBackendHandler.AddGameShieldBackend)
 		}
 		}
 		// Non-strict permission routing group
 		// Non-strict permission routing group
 		noStrictAuthRouter := v1.Group("/").Use(middleware.NoStrictAuth(jwt, logger))
 		noStrictAuthRouter := v1.Group("/").Use(middleware.NoStrictAuth(jwt, logger))

+ 147 - 40
internal/service/formatter.go

@@ -5,13 +5,21 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
 	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"maps"
+	"sort"
+	"strconv"
+	"strings"
 
 
 	"github.com/spf13/cast"
 	"github.com/spf13/cast"
-	"strconv"
 )
 )
 
 
 type FormatterService interface {
 type FormatterService interface {
-	FormatBackendData(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error)
+	FormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, output map[string]v1.SendGameShieldBackend) (string, int, error)
+	FormatPort(ctx context.Context, req interface{}) []int
+	OldFormat(ctx context.Context, req *[]model.GameShieldBackend) (map[string]v1.SendGameShieldBackend, error)
+	TidyFormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, keyCounter int) (map[string]v1.SendGameShieldBackend, error)
+	Sort(ctx context.Context, mapData map[string]v1.SendGameShieldBackend) (map[string]v1.SendGameShieldBackend, error)
 }
 }
 
 
 func NewFormatterService(
 func NewFormatterService(
@@ -30,63 +38,162 @@ type formatterService struct {
 	gameShieldPublicIpService GameShieldPublicIpService
 	gameShieldPublicIpService GameShieldPublicIpService
 }
 }
 
 
-func (service *formatterService) FormatBackendData(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error) {
-	output := make(map[string]map[string]interface{})
+func (service *formatterService) FormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, oldFormat map[string]v1.SendGameShieldBackend) (string, int, error) {
+	keyCounter := len(oldFormat)
+	formData, err := service.TidyFormatBackendData(ctx, req, keyCounter)
+	if err != nil {
+		return "", 0, err
+	}
+	maps.Copy(formData, oldFormat)
 
 
-	userIp, err := service.gameShieldPublicIpService.GetUserIp(ctx, req.Uid)
+	sortedOutput, err := service.Sort(ctx, formData)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", 0, err
 	}
 	}
-	if len(req.Data) == 0 {
-		return "", fmt.Errorf("data is required")
+	jsonBytes, err := json.Marshal(sortedOutput)
+	if err != nil {
+		return "", 0, err
 	}
 	}
-	// 解析 JSON 数据为 map[string]map[string]interface{}
-	var backend map[string]map[string]interface{}
-	if err := json.Unmarshal([]byte(req.Data), &backend); err != nil {
-		return "", fmt.Errorf("failed to unmarshal req.Data: %w", err)
+
+	return string(jsonBytes), keyCounter, nil
+}
+
+func (service *formatterService) FormatPort(ctx context.Context, req interface{}) []int {
+	if req == nil {
+		return []int{}
 	}
 	}
 
 
-	for i := 0; i < len(backend); i++ {
-		key := "key" + strconv.Itoa(i)
+	reqStr := cast.ToString(req)
 
 
-		innerMap, ok := backend[key]
-		if !ok {
-			continue
+	if reqStr == "" {
+		return []int{}
+	}
+
+	reqStr = strings.ReplaceAll(reqStr, ",", ",")
+
+	// 分割字符串并转换为整数
+	var res []int
+	for _, v := range strings.Split(reqStr, ",") {
+		// 去除空格
+		v = strings.TrimSpace(v)
+		if v != "" {
+			port := cast.ToInt(v)
+			res = append(res, port)
 		}
 		}
+	}
+	return res
+}
+
+func (service *formatterService) OldFormat(ctx context.Context, req *[]model.GameShieldBackend) (map[string]v1.SendGameShieldBackend, error) {
+	res := make(map[string]v1.SendGameShieldBackend)
+	for _, v := range *req {
+		addr := fmt.Sprintf("%s:%s", v.SourceMachineIP, v.ConnectPort)
+		sdkPort, err := strconv.Atoi(v.SdkPort)
+		if err != nil {
+			return nil, err
+		}
+		keyName := fmt.Sprintf("key%d", v.Key)
+		res[keyName] = v1.SendGameShieldBackend{
+			Addr:              []string{addr},
+			Protocol:          v.Protocol,
+			ProxyAddr:         v.ProxyAddr,
+			SdkPort:           sdkPort,
+			UdpSessionTimeout: "300s",
+			SdkIp:             v.PublicIp,
+		}
+	}
+	return res, nil
+}
 
 
-		addr := fmt.Sprintf("%s:%s", innerMap["source_machineIP"], cast.ToString(innerMap["connect_port"]))
+func (service *formatterService) TidyFormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, keyCounter int) (map[string]v1.SendGameShieldBackend, error) {
+	output := make(map[string]v1.SendGameShieldBackend)
+	userIp, err := service.gameShieldPublicIpService.GetUserIp(ctx, req.Uid)
+	if err != nil {
+		return nil, err
+	}
 
 
-		itemMap := map[string]interface{}{
-			"addr":     []string{addr},
-			"protocol": innerMap["protocol"],
+	for _, item := range req.Items {
+		// 提取必要字段
+		sourceIP := item.SourceMachineIP // 假设结构体中有这个字段
+		if sourceIP == "" {
+			continue // 跳过没有有效源IP的配置
 		}
 		}
 
 
-		if host, ok := innerMap["host"]; ok && host != "" {
-			itemMap["host"] = host
+		protocol := item.Protocol // 假设结构体中有这个字段
+		if protocol == "" {
+			continue // 跳过没有有效协议的配置
 		}
 		}
 
 
-		if innerMap["protocol"] != "udp" {
-			if req.Checked == 1 {
-				itemMap["agent_addr"] = fmt.Sprintf("%s:%s", innerMap["source_machineIP"], "23350")
-			}
-			itemMap["proxy_addr"] = userIp + ":32353"
-		} else {
-			itemMap["proxy_addr"] = ""
-			itemMap["udp_session_timeout"] = "300s"
+		// 获取端口数组
+		conPorts := service.FormatPort(ctx, item.ConnectPort)
+		sdkPorts := service.FormatPort(ctx, item.SdkPort)
+
+		if len(conPorts) != len(sdkPorts) {
+			return nil, fmt.Errorf("源端口和目标端口数量不匹配")
 		}
 		}
 
 
-		if sdkPort, ok := innerMap["sdk_port"]; ok {
-			itemMap["sdk_port"] = sdkPort
-		} else {
-			itemMap["sdk_port"] = 0
+		// 处理每一对端口
+		for i := 0; i < len(conPorts); i++ {
+			keyCounter++
+			key := fmt.Sprintf("key%d", keyCounter)
+
+			// 使用数组中的具体端口
+			addr := fmt.Sprintf("%s:%d", sourceIP, conPorts[i])
+
+			itemMap := v1.SendGameShieldBackend{
+				Addr:     []string{addr},
+				Protocol: protocol,
+			}
+
+			//// 设置主机名(如果存在)
+			//if item.Host != "" {
+			//	itemMap["host"] = item.Host
+			//}
+
+			// 根据协议设置不同属性
+			if protocol != "udp" {
+				if req.Checked == 1 {
+					itemMap.AgentAddr = fmt.Sprintf("%s:%s", sourceIP, "23350")
+				}
+				itemMap.ProxyAddr = userIp + ":32353"
+			} else {
+				itemMap.ProxyAddr = ""
+				itemMap.UdpSessionTimeout = "300s"
+			}
+			if item.Type == "mobile" {
+				itemMap.SdkIp = "127.0.0.1"
+			} else {
+				itemMap.SdkIp = item.PublicIp
+			}
+
+			// 设置SDK端口 - 使用数组中的具体端口
+			itemMap.SdkPort = sdkPorts[i]
+
+			output[key] = itemMap
 		}
 		}
+	}
 
 
-		output[key] = itemMap
+	return output, nil
+}
+
+func (service *formatterService) Sort(ctx context.Context, mapData map[string]v1.SendGameShieldBackend) (map[string]v1.SendGameShieldBackend, error) {
+	var keys []int
+	for key := range mapData {
+		intKey, err := strconv.Atoi(strings.TrimPrefix(key, "key"))
+		if err != nil {
+			return nil, err
+		}
+		keys = append(keys, intKey)
 	}
 	}
 
 
-	jsonBytes, err := json.Marshal(output)
-	if err != nil {
-		return "", err
+	// 2. 排序键
+	sort.Ints(keys)
+	// 3. 创建一个新的 output 切片或 map 来存储排序后的值
+	sortedOutput := make(map[string]v1.SendGameShieldBackend)
+
+	// 4. 按排序后的键遍历 map,并存储对应的值到 sortedOutput
+	for _, key := range keys {
+		sortedOutput["key"+strconv.Itoa(key)] = mapData["key"+strconv.Itoa(key)]
 	}
 	}
-	return string(jsonBytes), nil
+	return sortedOutput, nil
 }
 }

+ 88 - 97
internal/service/gameshield.go

@@ -7,7 +7,6 @@ import (
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
 	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
 	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
 	"github.com/spf13/viper"
 	"github.com/spf13/viper"
-	"strconv"
 	"time"
 	"time"
 )
 )
 
 
@@ -55,26 +54,26 @@ type gameShieldService struct {
 	required                  RequiredService
 	required                  RequiredService
 }
 }
 
 
-func (service *gameShieldService) GetGameShieldrequired(ctx context.Context, req *v1.GameShieldSubmitRequest) (*v1.GetGameShieldRequiredResponse, error) {
-	var res v1.GetGameShieldRequiredResponse
-	var err error
-	if req.Uid == 0 {
-		return nil, fmt.Errorf("uid is required")
-	}
-	res.ExpiredAt, err = service.duedate.NextDueDate(ctx, req.Uid, req.HostId)
-	if err != nil {
-		return nil, err
-	}
-	res.Backend, err = service.formatter.FormatBackendData(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-	res.Cookie, err = service.crawlerService.GetLoginCookie(ctx)
-	if err != nil {
-		return nil, err
-	}
-	return &res, nil
-}
+//func (service *gameShieldService) GetGameShieldrequired(ctx context.Context, req *v1.GameShieldSubmitRequest) (*v1.GetGameShieldRequiredResponse, error) {
+//	var res v1.GetGameShieldRequiredResponse
+//	var err error
+//	if req.Uid == 0 {
+//		return nil, fmt.Errorf("uid is required")
+//	}
+//	res.ExpiredAt, err = service.duedate.NextDueDate(ctx, req.Uid, req.HostId)
+//	if err != nil {
+//		return nil, err
+//	}
+//	res.Backend, err = service.formatter.FormatBackendData(ctx, req)
+//	if err != nil {
+//		return nil, err
+//	}
+//	res.Cookie, err = service.crawlerService.GetLoginCookie(ctx)
+//	if err != nil {
+//		return nil, err
+//	}
+//	return &res, nil
+//}
 
 
 func (service *gameShieldService) SubmitGameShield(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error) {
 func (service *gameShieldService) SubmitGameShield(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error) {
 	nameCount, err := service.gameShieldRepository.GetGameShieldDuplicateName(ctx, req.AppName, req.Uid)
 	nameCount, err := service.gameShieldRepository.GetGameShieldDuplicateName(ctx, req.AppName, req.Uid)
@@ -84,50 +83,46 @@ func (service *gameShieldService) SubmitGameShield(ctx context.Context, req *v1.
 	if nameCount > 0 {
 	if nameCount > 0 {
 		return "", fmt.Errorf("应用名称已存在")
 		return "", fmt.Errorf("应用名称已存在")
 	}
 	}
-	require, err := service.GetGameShieldrequired(ctx, req)
-	if err != nil {
-		return "", err
-	}
-	dunName := strconv.Itoa(req.Uid) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + req.AppName
-	formData := map[string]interface{}{
-		"app_name":         dunName,
-		"gateway_group_id": 4,
-		"backend":          require.Backend,
-		"expired_at":       require.ExpiredAt,
-		"max_device_count": 0,
-	}
-	respBody, err := service.required.SendForm(ctx, "admin/info/rule/new", "admin/new/rule", formData)
-	if err != nil {
-		return "", err
-	}
-	// 解析响应内容中的 alert 消息
-	res, err := service.parser.ParseAlert(string(respBody))
-	if err != nil {
-		return "", err
-	}
-	if res != "" {
-		return "", fmt.Errorf(res)
-	}
-	KeyAndField, err := service.required.GetKeyAndField(ctx, dunName, "rule_id")
-	if err != nil {
-		return "", err
-	}
+	//require, err := service.GetGameShieldrequired(ctx, req)
+	//if err != nil {
+	//	return "", err
+	//}
+	//dunName := strconv.Itoa(req.Uid) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + req.AppName
+	//formData := map[string]interface{}{
+	//	"app_name":         dunName,
+	//	"gateway_group_id": 4,
+	//	"backend":          require.Backend,
+	//	"expired_at":       require.ExpiredAt,
+	//	"max_device_count": 0,
+	//}
+	//respBody, err := service.required.SendForm(ctx, "admin/info/rule/new", "admin/new/rule", formData)
+	//if err != nil {
+	//	return "", err
+	//}
+	//// 解析响应内容中的 alert 消息
+	//res, err := service.parser.ParseAlert(string(respBody))
+	//if err != nil {
+	//	return "", err
+	//}
+	//if res != "" {
+	//	return "", fmt.Errorf(res)
+	//}
+	//KeyAndField, err := service.required.GetKeyAndField(ctx, dunName, "rule_id")
+	//if err != nil {
+	//	return "", err
+	//}
 	if err := service.gameShieldRepository.AddGameShield(ctx, &model.GameShield{
 	if err := service.gameShieldRepository.AddGameShield(ctx, &model.GameShield{
 		AppName:        req.AppName,
 		AppName:        req.AppName,
 		GatewayGroupId: 4,
 		GatewayGroupId: 4,
-		Backend:        require.Backend,
-		RuleId:         KeyAndField.FieldId,
-		Key:            KeyAndField.Key,
 		AddTime:        time.Now(),
 		AddTime:        time.Now(),
 		Uid:            req.Uid,
 		Uid:            req.Uid,
 		HostId:         req.HostId,
 		HostId:         req.HostId,
 		AppIp:          req.AppIp,
 		AppIp:          req.AppIp,
 		Checked:        req.Checked,
 		Checked:        req.Checked,
-		DunName:        dunName,
 	}); err != nil {
 	}); err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	return res, nil
+	return "", nil
 }
 }
 
 
 func (service *gameShieldService) EditGameShield(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error) {
 func (service *gameShieldService) EditGameShield(ctx context.Context, req *v1.GameShieldSubmitRequest) (string, error) {
@@ -138,59 +133,55 @@ func (service *gameShieldService) EditGameShield(ctx context.Context, req *v1.Ga
 	if ruleId != req.RuleId {
 	if ruleId != req.RuleId {
 		return "", fmt.Errorf("应用名称已存在")
 		return "", fmt.Errorf("应用名称已存在")
 	}
 	}
-	require, err := service.GetGameShieldrequired(ctx, req)
-	if err != nil {
-		return "", err
-	}
-	tokenUrl := service.Url + "admin/info/rule/edit?&__goadmin_edit_pk=" + strconv.Itoa(req.RuleId) + "_" + req.AppName
-	tokens, err := service.crawlerService.GetFormTokens(ctx, tokenUrl, require.Cookie)
-	if err != nil {
-		return "", err
-	}
-	sendUrl := service.Url + "admin/edit/rule"
-
-	dunName := strconv.Itoa(req.Uid) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + req.AppName
-	formData := map[string]interface{}{
-		"app_name":             dunName,
-		"gateway_group_id":     4,
-		"backend":              require.Backend,
-		"rule_id":              req.RuleId,
-		"expired_at":           require.ExpiredAt,
-		"max_device_count":     0,
-		"__go_admin_previous_": tokens["previous"],
-		"__go_admin_t_":        tokens["t"],
-	}
-	respBody, err := service.crawlerService.SendFormData(ctx, sendUrl, require.Cookie, formData)
-	if err != nil {
-		return "", err
-	}
-	// 解析响应内容中的 alert 消息
-	res, err := service.parser.ParseAlert(string(respBody))
-	if err != nil {
-		return "", err
-	}
-	if res != "" {
-		return "", fmt.Errorf(res)
-	}
-	KeyAndField, err := service.required.GetKeyAndField(ctx, dunName, "rule_id")
-	if err != nil {
-		return "", err
-	}
+	//require, err := service.GetGameShieldrequired(ctx, req)
+	//if err != nil {
+	//	return "", err
+	//}
+	//tokenUrl := service.Url + "admin/info/rule/edit?&__goadmin_edit_pk=" + strconv.Itoa(req.RuleId) + "_" + req.AppName
+	//tokens, err := service.crawlerService.GetFormTokens(ctx, tokenUrl, require.Cookie)
+	//if err != nil {
+	//	return "", err
+	//}
+	//sendUrl := service.Url + "admin/edit/rule"
+	//
+	//dunName := strconv.Itoa(req.Uid) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + req.AppName
+	//formData := map[string]interface{}{
+	//	"app_name":             dunName,
+	//	"gateway_group_id":     4,
+	//	"backend":              require.Backend,
+	//	"rule_id":              req.RuleId,
+	//	"expired_at":           require.ExpiredAt,
+	//	"max_device_count":     0,
+	//	"__go_admin_previous_": tokens["previous"],
+	//	"__go_admin_t_":        tokens["t"],
+	//}
+	//respBody, err := service.crawlerService.SendFormData(ctx, sendUrl, require.Cookie, formData)
+	//if err != nil {
+	//	return "", err
+	//}
+	//// 解析响应内容中的 alert 消息
+	//res, err := service.parser.ParseAlert(string(respBody))
+	//if err != nil {
+	//	return "", err
+	//}
+	//if res != "" {
+	//	return "", fmt.Errorf(res)
+	//}
+	//KeyAndField, err := service.required.GetKeyAndField(ctx, dunName, "rule_id")
+	//if err != nil {
+	//	return "", err
+	//}
 	if err := service.gameShieldRepository.UpdateGameShield(ctx, &model.GameShield{
 	if err := service.gameShieldRepository.UpdateGameShield(ctx, &model.GameShield{
 		AppName:        req.AppName,
 		AppName:        req.AppName,
 		GatewayGroupId: 4,
 		GatewayGroupId: 4,
-		Backend:        require.Backend,
-		RuleId:         KeyAndField.FieldId,
-		Key:            KeyAndField.Key,
 		Uid:            req.Uid,
 		Uid:            req.Uid,
 		HostId:         req.HostId,
 		HostId:         req.HostId,
 		AppIp:          req.AppIp,
 		AppIp:          req.AppIp,
 		Checked:        req.Checked,
 		Checked:        req.Checked,
-		DunName:        dunName,
 	}); err != nil {
 	}); err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	return res, nil
+	return "", nil
 }
 }
 
 
 func (service *gameShieldService) DeleteGameShield(ctx context.Context, id int) (string, error) {
 func (service *gameShieldService) DeleteGameShield(ctx context.Context, id int) (string, error) {

+ 205 - 0
internal/service/gameshieldbackend.go

@@ -0,0 +1,205 @@
+package service
+
+import (
+	"context"
+	"fmt"
+	v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/spf13/viper"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type GameShieldBackendService interface {
+	GetGameShieldBackend(ctx context.Context, id int64) (*model.GameShieldBackend, error)
+	GameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, int, error)
+	AddGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, error)
+	EditGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, error)
+	DeleteGameShieldBackend(ctx context.Context, id int64) error
+	GetGameShieldRequired(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (*v1.GetGameShieldRequiredResponse, int, error)
+}
+
+func NewGameShieldBackendService(
+	service *Service,
+	gameShieldBackendRepository repository.GameShieldBackendRepository,
+	gameShieldRepository repository.GameShieldRepository,
+	crawlerService CrawlerService,
+	gameShieldPublicIpService GameShieldPublicIpService,
+	duedate DuedateService,
+	formatter FormatterService,
+	parser ParserService,
+	required RequiredService,
+	conf *viper.Viper,
+) GameShieldBackendService {
+	return &gameShieldBackendService{
+		Service:                     service,
+		gameShieldBackendRepository: gameShieldBackendRepository,
+		gameShieldRepository:        gameShieldRepository,
+		crawlerService:              crawlerService,
+		gameShieldPublicIpService:   gameShieldPublicIpService,
+		duedate:                     duedate,
+		formatter:                   formatter,
+		parser:                      parser,
+		required:                    required,
+		Url:                         conf.GetString("crawler.Url"),
+	}
+}
+
+type gameShieldBackendService struct {
+	*Service
+	gameShieldBackendRepository repository.GameShieldBackendRepository
+	crawlerService              CrawlerService
+	gameShieldRepository        repository.GameShieldRepository
+	gameShieldPublicIpService   GameShieldPublicIpService
+	duedate                     DuedateService
+	formatter                   FormatterService
+	Url                         string
+	parser                      ParserService
+	required                    RequiredService
+}
+
+func (s *gameShieldBackendService) GetGameShieldRequired(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (*v1.GetGameShieldRequiredResponse, int, error) {
+	var res v1.GetGameShieldRequiredResponse
+	var err error
+	var count int
+	if req.Uid == 0 {
+		return nil, 0, fmt.Errorf("uid is required")
+	}
+	res.ExpiredAt, err = s.duedate.NextDueDate(ctx, req.Uid, strconv.Itoa(req.HostId))
+	if err != nil {
+		return nil, 0, err
+	}
+	gameShield, err := s.gameShieldRepository.GetGameShieldByHostId(ctx, req.HostId)
+	if err != nil {
+		return nil, 0, err
+	}
+	res.RuleId = gameShield.RuleId
+	oldBackend, err := s.gameShieldBackendRepository.GetGameShieldBackendByHostId(ctx, req.HostId)
+	if err != nil {
+		return nil, 0, err
+	}
+	OldBackend, err := s.formatter.OldFormat(ctx, oldBackend)
+	res.Backend, count, err = s.formatter.FormatBackendData(ctx, req, OldBackend)
+	if err != nil {
+		return nil, 0, err
+	}
+	res.Cookie, err = s.crawlerService.GetLoginCookie(ctx)
+	if err != nil {
+		return nil, 0, err
+	}
+	return &res, count, nil
+}
+func (s *gameShieldBackendService) GetGameShieldBackend(ctx context.Context, id int64) (*model.GameShieldBackend, error) {
+	res, err := s.gameShieldBackendRepository.GetGameShieldBackendById(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return res, nil
+}
+
+func (s *gameShieldBackendService) GameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, int, error) {
+	require, count, err := s.GetGameShieldRequired(ctx, req)
+	if err != nil {
+		return "", 0, err
+	}
+	tokenUrl := s.Url + "admin/info/rule/edit?&__goadmin_edit_pk=" + strconv.Itoa(require.RuleId) + "_" + req.AppName
+	tokens, err := s.crawlerService.GetFormTokens(ctx, tokenUrl, require.Cookie)
+	if err != nil {
+		return "", 0, err
+	}
+	dunName := strconv.Itoa(req.Uid) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + req.AppName
+	formData := map[string]interface{}{
+		"app_name":             dunName,
+		"gateway_group_id":     4,
+		"backend":              require.Backend,
+		"rule_id":              require.RuleId,
+		"expired_at":           require.ExpiredAt,
+		"max_device_count":     0,
+		"__go_admin_previous_": tokens["previous"],
+		"__go_admin_t_":        tokens["t"],
+	}
+	sendUrl := s.Url + "admin/edit/rule"
+	respBody, err := s.crawlerService.SendFormData(ctx, sendUrl, require.Cookie, formData)
+	if err != nil {
+		return "", 0, err
+	}
+	// 解析响应内容中的 alert 消息
+	res, err := s.parser.ParseAlert(string(respBody))
+	if err != nil {
+		return "", 0, err
+	}
+	if res != "" {
+		return "", 0, fmt.Errorf(res)
+	}
+	KeyAndField, err := s.required.GetKeyAndField(ctx, dunName, "rule_id")
+	if err != nil {
+		return "", 0, err
+	}
+	if err := s.gameShieldRepository.UpdateGameShieldByHostId(ctx, &model.GameShield{Id: req.HostId, Key: KeyAndField.Key, DunName: dunName}); err != nil {
+		return "", 0, err
+	}
+	return res, count, nil
+}
+
+func (s *gameShieldBackendService) AddGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, error) {
+	res, count, err := s.GameShieldBackend(ctx, req)
+	if err != nil {
+		return "", err
+	}
+	saveData, err := s.formatter.TidyFormatBackendData(ctx, req, count)
+	if err != nil {
+		return "", err
+	}
+	if err := s.SaveGameShieldBackend(ctx, saveData, req.HostId); err != nil {
+		return "", err
+	}
+	return res, nil
+}
+func (s *gameShieldBackendService) EditGameShieldBackend(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (string, error) {
+	for _, v := range req.Items {
+		if err := s.gameShieldBackendRepository.EditGameShieldBackend(ctx, &v); err != nil {
+			return "", err
+		}
+	}
+	res, _, err := s.GameShieldBackend(ctx, req)
+	if err != nil {
+		return "", err
+	}
+	return res, nil
+}
+
+func (s *gameShieldBackendService) DeleteGameShieldBackend(ctx context.Context, id int64) error {
+	if err := s.gameShieldBackendRepository.DeleteGameShieldBackend(ctx, id); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *gameShieldBackendService) SaveGameShieldBackend(ctx context.Context, req map[string]v1.SendGameShieldBackend, hostId int) error {
+	for k, v := range req {
+		parts := strings.Split(v.Addr[0], ":")
+		keyName := strings.Split(k, "key")[1]
+		key, err := strconv.Atoi(keyName)
+		if err != nil {
+			return err
+		}
+		if err := s.gameShieldBackendRepository.AddGameShieldBackend(ctx,
+			&model.GameShieldBackend{
+				HostId:          hostId,
+				Key:             key,
+				SourceMachineIP: parts[0],
+				Protocol:        v.Protocol,
+				ProxyAddr:       v.ProxyAddr,
+				ConnectPort:     parts[1],
+				PublicIp:        v.SdkIp,
+				SdkPort:         strconv.Itoa(v.SdkPort),
+				// 可以添加其他字段
+			}); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}