Initial commit
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/dnsprovider"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/model"
|
||||
"github.com/libdns/libdns"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DDNSOperationService DDNS 操作服务(全功能模式)
|
||||
type DDNSOperationService struct {
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewDDNSOperationService 创建 DDNS 操作服务
|
||||
func NewDDNSOperationService(logger *zap.Logger, db *gorm.DB) *DDNSOperationService {
|
||||
return &DDNSOperationService{
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDNSRecord 创建 DNS 记录(全功能模式)
|
||||
func (s *DDNSOperationService) CreateDNSRecord(config *model.Service, recordType string, name string, value string, ttl int) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 获取关联的 DDNS 配置
|
||||
ddnsConfig, err := s.getDDNSConfig(config.DDNSConfigID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DDNS 配置失败:%w", err)
|
||||
}
|
||||
|
||||
// 2. 创建 DNS Provider
|
||||
providerConfig := dnsprovider.ProviderConfig{
|
||||
Provider: dnsprovider.ProviderType(ddnsConfig.Provider),
|
||||
Domain: ddnsConfig.Domain,
|
||||
APIToken: ddnsConfig.Token,
|
||||
AccessKeyID: ddnsConfig.AuthUsername,
|
||||
AccessKeySecret: ddnsConfig.AuthPassword,
|
||||
SecretId: ddnsConfig.AuthUsername,
|
||||
SecretKey: ddnsConfig.AuthPassword,
|
||||
}
|
||||
|
||||
provider, err := dnsprovider.NewDNSProvider(providerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DNS Provider 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3. 构建 DNS 记录
|
||||
dnsRecord := &dnsprovider.DNSRecord{
|
||||
Type: dnsprovider.RecordType(recordType),
|
||||
Name: name,
|
||||
Value: value,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
// 4. 添加 DNS 记录
|
||||
zone := ddnsConfig.Domain
|
||||
libdnsRecord := dnsRecord.ToLibdnsRecord()
|
||||
|
||||
s.logger.Info("开始创建 DNS 记录",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("value", value),
|
||||
zap.String("domain", zone))
|
||||
|
||||
_, err = provider.AppendRecords(ctx, zone, []libdns.Record{libdnsRecord})
|
||||
if err != nil {
|
||||
return fmt.Errorf("添加 DNS 记录失败:%w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("DNS 记录创建成功",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("domain", zone))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDNSRecord 更新 DNS 记录(全功能模式)
|
||||
func (s *DDNSOperationService) UpdateDNSRecord(config *model.Service, recordType string, name string, value string, ttl int) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 获取关联的 DDNS 配置
|
||||
ddnsConfig, err := s.getDDNSConfig(config.DDNSConfigID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DDNS 配置失败:%w", err)
|
||||
}
|
||||
|
||||
// 2. 创建 DNS Provider
|
||||
providerConfig := dnsprovider.ProviderConfig{
|
||||
Provider: dnsprovider.ProviderType(ddnsConfig.Provider),
|
||||
Domain: ddnsConfig.Domain,
|
||||
APIToken: ddnsConfig.Token,
|
||||
AccessKeyID: ddnsConfig.AuthUsername,
|
||||
AccessKeySecret: ddnsConfig.AuthPassword,
|
||||
SecretId: ddnsConfig.AuthUsername,
|
||||
SecretKey: ddnsConfig.AuthPassword,
|
||||
}
|
||||
|
||||
provider, err := dnsprovider.NewDNSProvider(providerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DNS Provider 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3. 构建新的 DNS 记录
|
||||
newRecord := &dnsprovider.DNSRecord{
|
||||
Type: dnsprovider.RecordType(recordType),
|
||||
Name: name,
|
||||
Value: value,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
// 4. 使用 SetRecords 覆盖现有记录(会自动删除旧记录并创建新记录)
|
||||
zone := ddnsConfig.Domain
|
||||
libdnsRecord := newRecord.ToLibdnsRecord()
|
||||
|
||||
s.logger.Info("开始更新 DNS 记录",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("value", value),
|
||||
zap.String("domain", zone))
|
||||
|
||||
_, err = provider.SetRecords(ctx, zone, []libdns.Record{libdnsRecord})
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新 DNS 记录失败:%w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("DNS 记录更新成功",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("domain", zone))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDNSRecord 删除 DNS 记录(全功能模式)
|
||||
func (s *DDNSOperationService) DeleteDNSRecord(config *model.Service, recordType string, name string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 获取关联的 DDNS 配置
|
||||
ddnsConfig, err := s.getDDNSConfig(config.DDNSConfigID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DDNS 配置失败:%w", err)
|
||||
}
|
||||
|
||||
// 2. 创建 DNS Provider
|
||||
providerConfig := dnsprovider.ProviderConfig{
|
||||
Provider: dnsprovider.ProviderType(ddnsConfig.Provider),
|
||||
Domain: ddnsConfig.Domain,
|
||||
APIToken: ddnsConfig.Token,
|
||||
AccessKeyID: ddnsConfig.AuthUsername,
|
||||
AccessKeySecret: ddnsConfig.AuthPassword,
|
||||
SecretId: ddnsConfig.AuthUsername,
|
||||
SecretKey: ddnsConfig.AuthPassword,
|
||||
}
|
||||
|
||||
provider, err := dnsprovider.NewDNSProvider(providerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DNS Provider 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3. 先获取所有记录
|
||||
records, err := provider.GetRecords(ctx, ddnsConfig.Domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DNS 记录失败:%w", err)
|
||||
}
|
||||
|
||||
// 4. 找到要删除的记录
|
||||
var targetRecord *libdns.Record
|
||||
for _, rec := range records {
|
||||
if rec.Type == recordType && rec.Name == name {
|
||||
targetRecord = &rec
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetRecord == nil {
|
||||
s.logger.Warn("DNS 记录不存在,跳过删除",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 5. 删除记录
|
||||
s.logger.Info("开始删除 DNS 记录",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("domain", ddnsConfig.Domain))
|
||||
|
||||
_, err = provider.DeleteRecords(ctx, ddnsConfig.Domain, []libdns.Record{*targetRecord})
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除 DNS 记录失败:%w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("DNS 记录删除成功",
|
||||
zap.String("type", recordType),
|
||||
zap.String("name", name),
|
||||
zap.String("domain", ddnsConfig.Domain))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDDNSConfig 获取关联的 DDNS 配置
|
||||
func (s *DDNSOperationService) getDDNSConfig(configID string) (*model.Service, error) {
|
||||
// 从 Service 表中查询 ID=configID 且 Type=DDNS 的记录
|
||||
var ddnsService model.Service
|
||||
if err := s.db.Where("id = ? AND type = 'DDNS'", configID).First(&ddnsService).Error; err != nil {
|
||||
return nil, fmt.Errorf("查询 DDNS 配置失败:%w", err)
|
||||
}
|
||||
|
||||
return &ddnsService, nil
|
||||
}
|
||||
|
||||
// SyncMeshSeedToDNS 同步 MeshSeed 到 DNS TXT 记录(带重试机制)
|
||||
func (s *DDNSOperationService) SyncMeshSeedToDNS(networkID uint64, seedString string, ddnsServiceID string) error {
|
||||
const maxRetries = 3
|
||||
var lastErr error
|
||||
|
||||
// 查询 DDNS 配置
|
||||
var ddnsService model.Service
|
||||
if err := s.db.First(&ddnsService, ddnsServiceID).Error; err != nil {
|
||||
return fmt.Errorf("查询 DDNS 服务失败:%w", err)
|
||||
}
|
||||
|
||||
// 查询网络获取前缀
|
||||
var network model.Network
|
||||
if err := s.db.First(&network, networkID).Error; err != nil {
|
||||
return fmt.Errorf("查询网络失败:%w", err)
|
||||
}
|
||||
|
||||
// 重试逻辑(指数退避)
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
lastErr = s.doSyncMeshSeedToDNS(&network, &ddnsService, seedString)
|
||||
if lastErr == nil {
|
||||
// 成功,更新状态
|
||||
s.updateSyncStatus(networkID, "success", "")
|
||||
s.logger.Info("MeshSeed 同步到 DNS 成功",
|
||||
zap.Uint64("network_id", networkID),
|
||||
zap.Int("attempt", attempt))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 失败,记录日志
|
||||
s.logger.Warn("MeshSeed 同步失败",
|
||||
zap.Uint64("network_id", networkID),
|
||||
zap.Int("attempt", attempt),
|
||||
zap.Error(lastErr))
|
||||
|
||||
// 等待后重试(指数退避:1s, 2s, 4s...)
|
||||
if attempt < maxRetries {
|
||||
waitTime := time.Duration(1<<uint(attempt-1)) * time.Second
|
||||
time.Sleep(waitTime)
|
||||
}
|
||||
}
|
||||
|
||||
// 全部失败,更新状态
|
||||
s.updateSyncStatus(networkID, "failed", fmt.Sprintf("重试%d次失败:%v", maxRetries, lastErr))
|
||||
return fmt.Errorf("同步失败:%w", lastErr)
|
||||
}
|
||||
|
||||
// doSyncMeshSeedToDNS 执行实际的同步操作
|
||||
func (s *DDNSOperationService) doSyncMeshSeedToDNS(network *model.Network, ddnsService *model.Service, seedString string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 创建 DNS Provider
|
||||
providerConfig := dnsprovider.ProviderConfig{
|
||||
Provider: dnsprovider.ProviderType(ddnsService.Provider),
|
||||
Domain: ddnsService.Domain,
|
||||
APIToken: ddnsService.Token,
|
||||
AccessKeyID: ddnsService.AuthUsername,
|
||||
AccessKeySecret: ddnsService.AuthPassword,
|
||||
SecretId: ddnsService.AuthUsername,
|
||||
SecretKey: ddnsService.AuthPassword,
|
||||
}
|
||||
|
||||
provider, err := dnsprovider.NewDNSProvider(providerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DNS Provider 失败:%w", err)
|
||||
}
|
||||
|
||||
// 2. 构建 TXT 记录名称
|
||||
txtRecordName := network.DDNSPrefix
|
||||
if txtRecordName == "" {
|
||||
return fmt.Errorf("DDNS 前缀为空")
|
||||
}
|
||||
|
||||
// 3. 加密 MeshSeed(AES-256-GCM)
|
||||
encryptedSeed, err := s.encryptMeshSeed(seedString, network.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("加密 MeshSeed 失败:%w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("开始同步 MeshSeed 到 DNS",
|
||||
zap.String("record_name", txtRecordName),
|
||||
zap.String("domain", ddnsService.Domain))
|
||||
|
||||
// 4. 创建/更新 TXT 记录
|
||||
zone := ddnsService.Domain
|
||||
libdnsRecord := libdns.Record{
|
||||
Type: "TXT",
|
||||
Name: txtRecordName,
|
||||
Value: encryptedSeed,
|
||||
TTL: time.Duration(600) * time.Second,
|
||||
}
|
||||
|
||||
// 先尝试删除旧记录(如果存在)
|
||||
_, err = provider.DeleteRecords(ctx, zone, []libdns.Record{libdnsRecord})
|
||||
if err != nil {
|
||||
s.logger.Debug("删除旧记录失败(可能不存在)",
|
||||
zap.String("name", txtRecordName),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
// 添加新记录
|
||||
_, err = provider.AppendRecords(ctx, zone, []libdns.Record{libdnsRecord})
|
||||
if err != nil {
|
||||
return fmt.Errorf("添加 DNS 记录失败:%w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("MeshSeed 同步到 DNS 成功",
|
||||
zap.String("record_name", txtRecordName),
|
||||
zap.String("domain", ddnsService.Domain))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encryptMeshSeed 加密 MeshSeed(AES-256-GCM)
|
||||
func (s *DDNSOperationService) encryptMeshSeed(plaintext string, networkID uint64) (string, error) {
|
||||
// TODO: 实现 AES-256-GCM 加密
|
||||
// 密钥派生:SHA256("meshray-ddns" + Network.Secret)
|
||||
// 目前先返回明文(P3 阶段实现)
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// updateSyncStatus 更新同步状态
|
||||
func (s *DDNSOperationService) updateSyncStatus(networkID uint64, status, message string) {
|
||||
// 更新 NetworkDDNSBinding 表
|
||||
var binding model.NetworkDDNSBinding
|
||||
if err := s.db.Where("network_id = ?", networkID).First(&binding).Error; err == nil {
|
||||
binding.Status = status
|
||||
now := time.Now()
|
||||
binding.LastSyncAt = &now
|
||||
binding.SyncMessage = message
|
||||
s.db.Save(&binding)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user