aodun.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package service
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. "encoding/json"
  7. "fmt"
  8. "github.com/davecgh/go-spew/spew"
  9. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  10. "github.com/spf13/viper"
  11. "io"
  12. "net/http"
  13. "net/url"
  14. "strings"
  15. "time"
  16. )
  17. type AoDunService interface {
  18. DomainWhiteList(ctx context.Context, domain string, ip string, apiType string) error
  19. AddWhiteStaticList(ctx context.Context, req []v1.IpInfo) error
  20. DelWhiteStaticList(ctx context.Context, id string) error
  21. GetWhiteStaticList(ctx context.Context,ip string) (int,error)
  22. }
  23. func NewAoDunService(
  24. service *Service,
  25. conf *viper.Viper,
  26. ) AoDunService {
  27. // 1. 创建一个可复用的 Transport,并配置好 TLS 和其他参数
  28. tr := &http.Transport{
  29. TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 忽略 SSL 验证
  30. MaxIdleConns: 100, // 最大空闲连接数
  31. IdleConnTimeout: 90 * time.Second, // 空闲连接超时时间
  32. }
  33. // 2. 基于该 Transport 创建一个可复用的 http.Client
  34. client := &http.Client{
  35. Transport: tr,
  36. Timeout: 15 * time.Second, // 设置所有请求的默认超时时间
  37. }
  38. return &aoDunService{
  39. Service: service,
  40. Url: conf.GetString("aodun.Url"),
  41. clientID: conf.GetString("aodun.clientID"),
  42. username: conf.GetString("aodun.username"),
  43. password: conf.GetString("aodun.password"),
  44. IPusername: conf.GetString("aodunIp.username"),
  45. IPpassword: conf.GetString("aodunIp.password"),
  46. domainUserName: conf.GetString("domainWhite.username"),
  47. domainPassword: conf.GetString("domainWhite.password"),
  48. httpClient: client, // 存储共享的 client
  49. }
  50. }
  51. type aoDunService struct {
  52. *Service
  53. Url string
  54. clientID string
  55. username string
  56. password string
  57. IPusername string
  58. IPpassword string
  59. domainUserName string
  60. domainPassword string
  61. httpClient *http.Client // <--- 新增 http client 字段
  62. }
  63. func (s *aoDunService) sendFormData(ctx context.Context, apiUrl string, tokenType string, token string, formData map[string]interface{}) ([]byte, error) {
  64. URL := s.Url + apiUrl
  65. jsonData, err := json.Marshal(formData)
  66. if err != nil {
  67. return nil, fmt.Errorf("序列化请求数据失败: %w", err)
  68. }
  69. // 使用带有 context 的请求,以便上游可以控制请求的取消
  70. req, err := http.NewRequestWithContext(ctx, "POST", URL, bytes.NewBuffer(jsonData))
  71. if err != nil {
  72. return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err)
  73. }
  74. // 设置请求头
  75. req.Header.Set("Content-Type", "application/json")
  76. // 修正逻辑:当 token 不为空时才设置 Authorization
  77. if token != "" {
  78. req.Header.Set("Authorization", tokenType+" "+token)
  79. }
  80. // 使用结构体中共享的 httpClient 实例发送请求
  81. resp, err := s.httpClient.Do(req)
  82. if err != nil {
  83. return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err)
  84. }
  85. defer resp.Body.Close()
  86. // 6. 读取响应体内容
  87. body, err := io.ReadAll(resp.Body)
  88. if err != nil {
  89. return nil, fmt.Errorf("读取响应体失败: %w", err)
  90. }
  91. return body, nil
  92. }
  93. func (s *aoDunService) sendDomainFormData(ctx context.Context, domain string, ip string, apiType string) ([]byte, error) {
  94. var URL string
  95. if apiType == "add" {
  96. URL = "http://zapi.zzybgp.com/api/user/do_main"
  97. } else {
  98. URL = "http://zapi.zzybgp.com/api/user/do_main/delete"
  99. }
  100. formData := url.Values{}
  101. formData.Set("username", s.domainUserName)
  102. formData.Set("password", s.domainPassword)
  103. formData.Add("do_main_list[name][]", domain)
  104. formData.Add("do_main_list[ip]", ip)
  105. encodedData := formData.Encode()
  106. // 使用带有 context 的请求
  107. req, err := http.NewRequestWithContext(ctx, "POST", URL, bytes.NewBuffer([]byte(encodedData)))
  108. if err != nil {
  109. return nil, fmt.Errorf("创建 HTTP 请求失败: %w", err)
  110. }
  111. // 设置请求头
  112. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  113. // 使用共享的 httpClient 实例发送请求
  114. resp, err := s.httpClient.Do(req)
  115. if err != nil {
  116. return nil, fmt.Errorf("发送 HTTP 请求失败: %w", err)
  117. }
  118. defer resp.Body.Close()
  119. // 6. 读取响应体内容
  120. body, err := io.ReadAll(resp.Body)
  121. if err != nil {
  122. return nil, fmt.Errorf("读取响应体失败: %w", err)
  123. }
  124. return body, nil
  125. }
  126. // sendAuthenticatedRequest 封装了需要认证的API请求的通用流程:获取token -> 发送请求。
  127. func (s *aoDunService) sendAuthenticatedRequest(ctx context.Context, apiPath string, formData map[string]interface{}) ([]byte, error) {
  128. tokenType, token, err := s.GetToken(ctx)
  129. if err != nil {
  130. // 如果获取token失败,直接返回错误
  131. return nil, err
  132. }
  133. // 使用获取到的token发送请求
  134. return s.sendFormData(ctx, apiPath, tokenType, token, formData)
  135. }
  136. func (s *aoDunService) GetToken(ctx context.Context) (string, string, error) {
  137. formData := map[string]interface{}{
  138. "ClientID": s.clientID,
  139. "GrantType": "password",
  140. "Username": s.IPusername,
  141. "Password": s.IPpassword,
  142. }
  143. resBody, err := s.sendFormData(ctx,"/oauth/token","","",formData)
  144. if err != nil {
  145. return "", "", err
  146. }
  147. // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
  148. var responsePayload v1.GetTokenRespone
  149. if err := json.Unmarshal(resBody, &responsePayload); err != nil {
  150. // 如果反序列化失败,可能是响应格式不符合预期
  151. return "", "", fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
  152. }
  153. // 8. 检查 API 返回的操作结果代码
  154. if responsePayload.Code != 0 {
  155. return "", "", fmt.Errorf("API 错误: code %d, msg '%s', remote_ip '%s'",
  156. responsePayload.Code, responsePayload.Msg, responsePayload.RemoteIP)
  157. }
  158. // 9. 成功:返回 access_token
  159. if responsePayload.AccessToken == "" {
  160. // 理论上 code 为 0 时应该有 access_token,这是一个额外的健壮性检查
  161. return "", "", fmt.Errorf("API 成功 (code 0) 但 access_token 为空")
  162. }
  163. return responsePayload.TokenType,responsePayload.AccessToken, nil
  164. }
  165. func (s *aoDunService) AddWhiteStaticList(ctx context.Context, req []v1.IpInfo) error {
  166. formData := map[string]interface{}{
  167. "action": "add",
  168. "bwflag": "white",
  169. "insert_bw_list": req,
  170. }
  171. // 使用封装好的方法发送认证请求
  172. resBody, err := s.sendAuthenticatedRequest(ctx, "/v1.0/firewall/static_bw_list", formData)
  173. if err != nil {
  174. return err
  175. }
  176. // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
  177. var res v1.IpResponse
  178. if err := json.Unmarshal(resBody, &res); err != nil {
  179. // 如果反序列化失败,可能是响应格式不符合预期
  180. return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
  181. }
  182. if res.Code != 0 {
  183. if strings.Contains(res.Msg,"操作部分成功,重复IP如下") {
  184. s.logger.Info(res.Msg)
  185. return nil
  186. }
  187. return fmt.Errorf("API 错误: code %d, msg '%s'",
  188. res.Code, res.Msg)
  189. }
  190. return nil
  191. }
  192. func (s *aoDunService) GetWhiteStaticList(ctx context.Context, ip string) (int, error) {
  193. formData := map[string]interface{}{
  194. "action": "get",
  195. "bwflag": "white",
  196. "page": 1,
  197. "ids": ip,
  198. }
  199. // 使用封装好的方法发送认证请求
  200. resBody, err := s.sendAuthenticatedRequest(ctx, "/v1.0/firewall/static_bw_list", formData)
  201. if err != nil {
  202. return 0, err
  203. }
  204. // 7. 将响应体 JSON 数据反序列化到 ResponsePayload 结构体
  205. var res v1.IpGetResponse // 使用我们定义的 IpResponse 结构体
  206. if err := json.Unmarshal(resBody, &res); err != nil {
  207. // 如果反序列化失败,说明响应格式不符合预期
  208. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  209. }
  210. // 2. 检查 API 返回的 code,这是处理业务失败的关键
  211. if res.Code != 0 {
  212. // API 返回了错误码,例如 IP 不存在、参数错误等
  213. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
  214. }
  215. // 3. 检查 data 数组是否为空
  216. // 即使 code 为 0,也可能因为没有匹配的数据而返回一个空数组
  217. if len(res.Data) == 0 {
  218. return 0, fmt.Errorf("API 调用成功,但未找到与 IP '%s' 相关的记录", ip)
  219. }
  220. // 4. 获取 ID 并返回
  221. // 假设我们总是取返回结果中的第一个元素的 ID
  222. id := res.Data[0].ID
  223. spew.Dump(id)
  224. return id, nil // 成功!返回获取到的 id 和 nil 错误
  225. }
  226. func (s *aoDunService) DelWhiteStaticList(ctx context.Context, id string) error {
  227. formData := map[string]interface{}{
  228. "action": "del",
  229. "bwflag": "white",
  230. "flag": 0,
  231. "ids": id,
  232. }
  233. // 使用封装好的方法发送认证请求
  234. resBody, err := s.sendAuthenticatedRequest(ctx, "/v1.0/firewall/static_bw_list", formData)
  235. if err != nil {
  236. return err
  237. }
  238. var res v1.IpResponse
  239. if err := json.Unmarshal(resBody, &res); err != nil {
  240. return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
  241. }
  242. if res.Code != 0 {
  243. return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Msg)
  244. }
  245. return nil
  246. }
  247. func (s *aoDunService) DomainWhiteList(ctx context.Context, domain string, ip string, apiType string) error {
  248. resBody, err := s.sendDomainFormData(ctx,domain,ip,apiType)
  249. if err != nil {
  250. return err
  251. }
  252. var res v1.DomainResponse
  253. if err := json.Unmarshal(resBody, &res); err != nil {
  254. return fmt.Errorf("反序列化响应 JSON 失败 ( 内容: %s): %w", string(resBody), err)
  255. }
  256. if res.Code != 200 && apiType == "add" {
  257. return fmt.Errorf("API 错误: code %d, msg '%s', data '%s", res.Code, res.Msg, res.Info)
  258. }
  259. if res.Code != 600 && apiType == "del" {
  260. return fmt.Errorf("API 错误: code %d, msg '%s', data '%s", res.Code, res.Msg, res.Info)
  261. }
  262. return nil
  263. }