Files
Meshray-Manager/docs/DDNS 双模式架构实现完成报告.md
2026-06-30 15:14:37 +08:00

486 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<el-form-item label="记录类型" prop="record_type">
<el-select v-model="formData.record_type" placeholder="请选择记录类型">
<el-option label="A (IPv4 地址)" value="A" />
<el-option label="AAAA (IPv6 地址)" value="AAAA" />
<el-option label="TXT (文本记录)" value="TXT" />
</el-select>
</el-form-item>
```
**条件显示字段**:
**A/AAAA 记录时** (新增):
```vue
<template v-if="['A', 'AAAA'].includes(formData.record_type)">
<!-- 主机记录 -->
<el-form-item label="主机记录" prop="subdomain">
<el-input v-model="formData.subdomain" placeholder="@ 或 www" />
</el-form-item>
<!-- 目标 IP -->
<el-form-item label="目标 IP" prop="target_ip">
<el-input v-model="formData.target_ip" placeholder="1.2.3.4" />
</el-form-item>
<!-- 检测端口 -->
<el-form-item label="检测端口" prop="port">
<el-input-number v-model="formData.port" :min="1" :max="65535" />
<span>用于检测 IP 变化</span>
</el-form-item>
</template>
```
**TXT 记录时** (修改):
```vue
<template v-if="formData.record_type === 'TXT'">
<!-- TXT 记录名称 -->
<el-form-item label="TXT 记录名称" prop="txt_record_name">
<el-input
v-model="formData.txt_record_name"
placeholder="_meshray._mesh"
clearable
/>
<div class="form-tip">
<el-icon><InfoFilled /></el-icon>
TXT 记录前缀用于自定义用途
</div>
<div class="form-tip">
<el-icon><InfoFilled /></el-icon>
完整记录{{ formData.txt_record_name }}.{{ formData.domain || 'example.com' }}
</div>
</el-form-item>
<!-- TXT 记录值 -->
<el-form-item label="TXT 记录值" prop="txt_value">
<el-input
v-model="formData.txt_value"
type="textarea"
:rows="3"
placeholder="v=spf1 include:example.com ~all"
clearable
/>
<div class="form-tip">
<el-icon><InfoFilled /></el-icon>
TXT 记录内容可以是验证信息配置等
</div>
</el-form-item>
</template>
```
---
#### 默认值调整
```javascript
const configureDDNS = (provider) => {
formData.value = {
// ...
port: 80, // ✅ 改为 80A 记录用)
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、端口、主机名<br>TXT: 记录名、记录值 | 无需额外字段 |
| **数据表** | `external_services` | `ddns_configs` + `meshseeds` |
| **API** | `POST /api/v1/services` | `POST /api/v1/networks/:id/meshseed` |
| **同步触发** | 定期检测 IP 变化 | MeshSeed 生成/更新时 |
---
## 🎯 用户使用流程
### 场景 1: 配置通用 DDNSIP 解析)
```
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*