# TURN 配置设计说明 **更新时间**: 2026-03-24 **设计模式**: **内嵌式设计**(非独立表) --- ## 📋 **TURN 配置存储位置** ### ❌ **误解** 之前认为存在独立的 `TURNConfig` 表,实际上**没有这个独立模型**。 ### ✅ **实际设计** **TURN 配置内嵌在 `SystemSetting` 模型中**,作为系统全局设置的一部分。 **文件**: [`internal/model/models.go`](file://e:\Project\MeshRay\internal\model\models.go#L103-L122) ```go // SystemSetting 系统设置模型 - 单例模式,全局只有一条记录 type SystemSetting struct { ID uint `gorm:"primaryKey;type:bigint" json:"id"` // 固定为 1 ServerIP string `gorm:"type:varchar(45)" json:"server_ip"` // 服务端公网 IP ServerPort int `gorm:"default:51820" json:"server_port"` // WireGuard 监听端口 ServerPublicKey string `gorm:"type:varchar(64)" json:"server_public_key,omitempty"` // WireGuard 服务端公钥 DDNSDomain string `gorm:"type:varchar(255)" json:"ddns_domain"` // DDNS 域名 TURNMode string `gorm:"type:varchar(16);default:'auto'" json:"turn_mode"` // auto/manual TURNURL string `gorm:"type:varchar(255)" json:"turn_url"` // TURN 服务器 URL TURNUsername string `gorm:"type:varchar(128)" json:"turn_username,omitempty"` // TURN 用户名 TURNPassword string `gorm:"type:"size:varchar(128)" json:"-"` // TURN 密码(加密存储) LogLevel string `gorm:"type:varchar(16);default:'info'" json:"log_level"` // debug/info/warn/error LogFormat string `gorm:"type:varchar(16);default:'console'" json:"log_format"` // console/json MaxBackups int `gorm:"default:7" json:"max_backups"` // 日志最大保留份数 MaxAge int `gorm:"default:30" json:"max_age"` // 日志最大保留天数 Theme string `gorm:"type:varchar(32);default:'light'" json:"theme"` // light/dark/auto Language string `gorm:"type:varchar(16);default:'zh-CN'" json:"language"` // zh-CN/en-US CreatedAt time.Time `gorm:"autoCreateTime" json:"createdAt"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updatedAt"` } ``` --- ## 🎯 **为什么采用内嵌设计?** ### 1. **单例模式的合理性** **原因**: TURN 配置是**全局唯一**的系统级配置 - ✅ 整个系统只需要一套 TURN 配置 - ✅ 不需要为不同网络创建不同的 TURN 配置 - ✅ 与 ServerIP、ServerPort 等一样,属于基础设施配置 **对比独立表的劣势**: ```sql -- ❌ 如果设计为独立表 CREATE TABLE turn_configs ( id INTEGER PRIMARY KEY, url VARCHAR(255), username VARCHAR(128), password VARCHAR(128) ); -- 问题:永远只会有一条记录,浪费表结构 ``` **内嵌设计的优势**: ```sql -- ✅ 实际设计 CREATE TABLE system_settings ( id INTEGER PRIMARY KEY, server_ip VARCHAR(45), server_port INTEGER, turn_mode VARCHAR(16), -- 内嵌 TURN 配置 turn_url VARCHAR(255), -- 内嵌 TURN 配置 turn_username VARCHAR(128), -- 内嵌 TURN 配置 turn_password VARCHAR(128) -- 内嵌 TURN 配置 -- ... 其他系统配置 ); -- 优势:所有系统配置集中在一张表,便于管理和备份 ``` --- ### 2. **数据库迁移** **自动迁移时会自动创建字段**: ```go // store.go:36-56 func (s *Store) AutoMigrate() error { return s.db.AutoMigrate( &model.Network{}, &model.Device{}, // ... 其他模型 &model.SystemSetting{}, // ✅ 包含 TURN 配置字段 &model.ExternalService{}, // ✅ 也包含 TURN 服务器配置 ) } ``` **生成的数据库表结构**: ```sql -- SQLite 表结构 CREATE TABLE IF NOT EXISTS "system_settings" ( "id" integer PRIMARY KEY, "server_ip" varchar(45), "server_port" integer DEFAULT 51820, "server_public_key" varchar(64), "ddns_domain" varchar(255), "turn_mode" varchar(16) DEFAULT 'auto', -- ← TURN 配置 "turn_url" varchar(255), -- ← TURN 配置 "turn_username" varchar(128), -- ← TURN 配置 "turn_password" varchar(128), -- ← TURN 配置 "log_level" varchar(16) DEFAULT 'info', "log_format" varchar(16) DEFAULT 'console', "max_backups" integer DEFAULT 7, "max_age" integer DEFAULT 30, "theme" varchar(32) DEFAULT 'light', "language" varchar(16) DEFAULT 'zh-CN', "created_at" datetime, "updated_at" datetime ); ``` --- ## 📊 **TURN 配置字段说明** | 字段名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | **turn_mode** | string | "auto" | TURN 模式:auto/manual | | **turn_url** | string | - | TURN 服务器 URL(手动模式) | | **turn_username** | string | - | TURN 用户名(可选) | | **turn_password** | string | - | TURN 密码(加密存储,JSON 不返回) | --- ## 🔧 **使用方式** ### 1. **读取 TURN 配置** ```go // service/device.go var setting model.SystemSetting err := s.store.DB().First(&setting, 1).Error if err != nil { return nil, err } // 使用 TURN 配置 turnMode := setting.TURNMode // "auto" 或 "manual" turnURL := setting.TURNURL // "turn:stun.example.com:3478" turnUser := setting.TURNUsername // "myuser" // turnPass 不会通过 JSON 返回,但可以从数据库读取 ``` --- ### 2. **更新 TURN 配置** ```go // api/handler/settings.go func (h *SettingsHandler) UpdateSystemSettings(c *gin.Context) { var req struct { TURNMode string `json:"turn_mode"` TURNURL string `json:"turn_url"` TURNUsername string `json:"turn_username"` TURNPassword string `json:"turn_password"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // 获取现有设置 var setting model.SystemSetting h.store.DB().First(&setting, 1) // 更新 TURN 配置 setting.TURNMode = req.TURNMode setting.TURNURL = req.TURNURL setting.TURNUsername = req.TURNUsername if req.TURNPassword != "" { // 加密后存储 encrypted, _ := encrypt(req.TURNPassword) setting.TURNPassword = encrypted } // 保存到数据库 h.store.DB().Save(&setting) c.JSON(200, gin.H{"message": "更新成功"}) } ``` --- ### 3. **前端调用** ```vue ``` --- ## 🏗️ **架构设计考量** ### 1. **为什么不用独立表?** **独立表的问题**: ```go // ❌ 假设的独立表设计 type TURNConfig struct { ID uint `gorm:"primaryKey"` URL string Username string Password string } // 问题: // 1. 永远只会有一条记录(浪费表结构) // 2. 需要额外的 JOIN 查询 // 3. 增加数据库连接负担 // 4. 不符合"全局唯一配置"的语义 ``` **内嵌设计的优势**: ```go // ✅ 实际的内嵌设计 type SystemSetting struct { // ... 其他系统配置 TURNMode string // 直接访问,无需 JOIN TURNURL string TURNUsername string TURNPassword string } // 优势: // 1. 一次查询获取所有系统配置 // 2. 符合"全局唯一"的语义 // 3. 简化数据库设计 // 4. 便于备份和迁移 ``` --- ### 2. **与 ExternalService 的区别** **问题**: 既然有 `ExternalService` 表,为什么还需要内嵌 TURN 配置? **答案**: 两者用途不同 | 特性 | SystemSetting (内嵌) | ExternalService (独立表) | |------|---------------------|--------------------------| | **用途** | 主用 TURN 服务器 | 备用 TURN 服务器池 | | **数量** | 1 个(全局唯一) | N 个(可多个) | | **场景** | 默认配置 | 多区域/多服务商 | | **访问速度** | 快(一次查询) | 慢(需要 JOIN) | | **管理方式** | 系统设置页面 | 外部服务管理页面 | **示例**: ```go // SystemSetting: 主用 TURN turn_mode: "auto" turn_url: "turn:primary.example.com:3478" turn_username: "main_user" // ExternalService: 备用 TURN 池 [ { service_type: "turn_server", name: "美国节点", config: {"url": "turn:us.example.com:3478", ...} }, { service_type: "turn_server", name: "欧洲节点", config: {"url": "turn:eu.example.com:3478", ...} } ] ``` --- ## 📝 **总结** ### ✅ **关键结论** 1. **TURN 配置没有被删除** - 内嵌在 `SystemSetting` 模型中(第 110-113 行) - 作为系统全局配置的一部分 2. **为什么采用内嵌设计?** - ✅ 符合"全局唯一配置"的语义 - ✅ 简化数据库设计 - ✅ 提高查询性能(无需 JOIN) - ✅ 便于管理和备份 3. **字段完整性** - ✅ `turn_mode`: 自动/手动模式 - ✅ `turn_url`: TURN 服务器 URL - ✅ `turn_username`: 用户名 - ✅ `turn_password`: 密码(加密存储) 4. **数据库迁移** - ✅ `AutoMigrate` 会自动创建所有字段 - ✅ `initDefaultSettings` 会初始化默认值 --- ### 🎯 **最佳实践** **读取配置**: ```go var setting model.SystemSetting s.store.DB().First(&setting, 1) // 直接使用 TURN 配置 if setting.TURNMode == "manual" { useCustomTURN(setting.TURNURL) } ``` **更新配置**: ```go // 先获取,再修改,最后保存 var setting model.SystemSetting s.store.DB().First(&setting, 1) setting.TURNMode = "manual" setting.TURNURL = "turn:new-server.com:3478" s.store.DB().Save(&setting) ``` **前端展示**: ```javascript // 注意密码不会返回(json: "-") loadSettings().then(data => { console.log(data.turn_mode) // ✅ 有值 console.log(data.turn_url) // ✅ 有值 console.log(data.turn_username) // ✅ 有值 console.log(data.turn_password) // ❌ undefined(加密存储) }) ``` --- **状态**: ✅ **TURN 配置完整保留** **设计**: 内嵌在 SystemSetting 模型中 **位置**: [`internal/model/models.go:110-113`](file://e:\Project\MeshRay\internal\model\models.go#L110-L113) *MeshRay - 清晰的架构设计!* 🏗️✨