Initial commit

This commit is contained in:
2026-06-30 15:14:37 +08:00
commit 15dab96872
311 changed files with 95639 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
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
}