# DDNS Usage 前缀定义策略 **设计时间**: 2026-03-26 **核心问题**: 如何定义和管理 TXT 记录前缀 --- ## 🎯 问题背景 ### 当前需求 ``` 同一个 DDNS 配置(如 example.com)需要支持多个组网同步: ├─ 组网 A → _meshray._mesh.network-a.example.com ├─ 组网 B → _meshray._mesh.network-b.example.com └─ 组网 C → _custom.prefix.network-c.example.com 问题:前缀 (_meshray._mesh) 如何定义?谁来决定? ``` --- ## ✅ 三种设计方案 ### 方案一:系统预设固定前缀(推荐)⭐ **设计思路**: ``` 系统内置标准前缀,用户不可自定义 ├─ MeshSeed 同步专用:_meshray._mesh ├─ 未来扩展 1: _meshray.config (配置同步) └─ 未来扩展 2: _meshray.device (设备注册) ``` **优点**: - ✅ 标准化,避免混乱 - ✅ 安全性高(防止恶意前缀) - ✅ 实现简单 **缺点**: - ❌ 灵活性较低 - ❌ 无法适配特殊场景 **数据库设计**: ```go // DDNSUsage 模型 - 前缀字段枚举化 type DDNSUsage struct { ID string `gorm:"primaryKey;type:varchar(36)"` ServiceID string `gorm:"type:varchar(36);index"` // ✅ 方案 A:预设前缀类型(枚举) PrefixType string `gorm:"type:varchar(32);not null"` /* 可选值: - "MESHSEED_SYNC" → 对应 "_meshray._mesh" - "CONFIG_SYNC" → 对应 "_meshray.config" - "DEVICE_REG" → 对应 "_meshray.device" */ RecordPrefix string `gorm:"-"` // 计算字段,不存储 FullDomain string // 自动生成 NetworkID *uint64 `gorm:"type:bigint;index"` Enabled bool `gorm:"default:true"` } // 方法:获取实际前缀 func (u *DDNSUsage) GetRecordPrefix() string { switch u.PrefixType { case "MESHSEED_SYNC": return "_meshray._mesh" case "CONFIG_SYNC": return "_meshray.config" case "DEVICE_REG": return "_meshray.device" default: panic("未知的前缀类型:" + u.PrefixType) } } ``` **前端实现**: ```vue
当前仅支持 MeshSeed 同步,其他功能开发中
``` --- ### 方案二:管理员自定义前缀(灵活)🔧 **设计思路**: ``` 管理员创建 Usage 时自由填写前缀 ├─ 示例 1: _meshray._mesh ├─ 示例 2: _custom.prefix └─ 示例 3: anything.you.want ``` **优点**: - ✅ 灵活性极高 - ✅ 适配各种场景 **缺点**: - ❌ 容易冲突(需要验证唯一性) - ❌ 安全性风险(可能输入恶意前缀) - ❌ 用户学习成本高 **数据库设计**: ```go type DDNSUsage struct { ID string `gorm:"primaryKey;type:varchar(36)"` ServiceID string `gorm:"type:varchar(36);index"` // ✅ 方案 B:完全自定义前缀 RecordPrefix string `gorm:"type:varchar(255);not null"` /* 示例: - "_meshray._mesh" - "_custom.test" - "anything" */ // 格式验证 validator func(string) error NetworkID *uint64 `gorm:"type:bigint;index"` Enabled bool `gorm:"default:true"` } // 验证函数 func validateRecordPrefix(prefix string) error { if prefix == "" { return fmt.Errorf("前缀不能为空") } // DNS 标签规则 if len(prefix) > 253 { return fmt.Errorf("前缀过长(最大 253 字符)") } // 只能包含字母、数字、连字符、点 matched, _ := regexp.MatchString(`^[a-zA-Z0-9._-]+$`, prefix) if !matched { return fmt.Errorf("前缀只能包含字母、数字、点、下划线和连字符") } // 不能以特殊字符开头 if strings.HasPrefix(prefix, "_") && !strings.HasPrefix(prefix, "_meshray") { return fmt.Errorf("下划线前缀仅限系统使用") } return nil } ``` **前端实现**: ```vue
完整记录名:{{ formData.record_prefix }}.网络名称。域名
只能包含字母、数字、点、下划线和连字符
``` --- ### 方案三:混合模式(最佳实践)🏆 **设计思路**: ``` 系统预设 + 管理员自定义组合 ├─ 预设类型:快速选择,安全可靠 └─ 自定义:高级选项,满足特殊需求 ``` **数据库设计**: ```go type DDNSUsage struct { ID string `gorm:"primaryKey;type:varchar(36)"` ServiceID string `gorm:"type:varchar(36);index"` // ✅ 方案 C:混合模式 PrefixMode string `gorm:"type:varchar(16);not null;default:'preset'"` /* - "preset" → 使用预设类型 - "custom" → 使用自定义前缀 */ PrefixType string `gorm:"type:varchar(32)"` // preset 模式下使用 RecordPrefix string `gorm:"type:varchar(255)"` // custom 模式下使用 NetworkID *uint64 `gorm:"type:bigint;index"` Enabled bool `gorm:"default:true"` } // 方法:获取实际前缀 func (u *DDNSUsage) GetRecordPrefix() string { if u.PrefixMode == "preset" { return u.GetPresetPrefix() } else { return u.RecordPrefix } } func (u *DDNSUsage) GetPresetPrefix() string { switch u.PrefixType { case "MESHSEED_SYNC": return "_meshray._mesh" case "CONFIG_SYNC": return "_meshray.config" default: return "_meshray._mesh" // 默认回退 } } ``` **前端实现**: ```vue 系统预设 自定义
需符合 DNS 命名规范
``` --- ## 🎯 推荐方案:混合模式 ### 理由 1. **兼顾安全与灵活** ✅ - 默认使用预设,避免错误 - 允许高级用户自定义 2. **渐进式扩展** ✅ - 初期只有 MeshSeed 同步 - 后续可增加其他用途 3. **用户体验好** ✅ - 普通用户选预设即可 - 专家用户可以深度定制 --- ## 🔧 完整实现(混合模式) ### 后端验证逻辑 ```go // internal/service/ddns_usage.go // CreateUsage 创建 DDNS 使用方式 func (s *DDNSService) CreateUsage(ctx context.Context, req CreateUsageRequest) (*model.DDNSUsage, error) { // 1. 验证 DDNS 服务存在 var service model.ExternalService if err := s.db.First(&service, req.ServiceID).Error; err != nil { return nil, fmt.Errorf("DDNS 服务不存在") } // 2. 根据模式验证前缀 var recordPrefix string if req.PrefixMode == "preset" { // 预设模式:验证类型合法性 switch req.PrefixType { case "MESHSEED_SYNC": recordPrefix = "_meshray._mesh" default: return nil, fmt.Errorf("未知的预设类型") } } else if req.PrefixMode == "custom" { // 自定义模式:严格验证格式 if err := validateCustomPrefix(req.RecordPrefix); err != nil { return nil, err } recordPrefix = req.RecordPrefix } else { return nil, fmt.Errorf("无效的前缀模式") } // 3. 解析域名 var config map[string]interface{} json.Unmarshal([]byte(service.Config), &config) domain, _ := config["domain"].(string) if domain == "" { return nil, fmt.Errorf("DDNS 配置缺少域名") } // 4. 创建 Usage usage := &model.DDNSUsage{ ID: generateUUID(), ServiceID: req.ServiceID, PrefixMode: req.PrefixMode, PrefixType: req.PrefixType, RecordPrefix: recordPrefix, FullDomain: fmt.Sprintf("%s.%s", recordPrefix, domain), Enabled: true, } // 5. 检查是否重复(同一域名 + 前缀组合) var count int64 s.db.Model(&model.DDNSUsage{}). Where("service_id = ? AND record_prefix = ? AND network_id IS NOT NULL", req.ServiceID, recordPrefix). Count(&count) if count > 0 { return nil, fmt.Errorf("该前缀已被其他组网占用") } // 6. 保存 if err := s.db.Create(usage).Error; err != nil { return nil, fmt.Errorf("创建失败:%w", err) } return usage, nil } // 自定义前缀验证 func validateCustomPrefix(prefix string) error { if prefix == "" { return fmt.Errorf("前缀不能为空") } if len(prefix) > 253 { return fmt.Errorf("前缀过长") } // DNS 标签规范 if !regexp.MustCompile(`^[a-zA-Z0-9._-]+$`).MatchString(prefix) { return fmt.Errorf("前缀只能包含字母、数字、点、下划线和连字符") } // 保留前缀检查 if strings.HasPrefix(prefix, "_meshray.") && prefix != "_meshray._mesh" { return fmt.Errorf("_meshray.* 前缀为系统保留") } return nil } ``` --- ### 前端完整表单 ```vue ``` --- ## 📊 实际应用示例 ### 场景 1: 标准企业用户(使用预设) ``` 公司 IT 管理员配置: 1. 添加 DDNS 服务 └─ Cloudflare + mesh.company.com 2. 创建 Usage(预设模式) └─ 类型:MeshSeed 同步 (_meshray._mesh) 3. 创建组网 A └─ 选择 Usage → _meshray._mesh.office.mesh.company.com 4. 创建组网 B └─ 选择 Usage → _meshray._mesh.dev.mesh.company.com 结果: ✅ 自动分配不同子域名 ✅ 不会冲突 ✅ 管理简单 ``` --- ### 场景 2: 高级用户(自定义前缀) ``` 技术专家配置: 1. 添加 DDNS 服务 └─ Cloudflare + example.com 2. 创建 Usage(自定义模式) └─ 前缀:prod.meshray.sync 3. 创建生产环境组网 └─ 选择 Usage → prod.meshray.sync.prod-net.example.com 4. 创建第二个 Usage └─ 前缀:test.meshray.sync 5. 创建测试环境组网 └─ 选择 Usage → test.meshray.sync.test-net.example.com 结果: ✅ 环境隔离清晰 ✅ 命名规范自主定义 ✅ 灵活性极高 ``` --- ## ✅ 最终建议 **采用混合模式**: 1. **默认引导用户使用预设** (90% 场景) - MeshSeed 同步专用前缀:`_meshray._mesh` - 安全、标准、无需思考 2. **提供自定义入口** (10% 高级场景) - 严格验证格式 - 保留系统前缀 - 防止冲突 3. **未来扩展预留** - `_meshray.config` - 配置同步 - `_meshray.device` - 设备注册 - `_meshray.log` - 日志投递 这样既保证了大多数用户的易用性,又满足了高级用户的灵活性需求!🎯 需要我立即开始实现吗?