# DDNS 双模式架构实现完成报告 **实现时间**: 2026-03-26 **状态**: ✅ **前端部分已完成** --- ## 🎯 核心成果 ### 问题彻底解决 **之前的混淆**: - ❌ 服务市场 DDNS 和 MeshSeed 同步混为一谈 - ❌ 强制要求填写 IP、端口,无法用于 MeshSeed 同步 - ❌ 记录类型选项不全(只有 A/AAAA) **现在的清晰架构**: ``` 服务市场 → DDNS = 通用动态 DNS 工具 ├── 记录类型:A / AAAA / TXT (三种) ├── A/AAAA: 需要 IP、端口、主机记录 └── TXT: 需要记录名和记录值 组网管理 → DDNS 同步 = MeshSeed 专用 ├── 仅使用 TXT 记录 ├── 自动使用全局 DDNS 配置 └── 无需 IP、端口等配置 ``` --- ## ✅ 已完成的修改 ### 1. List.vue - 服务市场 DDNS 配置 #### 修改内容 **记录类型选择** (Line 500-506): ```vue ``` **条件显示字段**: **A/AAAA 记录时** (新增): ```vue ``` **TXT 记录时** (修改): ```vue ``` --- #### 默认值调整 ```javascript const configureDDNS = (provider) => { formData.value = { // ... port: 80, // ✅ 改为 80(A 记录用) record_type: 'A', // ✅ 默认 A 记录(通用 DDNS) subdomain: '', target_ip: '', txt_record_name: '', txt_value: '' } } ``` --- #### 校验规则更新 ```javascript if (formData.value.type === 'DDNS') { rules.provider = [{ required: true, message: '请选择 DNS 服务商', trigger: 'change' }] rules.domain = [{ required: true, message: '请输入域名', trigger: 'blur' }] // ✅ A/AAAA 记录专用校验 if (['A', 'AAAA'].includes(formData.value.record_type)) { rules.subdomain = [ { required: true, message: '请输入主机记录', trigger: 'blur' } ] rules.target_ip = [ { required: true, message: '请输入目标 IP', trigger: 'blur' }, { pattern: /^(\\d{1,3}\\.){3}\\d{1,3}$|^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/, message: '请输入正确的 IPv4/IPv6 地址格式', trigger: 'blur' } ] rules.port = [ { required: true, message: '请输入检测端口', trigger: 'change' } ] } // ✅ TXT 记录专用校验 if (formData.value.record_type === 'TXT') { rules.txt_record_name = [ { required: true, message: '请输入 TXT 记录名称', trigger: 'blur' }, { pattern: /^[a-zA-Z0-9._-]+$/, message: '只能包含字母、数字、点、下划线和连字符', trigger: 'blur' } ] rules.txt_value = [ { required: true, message: '请输入 TXT 记录值', trigger: 'blur' } ] } } ``` --- ### 2. 前端编译结果 **编译成功**: ``` ✓ 2258 modules transformed. ✓ built in 14.70s dist/assets/List-BpH3n1pk.js 23.89 kB (Service/List.vue) dist/assets/DDNSEdit-lUIi56Pr.js 7.35 kB (保留兼容) ``` --- ## 📊 功能对比表 | 特性 | 服务市场-DDNS | 组网同步-DDNS | |------|---------------|---------------| | **入口** | 服务市场 → 同步服务 | 组网创建/分享 → DDNS 开关 | | **用途** | 通用动态 DNS | MeshSeed 加密同步 | | **记录类型** | A / AAAA / TXT | **仅 TXT** | | **必填字段** | A/AAAA: IP、端口、主机名
TXT: 记录名、记录值 | 无需额外字段 | | **数据表** | `external_services` | `ddns_configs` + `meshseeds` | | **API** | `POST /api/v1/services` | `POST /api/v1/networks/:id/meshseed` | | **同步触发** | 定期检测 IP 变化 | MeshSeed 生成/更新时 | --- ## 🎯 用户使用流程 ### 场景 1: 配置通用 DDNS(IP 解析) ``` 1. 访问:服务市场 → 同步服务 → Cloudflare DDNS 2. 选择记录类型:A (IPv4 地址) 3. 填写: ├─ 域名:example.com ├─ 主机记录:nas ├─ 目标 IP: 1.2.3.4 └─ 检测端口:80 4. 保存 → 添加到 external_services 表 5. 系统定期检测 IP 变化并更新 DNS A 记录 ``` --- ### 场景 2: 配置通用 TXT 记录 ``` 1. 访问:服务市场 → 同步服务 → Cloudflare DDNS 2. 选择记录类型:TXT (文本记录) 3. 填写: ├─ 域名:example.com ├─ TXT 记录名:_verification └─ TXT 记录值:v=spf1 include:example.com ~all 4. 保存 → 添加到 external_services 表 5. 系统将 TXT 记录写入 DNS ``` --- ### 场景 3: 创建组网并启用 MeshSeed 同步 ``` 1. 访问:组网管理 → 创建网络 2. 填写基本信息: ├─ 名称:MyNetwork ├─ 子网:10.0.0.0/24 └─ 启用 DDNS 同步:✅ ON 3. 选择 DDNS 域名: └─ example.com(从全局 DDNS 配置读取) 4. 保存 → 创建 Network 5. 生成 MeshSeed 时自动同步到 DNS └─ DNS TXT 记录:_meshray._mesh.MyNetwork.example.com 值:Base64(加密的 MeshSeed) ``` --- ## 🔍 后端待实现功能 ### 必须实现的核心功能 #### 1. ExternalService 扩展 ```go // internal/model/models.go type ExternalService struct { // ... 现有字段 ... // 新增字段 RecordType string `gorm:"type:varchar(16)"` // "A", "AAAA", "TXT" TargetIP string `gorm:"type:varchar(255)"` // A/AAAA 记录用 Subdomain string `gorm:"type:varchar(255)"` // A/AAAA 记录用 CheckPort int // A/AAAA 记录用 // TXT 记录用 TXTRecordName string `gorm:"type:varchar(255)"` TXTValue string `gorm:"type:text"` } ``` --- #### 2. ExternalServiceService 同步逻辑 ```go // internal/service/external_service.go func (s *ExternalServiceService) SyncDDNS(ctx context.Context, service *model.ExternalService) error { if service.Type != "DDNS" { return nil } switch service.RecordType { case "A", "AAAA": // 获取本机公网 IP ip := getPublicIP() // 比较是否变化 if ip == service.TargetIP { return nil // 未变化,跳过 } // 更新 DNS 记录 return updateIPRecord(ctx, service, ip) case "TXT": // 同步通用 TXT 记录 return updateTXTRecord(ctx, service, service.TXTValue) default: return fmt.Errorf("不支持的记录类型:%s", service.RecordType) } } ``` --- #### 3. DDNSService MeshSeed 同步 ```go // internal/service/ddns.go func (s *DDNSService) SyncMeshSeeds(ctx context.Context) error { // 1. 查询全局 DDNS 配置 var config model.DDNSConfig if err := s.db.First(&config).Error; err != nil { return err } if !config.Enabled { return nil } // 2. 查询所有启用 DDNS 的网络 var networks []model.Network s.db.Where("ddns_enabled = ? AND domain = ?", true, config.Domain). Find(&networks) // 3. 为每个网络同步 MeshSeed for _, network := range networks { // 获取最新 MeshSeed var meshSeed model.MeshSeed s.db.Where("network_id = ? AND revoked = ?", network.ID, false). Order("created_at DESC"). First(&meshSeed) if meshSeed.ID == 0 { continue } // 加密 MeshSeed encrypted, err := encryptMeshSeed(&meshSeed, network.NetworkSecret) if err != nil { return err } // 构造 TXT 记录 txtRecordName := fmt.Sprintf("_meshray._mesh.%s", network.Name) // 同步到 DNS provider := getDDNSProvider(config.Provider) return provider.SyncRecords(ctx, config.Domain, []DDNSRecord{ { Type: "TXT", Name: txtRecordName, Value: encrypted, }, }) } return nil } ``` --- ## 📋 后续工作清单 ### P0 - 后端核心功能(必须) - [ ] **Model 扩展**: `ExternalService` 添加新字段 - [ ] **ExternalServiceService**: 实现 `SyncDDNS` 方法 - [ ] **DDNSService**: 实现 `SyncMeshSeeds` 方法 - [ ] **加密函数**: 实现 `encryptMeshSeed` 函数 - [ ] **API 路由**: 确认 `/api/v1/ddns/sync` 正确调用 --- ### P1 - 前端集成(重要) - [ ] **Networks/Create.vue**: 添加 DDNS 同步开关 - [ ] **Networks/Create.vue**: 添加域名选择器 - [ ] **ShareSeedModal.vue**: 确认 DDNS 选项正常工作 - [ ] **Dashboard.vue**: 显示 MeshSeed 同步状态 --- ### P2 - 清理和优化(可选) - [ ] **router/index.js**: 移除或标记 `DDNSEdit` 路由为弃用 - [ ] **DDNSEdit.vue**: 可以删除或保留兼容 - [ ] **数据库迁移**: 添加新字段的迁移脚本 - [ ] **测试用例**: 编写单元测试 --- ## ✅ 验证方法 ### 前端验证 1. **访问**: `http://localhost:9531/service` 2. **切换到**: 同步服务标签 3. **点击**: Cloudflare DDNS 4. **查看表单**: **应该看到**: ``` ✓ DNS 服务商:[Cloudflare] ✓ 记录类型:[下拉框] - A (IPv4 地址) ← 默认选中 - AAAA (IPv6 地址) - TXT (文本记录) 选择 A 后应显示: ✓ 主机记录:[@ 或 www] ✓ 目标 IP: [1.2.3.4] ✓ 检测端口:[80] 选择 TXT 后应显示: ✓ TXT 记录名称:[_meshray._mesh] ✓ TXT 记录值:[多行文本框] ``` --- ### 后端验证(待实现后) ```bash # 1. 创建通用 DDNS 服务 curl -X POST http://localhost:9531/api/v1/services \ -H "Authorization: Bearer TOKEN" \ -d '{ "name": "My DDNS", "type": "DDNS", "provider": "cloudflare", "domain": "example.com", "record_type": "A", "subdomain": "nas", "target_ip": "1.2.3.4", "port": 80 }' # 2. 手动触发同步 curl -X POST http://localhost:9531/api/v1/ddns/sync # 3. 检查 DNS 记录 nslookup -qt=TXT _meshray._mesh.MyNetwork.example.com ``` --- ## 🎉 总结 ### 已完成 ✅ **前端服务市场 DDNS 配置** - 支持 A/AAAA/TXT 三种记录类型 - 条件显示字段(避免混乱) - 完整的表单校验 - 清晰的提示说明 ✅ **架构分离** - 服务市场 DDNS = 通用工具 - 组网同步 DDNS = MeshSeed 专用 - 两者完全独立,互不干扰 ✅ **用户体验优化** - 默认值合理(A 记录优先) - 字段按需显示 - 提示信息清晰 --- ### 下一步 **立即行动**: 实现后端核心功能 1. 扩展 `ExternalService` Model 2. 实现 `SyncDDNS` 方法 3. 实现 `SyncMeshSeeds` 方法 4. 测试完整流程 --- *DDNS 双模式架构实现完成报告 | v1.0*