166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package cloudflare
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"sync"
|
||
"time"
|
||
|
||
"agent-wdd/log"
|
||
)
|
||
|
||
const (
|
||
// CloudflareAPIBaseURL is the base URL for Cloudflare API
|
||
CloudflareAPIBaseURL = "https://api.cloudflare.com/client/v4"
|
||
// DefaultTimeout is the default timeout for HTTP requests
|
||
DefaultTimeout = 5 * time.Second
|
||
)
|
||
|
||
// CloudflareClient represents a client for interacting with Cloudflare API
|
||
type CloudflareClient struct {
|
||
apiToken string
|
||
baseURL string
|
||
client *http.Client
|
||
userAgent string
|
||
dnsNameToIDMap map[string]string // 域名到ID的映射
|
||
}
|
||
|
||
var (
|
||
instance *CloudflareClient
|
||
once sync.Once
|
||
)
|
||
|
||
// Response is the general response structure from Cloudflare API
|
||
type Response struct {
|
||
Result interface{} `json:"result"`
|
||
ResultInfo *ResultInfo `json:"result_info,omitempty"`
|
||
Success bool `json:"success"`
|
||
Errors []Error `json:"errors"`
|
||
Messages []interface{} `json:"messages"`
|
||
}
|
||
|
||
// ResultInfo contains pagination information
|
||
type ResultInfo struct {
|
||
Page int `json:"page"`
|
||
PerPage int `json:"per_page"`
|
||
TotalPages int `json:"total_pages"`
|
||
Count int `json:"count"`
|
||
TotalCount int `json:"total_count"`
|
||
}
|
||
|
||
// Error represents an error returned by the Cloudflare API
|
||
type Error struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
ErrorChain []Error `json:"error_chain,omitempty"`
|
||
}
|
||
|
||
// NewClient creates a new instance of CloudflareClient with the given API token
|
||
func NewClient(apiToken string) *CloudflareClient {
|
||
log.Debug("Creating new Cloudflare client")
|
||
return &CloudflareClient{
|
||
apiToken: apiToken,
|
||
baseURL: CloudflareAPIBaseURL,
|
||
client: &http.Client{Timeout: DefaultTimeout},
|
||
userAgent: "WddSuperAgent/1.0",
|
||
}
|
||
}
|
||
|
||
// GetInstance returns the singleton instance of CloudflareClient
|
||
func GetInstance(apiToken string) *CloudflareClient {
|
||
if instance == nil {
|
||
once.Do(func() {
|
||
instance = NewClient(apiToken)
|
||
log.Info("Cloudflare client singleton instance created")
|
||
})
|
||
}
|
||
return instance
|
||
}
|
||
|
||
// SetAPIToken updates the API token used for authentication
|
||
func (c *CloudflareClient) SetAPIToken(apiToken string) {
|
||
c.apiToken = apiToken
|
||
log.Info("Cloudflare API token updated")
|
||
}
|
||
|
||
// SetTimeout updates the HTTP client timeout
|
||
func (c *CloudflareClient) SetTimeout(timeout time.Duration) {
|
||
c.client.Timeout = timeout
|
||
log.Info("Cloudflare client timeout updated to %v", timeout)
|
||
}
|
||
|
||
// doRequest performs an HTTP request with the given method, URL, and body
|
||
func (c *CloudflareClient) doRequest(method, url string, body interface{}) (*Response, error) {
|
||
log.Debug("Performing %s request to %s", method, url)
|
||
|
||
var reqBody io.Reader
|
||
if body != nil {
|
||
jsonBody, err := json.Marshal(body)
|
||
if err != nil {
|
||
log.Error("Failed to marshal request body: %v", err)
|
||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||
}
|
||
reqBody = bytes.NewBuffer(jsonBody)
|
||
}
|
||
|
||
req, err := http.NewRequest(method, url, reqBody)
|
||
if err != nil {
|
||
log.Error("Failed to create HTTP request: %v", err)
|
||
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||
}
|
||
|
||
// Set headers
|
||
req.Header.Set("Authorization", "Bearer "+c.apiToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("User-Agent", c.userAgent)
|
||
|
||
// Perform request
|
||
resp, err := c.client.Do(req)
|
||
if err != nil {
|
||
log.Error("Failed to execute HTTP request: %v", err)
|
||
return nil, fmt.Errorf("failed to execute HTTP request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// Read response body
|
||
respBody, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
log.Error("Failed to read response body: %v", err)
|
||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||
}
|
||
|
||
// Parse response
|
||
var cfResp Response
|
||
if err := json.Unmarshal(respBody, &cfResp); err != nil {
|
||
log.Error("Failed to unmarshal response: %v", err)
|
||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||
}
|
||
|
||
// Check for API errors
|
||
if !cfResp.Success {
|
||
errorMsg := "Cloudflare API error"
|
||
if len(cfResp.Errors) > 0 {
|
||
errorMsg = fmt.Sprintf("%s: %s (code: %d)", errorMsg, cfResp.Errors[0].Message, cfResp.Errors[0].Code)
|
||
}
|
||
log.Error(errorMsg)
|
||
return &cfResp, fmt.Errorf(errorMsg)
|
||
}
|
||
|
||
log.Debug("Request completed successfully")
|
||
return &cfResp, nil
|
||
}
|
||
|
||
// GetDNSNameToIDMap 获取域名到ID的映射
|
||
func (c *CloudflareClient) GetDNSNameToIDMap(zoneID string) map[string]string {
|
||
|
||
// 如果dnsNameToIDMap为空,则获取所有域名到ID的映射
|
||
if c.dnsNameToIDMap == nil {
|
||
c.ListDNSRecords(zoneID, nil)
|
||
}
|
||
|
||
return c.dnsNameToIDMap
|
||
}
|