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
@@ -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, // ✅ 改为 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*