Initial commit
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
# 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
|
||||
<!-- Settings.vue -->
|
||||
<template>
|
||||
<el-form :model="settings">
|
||||
<el-form-item label="TURN 模式">
|
||||
<el-radio-group v-model="settings.turn_mode">
|
||||
<el-radio label="auto">自动</el-radio>
|
||||
<el-radio label="manual">手动</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="TURN 服务器" v-if="settings.turn_mode === 'manual'">
|
||||
<el-input v-model="settings.turn_url" placeholder="turn:example.com:3478" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="settings.turn_username" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.turn_password" type="password" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const loadSettings = async () => {
|
||||
const res = await request.get('/settings/system')
|
||||
settings.value = res.data
|
||||
|
||||
// 注意:密码不会返回(json:"-")
|
||||
// form.turn_password 为空,需要用户手动输入
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ **架构设计考量**
|
||||
|
||||
### 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 - 清晰的架构设计!* 🏗️✨
|
||||
Reference in New Issue
Block a user