TXT 记录名:_meshray._mesh.{{ formData.name }}.{{ formData.ddns_domain }}
```
---
### 修改 3: 后端逻辑分离
#### A. ExternalService 处理(服务市场)
```go
// internal/service/external_service.go
type ExternalService struct {
ID uint `gorm:"primaryKey"`
Name string
Type string // "DDNS", "STUN", "TURN"
Provider string // "aliyun", "tencent", "cloudflare"
Domain string
RecordType string // "A", "AAAA", "TXT"
// A/AAAA 记录用
TargetIP string
Subdomain string
CheckPort int
// TXT 记录用(通用 DDNS)
TXTName string
TXTValue string
// 认证信息
AccessKey string
SecretKey string
}
// SyncExternalDDNS 同步外部 DDNS 服务
func (s *ExternalServiceService) SyncExternalDDNS(ctx context.Context, service *model.ExternalService) error {
if service.Type != "DDNS" {
return nil
}
switch service.RecordType {
case "A", "AAAA":
// 获取本机公网 IP
ip := getPublicIP()
// 更新 DNS A/AAAA 记录
return updateIPRecord(ctx, service, ip)
case "TXT":
// 通用 TXT 记录同步(非 MeshSeed)
return updateTXTRecord(ctx, service, service.TXTValue)
default:
return fmt.Errorf("不支持的记录类型:%s", service.RecordType)
}
}
```
---
#### B. DDNSService 处理(MeshSeed 同步)
```go
// internal/service/ddns.go
type DDNSService struct {
db *gorm.DB
logger *zap.Logger
}
// SyncMeshSeeds 同步所有网络的 MeshSeed 到 TXT 记录
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,跳过
}
// 加密 MeshSeed
encrypted, err := encryptMeshSeed(&meshSeed, network.NetworkSecret)
if err != nil {
return err
}
// 构造 TXT 记录名
txtRecordName := fmt.Sprintf("_meshray._mesh.%s.%s",
network.Name, config.Domain)
// 同步到 DNS
provider := getDDNSProvider(config.Provider)
err = provider.SyncRecords(ctx, config.Domain, []DDNSRecord{
{
Type: "TXT",
Name: txtRecordName,
Value: encrypted,
},
})
if err != nil {
return err
}
}
return nil
}
```
---
## 📋 前端路由调整
### 移除独立编辑页面
```javascript
// web/src/router/index.js - 移除或标记弃用
{
path: 'ddns/edit',
name: 'DDNSEdit',
component: () => import('@/views/Service/DDNSEdit.vue'),
meta: { deprecated: true } // 标记为弃用
}
```
**检查调用点**:
```bash
# 搜索所有引用
grep -r "DDNSEdit" web/src/
grep -r "/ddns/edit" web/src/
```
**预期结果**:
- ✅ List.vue 中的 `configureDDNS` 直接处理
- ✅ 不再有跳转到独立编辑页
---
## 🎯 完整用户流程
### 场景 1: 配置通用 DDNS(服务市场)
```
1. 访问:服务市场 → 同步服务
2. 点击:Cloudflare DDNS
3. 填写表单:
├─ DNS 服务商:Cloudflare
├─ 记录类型:A (IPv4 地址)
├─ 域名:example.com
├─ 主机记录:nas
├─ 目标 IP: 1.2.3.4
└─ 检测端口:80
4. 保存 → 添加到 external_services 表
5. 系统定期检测 IP 变化并更新 DNS
```
---
### 场景 2: 创建组网并启用 MeshSeed 同步
```
1. 访问:组网管理 → 创建网络
2. 填写基本信息:
├─ 名称:MyNetwork
├─ 子网:10.0.0.0/24
└─ 启用 DDNS 同步:✅ ON
3. 选择 DDNS 域名:
└─ example.com(从已配置的全局 DDNS 读取)
4. 保存 → 创建 Network
5. 生成 MeshSeed 时:
├─ POST /api/v1/networks/:id/meshseed
├─ ddns_enabled: true
└─ 自动触发同步到 DNS
6. DNS TXT 记录生成:
└─ _meshray._mesh.MyNetwork.example.com
值:Base64(加密的 MeshSeed)
```
---
### 场景 3: 分享组网(带 MeshSeed)
```
1. 访问:组网详情 → 分享
2. 配置分享参数:
├─ 有效期:7 天
├─ 最大使用次数:10
└─ DDNS 同步:✅ ON
3. 生成 MeshSeed URL:
└─ meshray://eyJhbGci... (加密 Token)
4. 同时自动同步到 DNS TXT 记录
5. 新成员加入:
├─ 方式 1: 扫描 QR Code
└─ 方式 2: DNS 查询 TXT 记录获取 MeshSeed
```
---
## 🔍 数据库设计
### external_services 表(服务市场)
```sql
CREATE TABLE external_services (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL, -- 服务名称
type TEXT NOT NULL, -- "DDNS", "STUN", "TURN"
provider TEXT, -- "aliyun", "tencent", "cloudflare"
-- 通用字段
domain TEXT, -- 域名
record_type TEXT, -- "A", "AAAA", "TXT"
-- A/AAAA 记录专用
target_ip TEXT, -- 目标 IP
subdomain TEXT, -- 子域名
check_port INTEGER, -- 检测端口
-- TXT 记录专用
txt_name TEXT, -- TXT 记录名
txt_value TEXT, -- TXT 记录值
-- 认证信息
access_key TEXT, -- AccessKey (加密)
secret_key TEXT, -- SecretKey (加密)
enabled BOOLEAN DEFAULT TRUE,
created_at DATETIME,
updated_at DATETIME
);
```
---
### ddns_configs 表(全局配置)
```sql
CREATE TABLE ddns_configs (
id INTEGER PRIMARY KEY,
provider TEXT NOT NULL, -- "aliyun", "tencent", "cloudflare"
access_key TEXT, -- AccessKey (加密)
secret_key TEXT, -- SecretKey (加密)
domain TEXT NOT NULL, -- 主域名
txt_record_name TEXT, -- TXT 记录前缀(默认_meshray._mesh)
sync_mode TEXT, -- "auto" | "manual"
retry_interval INTEGER, -- 重试间隔(秒)
max_retries INTEGER, -- 最大重试次数
enabled BOOLEAN DEFAULT TRUE,
last_sync_at DATETIME,
status TEXT, -- "reachable" | "unreachable"
created_at DATETIME,
updated_at DATETIME
);
```
---
### networks 表(组网)
```sql
CREATE TABLE networks (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
network_secret TEXT NOT NULL, -- 网络密钥(用于派生加密密钥)
subnet TEXT NOT NULL,
ddns_enabled BOOLEAN DEFAULT FALSE, -- 是否启用 MeshSeed 同步
ddns_domain TEXT, -- DDNS 域名(引用 ddns_configs.domain)
created_at DATETIME,
updated_at DATETIME
);
```
---
### meshseeds 表(MeshSeed)
```sql
CREATE TABLE meshseeds (
id INTEGER PRIMARY KEY,
seed_id TEXT NOT NULL, -- 随机 Seed ID
network_id INTEGER NOT NULL, -- 关联网络
join_token TEXT NOT NULL, -- Base64 Token
signature TEXT NOT NULL, -- Ed25519 签名
ddns_enabled BOOLEAN DEFAULT FALSE, -- 是否同步到 DNS
ddns_domain TEXT, -- 同步到的域名
expires_at DATETIME,
revoked BOOLEAN DEFAULT FALSE,
created_at DATETIME,
updated_at DATETIME,
FOREIGN KEY (network_id) REFERENCES networks(id)
);
```
---
## ✅ 修改清单
### 前端修改
1. ✅ **List.vue** - 服务市场 DDNS 配置
- 添加 TXT 记录选项
- 条件显示字段(A/AAAA vs TXT)
- 修改 `configureDDNS` 函数逻辑
2. ✅ **Networks/Create.vue** - 创建组网
- 添加 DDNS 同步开关
- 添加域名选择器
3. ✅ **Networks/List.vue** - 分享组网
- DDNS 同步选项保留
- 说明文字更新
4. ✅ **router/index.js** - 路由
- 标记 DDNSEdit 为弃用
- 或直接移除
5. ❌ **DDNSEdit.vue** - 独立编辑页
- 不再使用
- 可以删除或保留兼容
---
### 后端修改
1. ✅ **ExternalService Model** - 扩展字段
- 添加 `record_type`, `txt_name`, `txt_value`
2. ✅ **ExternalServiceService** - 新增方法
- `SyncExternalDDNS()` - 同步外部 DDNS
3. ✅ **DDNSService** - 重写逻辑
- `SyncMeshSeeds()` - 同步 MeshSeed 到 TXT
- 与 IP 同步完全分离
4. ✅ **Network Model** - 确认字段
- `ddns_enabled`
- `ddns_domain`
- `network_secret`
---
## 🎯 下一步行动
**优先级排序**:
1. **P0 - 后端分离逻辑** (最关键)
- 修改 `DDNSService.SyncMeshSeeds()`
- 确保只处理 TXT 记录和 MeshSeed
2. **P1 - 前端服务市场改造**
- List.vue 添加 TXT 选项
- 条件显示字段
3. **P2 - 组网创建集成**
- Create.vue 添加 DDNS 开关
- 域名选择器
4. **P3 - 清理弃用代码**
- 移除 DDNSEdit 路由
- 删除或归档 DDNSEdit.vue
---
*DDNS 双模式架构修复方案 | v1.0*