Prechádzať zdrojové kódy

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

- 新增 GameShieldBackend 模型和相关服务- 优化 FormatBackendData 方法,支持数组格式请求
- 添加旧数据格式转换功能
- 实现游戏盾后台数据排序
- 新增游戏盾后台数据保存和编辑功能
fusu 3 mesiacov pred
rodič
commit
4f422d88b7

+ 1 - 0
api/v1/GameShield.go

@@ -37,6 +37,7 @@ type GetGameShieldRequiredResponse struct {
 	ExpiredAt string
 	Backend   string
 	Cookie    string
+	RuleId    int
 }
 
 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.NewTcpLimitRepository,
 	repository.NewUdpLimitRepository,
+	repository.NewGameShieldBackendRepository,
 )
 
 var serviceSet = wire.NewSet(
@@ -54,6 +55,7 @@ var serviceSet = wire.NewSet(
 	service.NewWebLimitService,
 	service.NewTcpLimitService,
 	service.NewUdpLimitService,
+	service.NewGameShieldBackendService,
 )
 
 var handlerSet = wire.NewSet(
@@ -68,6 +70,7 @@ var handlerSet = wire.NewSet(
 	handler.NewWebLimitHandler,
 	handler.NewTcpLimitHandler,
 	handler.NewUdpLimitHandler,
+	handler.NewGameShieldBackendHandler,
 )
 
 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)
 	gameShieldService := service.NewGameShieldService(serviceService, gameShieldRepository, crawlerService, gameShieldPublicIpService, duedateService, formatterService, parserService, requiredService, viperViper)
 	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)
 	webForwardingService := service.NewWebForwardingService(serviceService, requiredService, webForwardingRepository, crawlerService, parserService)
 	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)
 	udpLimitService := service.NewUdpLimitService(serviceService, udpLimitRepository, requiredService, crawlerService, parserService)
 	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)
 	userJob := job.NewUserJob(jobJob, userRepository)
 	jobServer := server.NewJobServer(logger, userJob)
@@ -78,11 +81,11 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 
 // 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)
 

+ 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)
 	GetGameShieldIdByDunName(ctx context.Context, id int64) (string, 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(
@@ -133,3 +135,18 @@ func (r *gameShieldRepository) GetGameShieldRuleIdByAppName(ctx context.Context,
 	}
 	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,
 	userHandler *handler.UserHandler,
 	gameShieldHandler *handler.GameShieldHandler,
+	gameShieldBackendHandler *handler.GameShieldBackendHandler,
 	webForwardingHandler *handler.WebForwardingHandler,
 	weblimitHandler *handler.WebLimitHandler,
 	tcpForwardingHandler *handler.TcpforwardingHandler,
 	udpForwardingHandler *handler.UdpForWardingHandler,
 	tcpLimitHandler *handler.TcpLimitHandler,
 	udpLimitHandler *handler.UdpLimitHandler,
+
 ) *http.Server {
 	gin.SetMode(gin.DebugMode)
 	s := http.NewServer(
@@ -98,6 +100,7 @@ func NewHTTPServer(
 			noAuthRouter.POST("/udpLimit/add", udpLimitHandler.AddUdpLimit)
 			noAuthRouter.POST("/udpLimit/edit", udpLimitHandler.EditUdpLimit)
 			noAuthRouter.POST("/udpLimit/delete", udpLimitHandler.DeleteUdpLimit)
+			noAuthRouter.POST("/gameShieldBackend/add", gameShieldBackendHandler.AddGameShieldBackend)
 		}
 		// Non-strict permission routing group
 		noStrictAuthRouter := v1.Group("/").Use(middleware.NoStrictAuth(jwt, logger))

+ 147 - 40
internal/service/formatter.go

@@ -5,13 +5,21 @@ import (
 	"encoding/json"
 	"fmt"
 	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"
-	"strconv"
 )
 
 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(
@@ -30,63 +38,162 @@ type formatterService struct {
 	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 {
-		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/repository"
 	"github.com/spf13/viper"
-	"strconv"
 	"time"
 )
 
@@ -55,26 +54,26 @@ type gameShieldService struct {
 	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) {
 	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 {
 		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{
 		AppName:        req.AppName,
 		GatewayGroupId: 4,
-		Backend:        require.Backend,
-		RuleId:         KeyAndField.FieldId,
-		Key:            KeyAndField.Key,
 		AddTime:        time.Now(),
 		Uid:            req.Uid,
 		HostId:         req.HostId,
 		AppIp:          req.AppIp,
 		Checked:        req.Checked,
-		DunName:        dunName,
 	}); err != nil {
 		return "", err
 	}
-	return res, nil
+	return "", nil
 }
 
 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 {
 		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{
 		AppName:        req.AppName,
 		GatewayGroupId: 4,
-		Backend:        require.Backend,
-		RuleId:         KeyAndField.FieldId,
-		Key:            KeyAndField.Key,
 		Uid:            req.Uid,
 		HostId:         req.HostId,
 		AppIp:          req.AppIp,
 		Checked:        req.Checked,
-		DunName:        dunName,
 	}); err != nil {
 		return "", err
 	}
-	return res, nil
+	return "", nil
 }
 
 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
+}