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
+357
View File
@@ -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. 加密 MeshSeedAES-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 加密 MeshSeedAES-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)
}
}