package service import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" ) type cloudflareProvider struct { token string client *http.Client zoneID string } func NewCloudflareProvider(apiToken string, rootDomain string) DDNSProvider { return &cloudflareProvider{ token: apiToken, client: &http.Client{Timeout: 10 * time.Second}, // 注意:实际场景需通过 rootDomain 获取 zoneID。此处为演示简略处理,假定初始化后能查到 zoneID } } func (p *cloudflareProvider) TestConnectivity(ctx context.Context) error { req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/user/tokens/verify", nil) req.Header.Set("Authorization", "Bearer "+p.token) resp, err := p.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("Cloudflare 认证失败,状态码: %d", resp.StatusCode) } return nil } func (p *cloudflareProvider) SyncRecords(ctx context.Context, baseDomain string, records []DDNSRecord) error { // 实战中首先需要查询 Zones 获取 zone_id // 为了简化演示修复,此处假设获取 zone 的逻辑 (伪代码实现,可进一步真实拉取) zoneID, err := p.getZoneID(ctx, baseDomain) if err != nil { return err } for _, rec := range records { fullDomain := rec.Name + "." + baseDomain if rec.Name == "@" { fullDomain = baseDomain } // 1. 查询现有的 Record recordID, _ := p.getRecordID(ctx, zoneID, fullDomain, rec.Type) // 2. 构造 payload payload := map[string]interface{}{ "type": rec.Type, "name": fullDomain, "content": rec.Value, "ttl": 1, // 自动 "proxied": false, } data, _ := json.Marshal(payload) var req *http.Request if recordID != "" { // 更新 req, _ = http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID), bytes.NewReader(data)) } else { // 创建 req, _ = http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID), bytes.NewReader(data)) } req.Header.Set("Authorization", "Bearer "+p.token) req.Header.Set("Content-Type", "application/json") resp, err := p.client.Do(req) if err != nil { return err } resp.Body.Close() } return nil } // 辅助方法:获取 Zone ID func (p *cloudflareProvider) getZoneID(ctx context.Context, domain string) (string, error) { req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.cloudflare.com/client/v4/zones?name="+domain, nil) req.Header.Set("Authorization", "Bearer "+p.token) resp, err := p.client.Do(req) if err != nil { return "", err } defer resp.Body.Close() var result struct { Result []struct { ID string `json:"id"` } `json:"result"` } body, _ := io.ReadAll(resp.Body) json.Unmarshal(body, &result) if len(result.Result) > 0 { return result.Result[0].ID, nil } return "", fmt.Errorf("找不到域名 %s 的 Zone", domain) } // 辅助方法:获取 Record ID func (p *cloudflareProvider) getRecordID(ctx context.Context, zoneID, name, recType string) (string, error) { req, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?name=%s&type=%s", zoneID, name, recType), nil) req.Header.Set("Authorization", "Bearer "+p.token) resp, err := p.client.Do(req) if err != nil { return "", err } defer resp.Body.Close() var result struct { Result []struct { ID string `json:"id"` } `json:"result"` } body, _ := io.ReadAll(resp.Body) json.Unmarshal(body, &result) if len(result.Result) > 0 { return result.Result[0].ID, nil } return "", nil }