package service import ( "context" "encoding/json" "fmt" "github.com/AlekSi/pointer" 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" "go.uber.org/zap" "maps" "sort" "strconv" "strings" "github.com/spf13/cast" ) type FormatterService interface { FormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, output map[string]v1.SendGameShieldBackend, keyCounter int) (string, 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) ValidateBackendData(ctx context.Context, mapData map[string]v1.SendGameShieldBackend, hostId int) error } func NewFormatterService( service *Service, gameShieldPublicIpService GameShieldPublicIpService, gameShieldBackendRepository repository.GameShieldBackendRepository, hostService HostService, ) FormatterService { return &formatterService{ Service: service, gameShieldPublicIpService: gameShieldPublicIpService, gameShieldBackendRepository: gameShieldBackendRepository, hostService: hostService, } } type formatterService struct { *Service gameShieldPublicIpService GameShieldPublicIpService gameShieldBackendRepository repository.GameShieldBackendRepository hostService HostService } func (service *formatterService) FormatBackendData(ctx context.Context, req *v1.GameShieldBackendArrayRequest, oldFormat map[string]v1.SendGameShieldBackend, keyCounter int) (string, error) { formData, err := service.TidyFormatBackendData(ctx, req, keyCounter) for _, v := range formData { v.Type = "" v.Remark = "" } if err != nil { return "", err } maps.Copy(formData, oldFormat) // 验证 err = service.ValidateBackendData(ctx, formData, req.HostId) if err != nil { return "", err } sortedOutput, err := service.Sort(ctx, formData) if err != nil { return "", err } jsonBytes, err := json.MarshalIndent(sortedOutput, "", " ") if err != nil { return "", err } return string(jsonBytes), nil } // FormatPort 格式化端口 func (service *formatterService) FormatPort(ctx context.Context, req interface{}) []int { if req == nil { return []int{} } reqStr := cast.ToString(req) 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 } // OldFormat 旧格式 func (service *formatterService) OldFormat(ctx context.Context, req *[]model.GameShieldBackend) (map[string]v1.SendGameShieldBackend, error) { res := make(map[string]v1.SendGameShieldBackend) var UdpSessionTimeout string var MaxBandwidth string for _, v := range *req { service.logger.Info("v", zap.Any("v", v)) addr := fmt.Sprintf("%s:%s", v.SourceMachineIP, v.ConnectPort) service.logger.Info("addr", zap.Any("addr", addr)) sdkPort, err := strconv.Atoi(v.SdkPort) if err != nil { return nil, err } if v.Protocol == "udp" { UdpSessionTimeout = "300s" } else { UdpSessionTimeout = "" } keyName := fmt.Sprintf("key%d", v.KeySort) if v.Type != "pc" { v.SdkIp = "" } if v.MaxBandwidth == 1 { MaxBandwidth = "100m" } else { MaxBandwidth = "" } res[keyName] = v1.SendGameShieldBackend{ Addr: []string{addr}, Protocol: v.Protocol, ProxyAddr: v.ProxyAddr, SdkPort: sdkPort, UdpSessionTimeout: UdpSessionTimeout, SdkIp: v.SdkIp, MaxBandwidth: MaxBandwidth, Host: v.Host, } service.logger.Info("res", zap.Any("res", res[keyName].Addr)) } return res, nil } 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 } for _, item := range req.Items { // 提取必要字段 sourceIP := item.SourceMachineIP // 假设结构体中有这个字段 if sourceIP == "" { return nil, fmt.Errorf("没有有效源IP的配置") // 跳过没有有效源IP的配置 } protocol := item.Protocol // 假设结构体中有这个字段 if protocol == "" { return nil, fmt.Errorf("没有有效协议的配置") // 跳过没有有效协议的配置 } // 获取端口数组 conPorts := service.FormatPort(ctx, item.ConnectPort) sdkPorts := service.FormatPort(ctx, item.SdkPort) // 验证端口数量 if len(sdkPorts) > 0 && len(conPorts) != len(sdkPorts) { return nil, fmt.Errorf("端口数量不匹配") } // 处理每一对端口 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, Type: item.Type, } // 设置主机名(如果存在) if item.Protocol == "http" && item.Host != "" { itemMap.Host = item.Host } // 根据协议设置不同属性 if protocol != "udp" { if item.RealIp == "agent" { itemMap.AgentAddr = fmt.Sprintf("%s:%s", sourceIP, "23350") } itemMap.ProxyAddr = userIp + ":32353" } else { itemMap.ProxyAddr = "" itemMap.UdpSessionTimeout = "300s" } if item.Type != "pc" { itemMap.SdkIp = "" } else { itemMap.SdkIp = item.SdkIp } if pointer.GetInt(item.MaxBandwidth) == 1 { itemMap.MaxBandwidth = "100m" } else { itemMap.MaxBandwidth = "" } // 设置SDK端口 - 使用数组中的具体端口 if len(sdkPorts) != 0 { if sdkPorts[i] <= 1024 { if item.Type == "mobile" { return nil, fmt.Errorf("移动端不支持SSH端口") } } itemMap.SdkPort = sdkPorts[i] } if len(item.Remark) > 0 { itemMap.Remark = item.Remark } 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) } // 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 sortedOutput, nil } // 验证后端数据 func (service *formatterService) ValidateBackendData(ctx context.Context, data map[string]v1.SendGameShieldBackend, hostId int) error { // 获取配置限制 configCount, err := service.hostService.GetGameShieldConfig(ctx, hostId) if err != nil { return fmt.Errorf("获取配置限制失败: %w", err) } // 提取源机IP和SDK端口 sourceIPs := make(map[string]bool) ruleEntriesCount := int64(0) maxBandwidthCount := int64(0) // 分协议检查SDK端口 tcpHttpPorts := make(map[int]bool) // TCP和HTTP共用 udpPorts := make(map[int]bool) // UDP单独使用 for _, item := range data { // 计算规则条目数 ruleEntriesCount += int64(len(item.Addr)) // 计算源机IP数 for _, addr := range item.Addr { parts := strings.Split(addr, ":") if len(parts) > 0 { sourceIPs[parts[0]] = true } } // 计算最大带宽设置数 if item.MaxBandwidth != "" { maxBandwidthCount++ } // 根据协议类型检查SDK端口重复 if item.SdkPort != 0 { // 根据协议字段来判断SDK端口是否重复 switch strings.ToUpper(item.Protocol) { case "TCP", "HTTP": if tcpHttpPorts[item.SdkPort] { return fmt.Errorf("TCP/HTTP SDK端口%d重复,每个端口只能配置一次", item.SdkPort) } tcpHttpPorts[item.SdkPort] = true case "UDP": if udpPorts[item.SdkPort] { return fmt.Errorf("UDP SDK端口%d重复,每个端口只能配置一次", item.SdkPort) } udpPorts[item.SdkPort] = true default: return fmt.Errorf("不支持的协议类型: %s", item.Protocol) } } } // 验证源机数量 if int64(len(sourceIPs)) > configCount.SourceMachinesCount { return fmt.Errorf("超出最大源机数量,当前配置允许%d个源机,合并后有%d个源机", configCount.SourceMachinesCount, len(sourceIPs)) } // 验证规则条目数 if ruleEntriesCount > configCount.RuleEntriesCount { return fmt.Errorf("超出最大规则数量,当前配置允许%d个规则,合并后有%d个规则", configCount.RuleEntriesCount, ruleEntriesCount) } // 验证最大带宽设置数 if maxBandwidthCount > configCount.MaxBandwidthCount { return fmt.Errorf("超出最大带宽数量,当前配置允许%d个带宽设置,合并后有%d个", configCount.MaxBandwidthCount, maxBandwidthCount) } return nil }