package service import ( "bytes" "context" "crypto/tls" "encoding/json" "fmt" v1 "github.com/go-nunu/nunu-layout-advanced/api/v1" "github.com/spf13/viper" "io" "net/http" "net/url" "strings" "time" ) type AoDunService interface { DomainWhiteList(ctx context.Context, domain string, ip string, apiType string) error AddWhiteStaticList(ctx context.Context, req []v1.IpInfo) error DelWhiteStaticList(ctx context.Context, req v1.DeleteIp) error } func NewAoDunService( service *Service, conf *viper.Viper, ) AoDunService { return &aoDunService{ Service: service, Url: conf.GetString("aodun.Url"), clientID: conf.GetString("aodun.clientID"), username: conf.GetString("aodun.username"), password: conf.GetString("aodun.password"), IPusername: conf.GetString("aodunIp.username"), IPpassword: conf.GetString("aodunIp.password"), domainUserName: conf.GetString("domainWhite.username"), domainPassword: conf.GetString("domainWhite.password"), } } type aoDunService struct { *Service Url string clientID string username string password string IPusername string IPpassword string domainUserName string domainPassword string } func (s *aoDunService) sendFormData(ctx context.Context,apiUrl string,tokenType string,token string,formData map[string]interface{}) ([]byte,error) { URL := s.Url + apiUrl jsonData, err := json.Marshal(formData) if err != nil { return nil, fmt.Errorf("序列化请求数据失败: %w", err) } req, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err) } // 设置请求头 Content-Type 为 "application/json" req.Header.Set("Content-Type", "application/json") if tokenType == "" { req.Header.Set("Authorization", tokenType + " " + token) } tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // <--- 关键修改:忽略 SSL 验证 } // 5. 使用 HTTP 客户端发送请求 client := &http.Client{ Transport: tr, Timeout: 15 * time.Second, // 设置一个合理的超时时间,例如15秒 } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err) } // defer 确保在函数返回前关闭响应体,防止资源泄露 defer resp.Body.Close() // 6. 读取响应体内容 body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应体失败: %w", err) } return body, nil } func (s *aoDunService) sendDomainFormData(ctx context.Context,domain string,ip string,apiType string) ([]byte,error) { var URL string if apiType == "add" { URL = "http://zapi.zzybgp.com/api/user/do_main" } else { URL = "http://zapi.zzybgp.com/api/user/do_main/delete" } formData := url.Values{} formData.Set("username", s.domainUserName) formData.Set("password", s.domainPassword) formData.Add("do_main_list[name][]", domain) formData.Add("do_main_list[ip]", ip) encodedData := formData.Encode() req, err := http.NewRequest("POST", URL, bytes.NewBuffer([]byte(encodedData))) if err != nil { return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err) } // 设置请求头 Content-Type 为 "application/x-www-form-urlencoded" req.Header.Set("Content-Type", "application/x-www-form-urlencoded") tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // <--- 关键修改:忽略 SSL 验证 } // 5. 使用 HTTP 客户端发送请求 client := &http.Client{ Transport: tr, Timeout: 15 * time.Second, // 设置一个合理的超时时间,例如15秒 } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err) } // defer 确保在函数返回前关闭响应体,防止资源泄露 defer resp.Body.Close() // 6. 读取响应体内容 body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应体失败: %w", err) } return body, nil } func (s *aoDunService) GetToken(ctx context.Context) (string,string,error) { formData := map[string]interface{}{ "ClientID": s.clientID, "GrantType": "password", "Username": s.IPusername, "Password": s.IPpassword, } resBody, err := s.sendFormData(ctx,"/oauth/token","","",formData) if err != nil { return "", "", err } // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体 var responsePayload v1.GetTokenRespone if err := json.Unmarshal(resBody, &responsePayload); err != nil { // 如果反序列化失败,可能是响应格式不符合预期 return "", "", fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err) } // 8. 检查 API 返回的操作结果代码 if responsePayload.Code != 0 { return "", "", fmt.Errorf("API 错误: code %d, msg '%s', remote_ip '%s'", responsePayload.Code, responsePayload.Msg, responsePayload.RemoteIP) } // 9. 成功:返回 access_token if responsePayload.AccessToken == "" { // 理论上 code 为 0 时应该有 access_token,这是一个额外的健壮性检查 return "", "", fmt.Errorf("API 成功 (code 0) 但 access_token 为空") } return responsePayload.TokenType,responsePayload.AccessToken, nil } func (s *aoDunService) AddWhiteStaticList(ctx context.Context,req []v1.IpInfo) error { tokenType,token, err := s.GetToken(ctx) if err != nil { return err } formData := map[string]interface{}{ "action" : "add", "bwflag" : "white", "insert_bw_list": req, } resBody, err := s.sendFormData(ctx,"/v1.0/firewall/static_bw_list",tokenType,token,formData) if err != nil { return err } // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体 var res v1.IpResponse if err := json.Unmarshal(resBody, &res); err != nil { // 如果反序列化失败,可能是响应格式不符合预期 return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err) } if res.Code != 0 { if strings.Contains(res.Msg,"操作部分成功,重复IP如下") { s.logger.Info(res.Msg) return nil } return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg) } return nil } func (s *aoDunService) GetWhiteStaticList(ctx context.Context,ip string) (int,error) { tokenType,token, err := s.GetToken(ctx) if err != nil { return 0, err } formData := map[string]interface{}{ "action" : "get", "bwflag" : "white", "page" : 1, "ids": ip, } resBody, err := s.sendFormData(ctx,"/v1.0/firewall/static_bw_list",tokenType,token,formData) if err != nil { return 0, err } // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体 var res v1.IpGetResponse // 使用我们定义的 IpResponse 结构体 if err := json.Unmarshal(resBody, &res); err != nil { // 如果反序列化失败,说明响应格式不符合预期 return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err) } // 2. 检查 API 返回的 code,这是处理业务失败的关键 if res.Code != 0 { // API 返回了错误码,例如 IP 不存在、参数错误等 return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg) } // 3. 检查 data 数组是否为空 // 即使 code 为 0,也可能因为没有匹配的数据而返回一个空数组 if len(res.Data) == 0 { return 0, fmt.Errorf("API 调用成功,但未找到与 IP '%s' 相关的记录", ip) } // 4. 获取 ID 并返回 // 假设我们总是取返回结果中的第一个元素的 ID id := res.Data[0].ID return id, nil // 成功!返回获取到的 id 和 nil 错误 } func (s *aoDunService) DelWhiteStaticList(ctx context.Context, req v1.DeleteIp) error { tokenType, token, err := s.GetToken(ctx) if err != nil { return err } formData := map[string]interface{}{ "action": "del", "bwflag": "white", "flag": 0, "ids": req.Ids, } resBody, err := s.sendFormData(ctx, "/v1.0/firewall/static_bw_list", tokenType, token, formData) if err != nil { return err } var res v1.IpResponse if err := json.Unmarshal(resBody, &res); err != nil { return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err) } if res.Code != 0 { return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg) } return nil } func (s *aoDunService) DomainWhiteList(ctx context.Context, domain string, ip string, apiType string) error { resBody, err := s.sendDomainFormData(ctx,domain,ip,apiType) if err != nil { return err } var res v1.DomainResponse if err := json.Unmarshal(resBody, &res); err != nil { return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err) } if res.Code != 200 && apiType == "add" { return fmt.Errorf("API 错误: code %d, msg '%s', data '%s", res.Code, res.Msg, res.Info) } if res.Code != 600 && apiType == "del" { return fmt.Errorf("API 错误: code %d, msg '%s', data '%s", res.Code, res.Msg, res.Info) } return nil }