Initial commit
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
# 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, // ✅ 改为 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、端口、主机名<br>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*
|
||||
Reference in New Issue
Block a user