package service import ( "context" "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" "github.com/jinzhu/copier" "github.com/spf13/cast" "github.com/spf13/viper" "strconv" "strings" ) 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, req *v1.DelGameShieldBackendRequest) (string, error) GetGameShieldRequired(ctx context.Context, req *v1.GameShieldBackendArrayRequest) (*v1.GetGameShieldRequiredResponse, int, error) ReplacementSourceMachineIp(ctx context.Context, req *v1.ReplacementSourceMachineIpRequest) 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, shieldService GameShieldService, hostService HostService, ) 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"), shieldService: shieldService, hostService: hostService, } } type gameShieldBackendService struct { *Service gameShieldBackendRepository repository.GameShieldBackendRepository crawlerService CrawlerService gameShieldRepository repository.GameShieldRepository gameShieldPublicIpService GameShieldPublicIpService duedate DuedateService formatter FormatterService Url string parser ParserService required RequiredService shieldService GameShieldService hostService HostService } 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, 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 } if len(oldBackend) != 0 { count = oldBackend[0].KeySort } OldBackend, err := s.formatter.OldFormat(ctx, &oldBackend) res.Backend, err = s.formatter.FormatBackendData(ctx, req, OldBackend, count) if err != nil { return nil, 0, err } res.Cookie, err = s.crawlerService.GetLoginCookie(ctx) if err != nil { return nil, 0, err } res.DunName = gameShield.DunName 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) + "_" + require.DunName tokens, err := s.crawlerService.GetFormTokens(ctx, tokenUrl, require.Cookie) if err != nil { return "", 0, err } configCount, err := s.hostService.GetGameShieldConfig(ctx, req.HostId) if err != nil { return "", 0, fmt.Errorf("获取配置限制失败: %w", err) } formData := map[string]interface{}{ "app_name": require.DunName, "gateway_group_id": 5, // TODO: 临时写死 "backend": require.Backend, "rule_id": require.RuleId, "expired_at": require.ExpiredAt, "max_device_count": configCount.OnlineDevicesCount, "sdk_args": "--max-bandwidth 1000K", "__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, require.DunName, "rule_id") if err != nil { return "", 0, err } timeBase, err := s.gameShieldRepository.GetGameShieldNextduedate(ctx, int64(req.Uid), req.HostId) if err != nil { return "", 0, err } timestampSec, err := strconv.ParseInt(timeBase, 10, 64) if err != nil { return "", 0, err } if err := s.gameShieldRepository.UpdateGameShieldByHostId(ctx, &model.GameShield{HostId: req.HostId, Key: KeyAndField.Key, ExpireTime: timestampSec}); 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) { // 1. 获取当前所有数据库记录 currentData, err := s.gameShieldBackendRepository.GetGameShieldBackendByHostId(ctx, req.HostId) if err != nil { return "", fmt.Errorf("获取当前配置失败: %w", err) } // 2. 创建当前记录的副本用于模拟修改 simulatedData := make([]model.GameShieldBackend, len(currentData)) copy(simulatedData, currentData) // 3. 创建ID到索引的映射,方便查找和修改 idToIndex := make(map[int64]int) for i, item := range simulatedData { idToIndex[int64(item.Id)] = i } // 4. 在副本上应用修改 for _, v := range req.Items { if v.Id == 0 { return "", fmt.Errorf("id 不能为空") } // 查找对应记录 idx, exists := idToIndex[int64(v.Id)] if !exists { return "", fmt.Errorf("ID为%d的记录不存在", v.Id) } // 更新记录(只更新需要修改的字段) simulatedData[idx].SourceMachineIP = v.SourceMachineIP simulatedData[idx].ConnectPort = v.ConnectPort simulatedData[idx].Protocol = v.Protocol simulatedData[idx].SdkPort = v.SdkPort simulatedData[idx].SdkIp = v.SdkIp simulatedData[idx].Type = v.Type //获取指针的值 simulatedData[idx].MaxBandwidth = pointer.GetInt(v.MaxBandwidth) } // 5. 使用模拟修改后的数据进行验证 // 转换数据格式 simulatedBackend, err := s.formatter.OldFormat(ctx, &simulatedData) if err != nil { return "", fmt.Errorf("格式化模拟数据失败: %w", err) } // 验证修改后的配置 err = s.formatter.ValidateBackendData(ctx, simulatedBackend, req.HostId) if err != nil { return "", fmt.Errorf("验证失败: %w", err) } // 6. 验证通过,执行实际的数据库修改 for _, v := range req.Items { if err := s.gameShieldBackendRepository.EditGameShieldBackend(ctx, &v); err != nil { return "", fmt.Errorf("修改数据失败(ID:%d): %w", v.Id, err) } } // 7. 更新远程配置 res, _, err := s.GameShieldBackend(ctx, &v1.GameShieldBackendArrayRequest{ HostId: req.HostId, Uid: req.Uid, Items: nil, }) if err != nil { return "", fmt.Errorf("更新配置失败: %w", err) } return res, nil } func (s *gameShieldBackendService) DeleteGameShieldBackend(ctx context.Context, req *v1.DelGameShieldBackendRequest) (string, error) { for _, v := range req.Ids { if err := s.gameShieldBackendRepository.DeleteGameShieldBackend(ctx, v, req.HostId); err != nil { return "", err } } res, _, err := s.GameShieldBackend(ctx, &v1.GameShieldBackendArrayRequest{HostId: req.HostId, Uid: req.Uid, Items: nil}) if err != nil { return "", err } return res, 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 v.Type != "pc" { v.SdkIp = "127.0.0.1" } if v.MaxBandwidth == "100m" { v.MaxBandwidth = "1" } else { v.MaxBandwidth = "0" } if v.Protocol != "http" { v.Host = "" } if err := s.gameShieldBackendRepository.AddGameShieldBackend(ctx, &model.GameShieldBackend{ HostId: hostId, KeySort: key, SourceMachineIP: parts[0], Protocol: v.Protocol, ProxyAddr: v.ProxyAddr, ConnectPort: parts[1], SdkIp: v.SdkIp, SdkPort: strconv.Itoa(v.SdkPort), Type: v.Type, MaxBandwidth: cast.ToInt(v.MaxBandwidth), Host: v.Host, Remark: v.Remark, }); err != nil { return err } } return nil } // ReplacementSourceMachineIp 替换源机ip func (s *gameShieldBackendService) ReplacementSourceMachineIp(ctx context.Context, req *v1.ReplacementSourceMachineIpRequest) error { var data []v1.GameShieldBackendRequest var baseData []model.GameShieldBackend var err error if len(req.OldSourceMachineIp) != 0 { baseData, err = s.gameShieldBackendRepository.GetGameShieldBackendByHostIdAndSourceMachineIp(ctx, req.HostId, req.OldSourceMachineIp) if err != nil { return err } for i := range baseData { baseData[i].SourceMachineIP = req.NewSourceMachineIp } } if len(req.OldSourceMachineIp) == 0 { baseData, err = s.gameShieldBackendRepository.GetGameShieldBackendByHostId(ctx, req.HostId) if err != nil { return err } for i := range baseData { baseData[i].SourceMachineIP = req.NewSourceMachineIp } } err = copier.Copy(&data, &baseData) if err != nil { return err } _, err = s.EditGameShieldBackend(ctx, &v1.GameShieldBackendArrayRequest{ Items: data, HostId: req.HostId, Uid: req.Uid, }) if err != nil { return err } return nil }