mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
feat(rustdesk): 完整实现RustDesk协议和P2P连接
重大变更: - 从prost切换到protobuf 3.4实现完整的RustDesk协议栈 - 新增P2P打洞模块(punch.rs)支持直连和中继回退 - 重构加密系统:临时Curve25519密钥对+Ed25519签名 - 完善HID适配器:支持CapsLock状态同步和修饰键映射 - 添加音频流支持:Opus编码+音频帧适配器 - 优化视频流:改进帧适配器和编码器协商 - 移除pacer.rs简化视频管道 扩展系统: - 在设置向导中添加扩展步骤(ttyd/rustdesk切换) - 扩展可用性检测和自动启动 - 新增WebConfig handler用于Web服务器配置 前端改进: - SetupView增加第4步扩展配置 - 音频设备列表和配置界面 - 新增多语言支持(en-US/zh-CN) - TypeScript类型生成更新 文档: - 更新系统架构文档 - 完善config/hid/rustdesk/video/webrtc模块文档
This commit is contained in:
@@ -2,22 +2,24 @@
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Config 模块提供配置管理功能,所有配置存储在 SQLite 数据库中。
|
||||
Config 模块提供配置管理功能,所有配置存储在 SQLite 数据库中,使用 ArcSwap 实现无锁读取,提供高性能配置访问。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- SQLite 配置存储
|
||||
- SQLite 配置存储(持久化)
|
||||
- 无锁配置读取(ArcSwap)
|
||||
- 类型安全的配置结构
|
||||
- 热重载支持
|
||||
- TypeScript 类型生成
|
||||
- 配置变更通知(broadcast channel)
|
||||
- TypeScript 类型生成(typeshare)
|
||||
- RESTful API(按功能域分离)
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/config/
|
||||
├── mod.rs # 模块导出
|
||||
├── schema.rs # 配置结构定义 (12KB)
|
||||
└── store.rs # SQLite 存储 (8KB)
|
||||
├── schema.rs # 配置结构定义(包含所有子配置)
|
||||
└── store.rs # SQLite 存储与无锁缓存
|
||||
```
|
||||
|
||||
---
|
||||
@@ -26,110 +28,292 @@ src/config/
|
||||
|
||||
### 2.1 ConfigStore (store.rs)
|
||||
|
||||
配置存储使用 **ArcSwap** 实现无锁读取,提供接近零成本的配置访问性能:
|
||||
|
||||
```rust
|
||||
pub struct ConfigStore {
|
||||
db: Pool<Sqlite>,
|
||||
pool: Pool<Sqlite>,
|
||||
/// 无锁缓存,使用 ArcSwap 实现零成本读取
|
||||
cache: Arc<ArcSwap<AppConfig>>,
|
||||
/// 配置变更通知通道
|
||||
change_tx: broadcast::Sender<ConfigChange>,
|
||||
}
|
||||
|
||||
impl ConfigStore {
|
||||
/// 创建存储
|
||||
pub async fn new(db_path: &Path) -> Result<Self>;
|
||||
|
||||
/// 获取完整配置
|
||||
pub async fn get_config(&self) -> Result<AppConfig>;
|
||||
/// 获取当前配置(无锁,零拷贝)
|
||||
///
|
||||
/// 返回 Arc<AppConfig>,高效共享无需克隆
|
||||
/// 这是一个无锁操作,开销极小
|
||||
pub fn get(&self) -> Arc<AppConfig>;
|
||||
|
||||
/// 更新配置
|
||||
pub async fn update_config(&self, config: &AppConfig) -> Result<()>;
|
||||
/// 设置完整配置
|
||||
pub async fn set(&self, config: AppConfig) -> Result<()>;
|
||||
|
||||
/// 获取单个配置项
|
||||
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>>;
|
||||
/// 使用闭包更新配置
|
||||
///
|
||||
/// 读-修改-写模式。并发更新时,最后的写入获胜。
|
||||
/// 对于不频繁的用户触发配置更改来说是可接受的。
|
||||
pub async fn update<F>(&self, f: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce(&mut AppConfig);
|
||||
|
||||
/// 设置单个配置项
|
||||
pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<()>;
|
||||
/// 订阅配置变更事件
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<ConfigChange>;
|
||||
|
||||
/// 删除配置项
|
||||
pub async fn delete(&self, key: &str) -> Result<()>;
|
||||
/// 检查系统是否已初始化(无锁)
|
||||
pub fn is_initialized(&self) -> bool;
|
||||
|
||||
/// 重置为默认
|
||||
pub async fn reset_to_default(&self) -> Result<()>;
|
||||
/// 获取数据库连接池(用于会话管理)
|
||||
pub fn pool(&self) -> &Pool<Sqlite>;
|
||||
}
|
||||
```
|
||||
|
||||
**性能特点**:
|
||||
- `get()` 是无锁读取操作,返回 `Arc<AppConfig>`,无需克隆
|
||||
- 配置读取频率远高于写入,ArcSwap 优化了读取路径
|
||||
- 写入操作先持久化到数据库,再原子性更新内存缓存
|
||||
- 使用 broadcast channel 通知配置变更,支持多订阅者
|
||||
|
||||
**数据库连接池配置**:
|
||||
```rust
|
||||
SqlitePoolOptions::new()
|
||||
.max_connections(2) // SQLite 单写模式,2 个连接足够
|
||||
.acquire_timeout(Duration::from_secs(5))
|
||||
.idle_timeout(Duration::from_secs(300))
|
||||
```
|
||||
|
||||
### 2.2 AppConfig (schema.rs)
|
||||
|
||||
主应用配置结构,包含所有子系统的配置:
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct AppConfig {
|
||||
/// 视频配置
|
||||
/// 初始设置是否完成
|
||||
pub initialized: bool,
|
||||
|
||||
/// 认证配置
|
||||
pub auth: AuthConfig,
|
||||
|
||||
/// 视频采集配置
|
||||
pub video: VideoConfig,
|
||||
|
||||
/// 流配置
|
||||
pub stream: StreamConfig,
|
||||
|
||||
/// HID 配置
|
||||
/// HID(键盘/鼠标)配置
|
||||
pub hid: HidConfig,
|
||||
|
||||
/// MSD 配置
|
||||
/// MSD(大容量存储)配置
|
||||
pub msd: MsdConfig,
|
||||
|
||||
/// ATX 配置
|
||||
/// ATX 电源控制配置
|
||||
pub atx: AtxConfig,
|
||||
|
||||
/// 音频配置
|
||||
pub audio: AudioConfig,
|
||||
|
||||
/// 认证配置
|
||||
pub auth: AuthConfig,
|
||||
/// 流媒体配置
|
||||
pub stream: StreamConfig,
|
||||
|
||||
/// Web 配置
|
||||
/// Web 服务器配置
|
||||
pub web: WebConfig,
|
||||
|
||||
/// RustDesk 配置
|
||||
pub rustdesk: RustDeskConfig,
|
||||
|
||||
/// 扩展配置
|
||||
/// 扩展配置(ttyd, gostc, easytier)
|
||||
pub extensions: ExtensionsConfig,
|
||||
|
||||
/// RustDesk 远程访问配置
|
||||
pub rustdesk: RustDeskConfig,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 各模块配置
|
||||
### 2.3 主要子配置结构
|
||||
|
||||
#### AuthConfig - 认证配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct AuthConfig {
|
||||
/// 会话超时时间(秒)
|
||||
pub session_timeout_secs: u32, // 默认 86400(24小时)
|
||||
/// 启用双因素认证
|
||||
pub totp_enabled: bool,
|
||||
/// TOTP 密钥(加密存储)
|
||||
pub totp_secret: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
#### VideoConfig - 视频采集配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct VideoConfig {
|
||||
/// 视频设备路径(如 /dev/video0)
|
||||
pub device: Option<String>,
|
||||
/// 像素格式(如 "MJPEG", "YUYV", "NV12")
|
||||
pub format: Option<String>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fps: u32,
|
||||
pub quality: u32,
|
||||
/// 分辨率宽度
|
||||
pub width: u32, // 默认 1920
|
||||
/// 分辨率高度
|
||||
pub height: u32, // 默认 1080
|
||||
/// 帧率
|
||||
pub fps: u32, // 默认 30
|
||||
/// JPEG 质量(1-100)
|
||||
pub quality: u32, // 默认 80
|
||||
}
|
||||
```
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct StreamConfig {
|
||||
pub mode: StreamMode,
|
||||
pub bitrate_kbps: u32,
|
||||
pub gop_size: u32,
|
||||
pub encoder: EncoderType,
|
||||
pub stun_server: Option<String>,
|
||||
pub turn_server: Option<String>,
|
||||
pub turn_username: Option<String>,
|
||||
pub turn_password: Option<String>,
|
||||
}
|
||||
#### HidConfig - HID 配置
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct HidConfig {
|
||||
pub backend: HidBackendType,
|
||||
pub ch9329_device: Option<String>,
|
||||
pub ch9329_baud_rate: Option<u32>,
|
||||
pub default_mouse_mode: MouseMode,
|
||||
/// HID 后端类型
|
||||
pub backend: HidBackend, // Otg | Ch9329 | None
|
||||
/// OTG 键盘设备路径
|
||||
pub otg_keyboard: String, // 默认 "/dev/hidg0"
|
||||
/// OTG 鼠标设备路径
|
||||
pub otg_mouse: String, // 默认 "/dev/hidg1"
|
||||
/// OTG UDC(USB 设备控制器)名称
|
||||
pub otg_udc: Option<String>,
|
||||
/// OTG USB 设备描述符配置
|
||||
pub otg_descriptor: OtgDescriptorConfig,
|
||||
/// CH9329 串口路径
|
||||
pub ch9329_port: String, // 默认 "/dev/ttyUSB0"
|
||||
/// CH9329 波特率
|
||||
pub ch9329_baudrate: u32, // 默认 9600
|
||||
/// 鼠标模式:绝对定位或相对定位
|
||||
pub mouse_absolute: bool, // 默认 true
|
||||
}
|
||||
|
||||
// ... 其他配置结构
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[typeshare]
|
||||
pub struct OtgDescriptorConfig {
|
||||
pub vendor_id: u16, // 默认 0x1d6b(Linux Foundation)
|
||||
pub product_id: u16, // 默认 0x0104
|
||||
pub manufacturer: String, // 默认 "One-KVM"
|
||||
pub product: String, // 默认 "One-KVM USB Device"
|
||||
pub serial_number: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
#### StreamConfig - 流媒体配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct StreamConfig {
|
||||
/// 流模式(WebRTC | Mjpeg)
|
||||
pub mode: StreamMode,
|
||||
/// 编码器类型
|
||||
pub encoder: EncoderType, // Auto | Software | Vaapi | Nvenc | Qsv | Amf | Rkmpp | V4l2m2m
|
||||
/// 码率预设(Speed | Balanced | Quality)
|
||||
pub bitrate_preset: BitratePreset,
|
||||
/// 自定义 STUN 服务器
|
||||
pub stun_server: Option<String>, // 默认 "stun:stun.l.google.com:19302"
|
||||
/// 自定义 TURN 服务器
|
||||
pub turn_server: Option<String>,
|
||||
/// TURN 用户名
|
||||
pub turn_username: Option<String>,
|
||||
/// TURN 密码(加密存储,不通过 API 暴露)
|
||||
pub turn_password: Option<String>,
|
||||
/// 无客户端时自动暂停
|
||||
#[typeshare(skip)]
|
||||
pub auto_pause_enabled: bool,
|
||||
/// 自动暂停延迟(秒)
|
||||
#[typeshare(skip)]
|
||||
pub auto_pause_delay_secs: u64,
|
||||
/// 客户端超时清理(秒)
|
||||
#[typeshare(skip)]
|
||||
pub client_timeout_secs: u64,
|
||||
}
|
||||
```
|
||||
|
||||
#### MsdConfig - 大容量存储配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct MsdConfig {
|
||||
/// 启用 MSD 功能
|
||||
pub enabled: bool, // 默认 true
|
||||
/// ISO/IMG 镜像存储路径
|
||||
pub images_path: String, // 默认 "./data/msd/images"
|
||||
/// Ventoy 启动驱动器文件路径
|
||||
pub drive_path: String, // 默认 "./data/msd/ventoy.img"
|
||||
/// 虚拟驱动器大小(MB,最小 1024)
|
||||
pub virtual_drive_size_mb: u32, // 默认 16384(16GB)
|
||||
}
|
||||
```
|
||||
|
||||
#### AtxConfig - ATX 电源控制配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct AtxConfig {
|
||||
/// 启用 ATX 功能
|
||||
pub enabled: bool,
|
||||
/// 电源按钮配置(短按和长按共用)
|
||||
pub power: AtxKeyConfig,
|
||||
/// 重置按钮配置
|
||||
pub reset: AtxKeyConfig,
|
||||
/// LED 检测配置(可选)
|
||||
pub led: AtxLedConfig,
|
||||
/// WOL 数据包使用的网络接口(空字符串 = 自动)
|
||||
pub wol_interface: String,
|
||||
}
|
||||
```
|
||||
|
||||
#### AudioConfig - 音频配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct AudioConfig {
|
||||
/// 启用音频采集
|
||||
pub enabled: bool, // 默认 false
|
||||
/// ALSA 设备名称
|
||||
pub device: String, // 默认 "default"
|
||||
/// 音频质量预设:"voice" | "balanced" | "high"
|
||||
pub quality: String, // 默认 "balanced"
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:采样率固定为 48000Hz,声道固定为 2(立体声),这是 Opus 编码和 WebRTC 的最佳配置。
|
||||
|
||||
#### WebConfig - Web 服务器配置
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
#[typeshare]
|
||||
pub struct WebConfig {
|
||||
/// HTTP 端口
|
||||
pub http_port: u16, // 默认 8080
|
||||
/// HTTPS 端口
|
||||
pub https_port: u16, // 默认 8443
|
||||
/// 绑定地址
|
||||
pub bind_address: String, // 默认 "0.0.0.0"
|
||||
/// 启用 HTTPS
|
||||
pub https_enabled: bool, // 默认 false
|
||||
/// 自定义 SSL 证书路径
|
||||
pub ssl_cert_path: Option<String>,
|
||||
/// 自定义 SSL 密钥路径
|
||||
pub ssl_key_path: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -170,22 +354,55 @@ typeshare src --lang=typescript --output-file=web/src/types/generated.ts
|
||||
|
||||
## 4. API 端点
|
||||
|
||||
所有配置端点均需要 **Admin** 权限,采用 RESTful 设计,按功能域分离。
|
||||
|
||||
### 4.1 全局配置
|
||||
|
||||
| 端点 | 方法 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `/api/config` | GET | Admin | 获取完整配置(敏感信息已过滤) |
|
||||
| `/api/config` | POST | Admin | 更新完整配置(已废弃,请使用按域 PATCH) |
|
||||
|
||||
### 4.2 分域配置端点
|
||||
|
||||
| 端点 | 方法 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `/api/config` | GET | Admin | 获取完整配置 |
|
||||
| `/api/config` | PATCH | Admin | 更新配置 |
|
||||
| `/api/config/video` | GET | Admin | 获取视频配置 |
|
||||
| `/api/config/video` | PATCH | Admin | 更新视频配置 |
|
||||
| `/api/config/video` | PATCH | Admin | 更新视频配置(部分更新) |
|
||||
| `/api/config/stream` | GET | Admin | 获取流配置 |
|
||||
| `/api/config/stream` | PATCH | Admin | 更新流配置 |
|
||||
| `/api/config/stream` | PATCH | Admin | 更新流配置(部分更新) |
|
||||
| `/api/config/hid` | GET | Admin | 获取 HID 配置 |
|
||||
| `/api/config/hid` | PATCH | Admin | 更新 HID 配置 |
|
||||
| `/api/config/reset` | POST | Admin | 重置为默认 |
|
||||
| `/api/config/hid` | PATCH | Admin | 更新 HID 配置(部分更新) |
|
||||
| `/api/config/msd` | GET | Admin | 获取 MSD 配置 |
|
||||
| `/api/config/msd` | PATCH | Admin | 更新 MSD 配置(部分更新) |
|
||||
| `/api/config/atx` | GET | Admin | 获取 ATX 配置 |
|
||||
| `/api/config/atx` | PATCH | Admin | 更新 ATX 配置(部分更新) |
|
||||
| `/api/config/audio` | GET | Admin | 获取音频配置 |
|
||||
| `/api/config/audio` | PATCH | Admin | 更新音频配置(部分更新) |
|
||||
| `/api/config/web` | GET | Admin | 获取 Web 服务器配置 |
|
||||
| `/api/config/web` | PATCH | Admin | 更新 Web 服务器配置(部分更新) |
|
||||
|
||||
### 响应格式
|
||||
### 4.3 RustDesk 配置端点
|
||||
|
||||
| 端点 | 方法 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `/api/config/rustdesk` | GET | Admin | 获取 RustDesk 配置 |
|
||||
| `/api/config/rustdesk` | PATCH | Admin | 更新 RustDesk 配置 |
|
||||
| `/api/config/rustdesk/status` | GET | Admin | 获取 RustDesk 服务状态 |
|
||||
| `/api/config/rustdesk/password` | GET | Admin | 获取设备密码 |
|
||||
| `/api/config/rustdesk/regenerate-id` | POST | Admin | 重新生成设备 ID |
|
||||
| `/api/config/rustdesk/regenerate-password` | POST | Admin | 重新生成设备密码 |
|
||||
|
||||
### 4.4 请求/响应示例
|
||||
|
||||
#### 获取视频配置
|
||||
|
||||
```bash
|
||||
GET /api/config/video
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
// GET /api/config/video
|
||||
{
|
||||
"device": "/dev/video0",
|
||||
"format": "MJPEG",
|
||||
@@ -194,90 +411,282 @@ typeshare src --lang=typescript --output-file=web/src/types/generated.ts
|
||||
"fps": 30,
|
||||
"quality": 80
|
||||
}
|
||||
```
|
||||
|
||||
#### 部分更新视频配置
|
||||
|
||||
```bash
|
||||
PATCH /api/config/video
|
||||
Content-Type: application/json
|
||||
|
||||
// PATCH /api/config/video
|
||||
// Request:
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
"height": 720,
|
||||
"fps": 60
|
||||
}
|
||||
|
||||
// Response: 更新后的完整配置
|
||||
```
|
||||
|
||||
响应:更新后的完整 VideoConfig
|
||||
```json
|
||||
{
|
||||
"device": "/dev/video0",
|
||||
"format": "MJPEG",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"fps": 60,
|
||||
"quality": 80
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:
|
||||
- 所有 PATCH 请求都支持部分更新,只需要提供要修改的字段
|
||||
- 未提供的字段保持原有值不变
|
||||
- 更新后返回完整的配置对象
|
||||
- 配置变更会自动触发相关组件重载
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置热重载
|
||||
## 5. 配置变更通知
|
||||
|
||||
配置更改后自动重载相关组件:
|
||||
ConfigStore 提供 broadcast channel 用于配置变更通知:
|
||||
|
||||
```rust
|
||||
// 更新配置
|
||||
config_store.update_config(&new_config).await?;
|
||||
/// 配置变更事件
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigChange {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
// 发布配置变更事件
|
||||
events.publish(SystemEvent::ConfigChanged {
|
||||
section: "video".to_string(),
|
||||
// 订阅配置变更
|
||||
let mut rx = config_store.subscribe();
|
||||
|
||||
// 监听变更事件
|
||||
while let Ok(change) = rx.recv().await {
|
||||
println!("配置 {} 已更新", change.key);
|
||||
// 重载相关组件
|
||||
}
|
||||
```
|
||||
|
||||
**工作流程**:
|
||||
1. 调用 `config_store.set()` 或 `config_store.update()`
|
||||
2. 配置写入数据库(持久化)
|
||||
3. 原子性更新内存缓存(ArcSwap)
|
||||
4. 发送 `ConfigChange` 事件到 broadcast channel
|
||||
5. 各组件的订阅者接收事件并执行重载逻辑
|
||||
|
||||
**组件重载示例**:
|
||||
```rust
|
||||
// VideoStreamManager 监听配置变更
|
||||
let mut config_rx = config_store.subscribe();
|
||||
tokio::spawn(async move {
|
||||
while let Ok(change) = config_rx.recv().await {
|
||||
if change.key == "app_config" {
|
||||
video_manager.reload().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 各组件监听事件并重载
|
||||
// VideoStreamManager::on_config_changed()
|
||||
// HidController::reload()
|
||||
// etc.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据库结构
|
||||
|
||||
ConfigStore 使用 SQLite 存储配置和其他系统数据:
|
||||
|
||||
### 6.1 配置表
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
配置以 JSON 格式存储:
|
||||
```sql
|
||||
-- 应用配置
|
||||
key: 'app_config'
|
||||
value: '{"initialized": true, "video": {...}, "hid": {...}, ...}'
|
||||
```
|
||||
|
||||
### 6.2 用户表
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
key: "app_config"
|
||||
value: { "video": {...}, "hid": {...}, ... }
|
||||
|
||||
### 6.3 会话表
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
expires_at TEXT NOT NULL,
|
||||
data TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### 6.4 API 令牌表
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
permissions TEXT NOT NULL,
|
||||
expires_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_used TEXT
|
||||
);
|
||||
```
|
||||
|
||||
**存储特点**:
|
||||
- 所有配置存储在单个 JSON 文本中(`app_config` key)
|
||||
- 每次配置更新都更新整个 JSON,简化事务处理
|
||||
- 使用 `ON CONFLICT` 实现 upsert 操作
|
||||
- 连接池大小为 2(1 读 + 1 写),适合嵌入式环境
|
||||
|
||||
---
|
||||
|
||||
## 7. 使用示例
|
||||
|
||||
### 7.1 基本用法
|
||||
|
||||
```rust
|
||||
// 获取配置
|
||||
let config = config_store.get_config().await?;
|
||||
println!("Video device: {:?}", config.video.device);
|
||||
use crate::config::ConfigStore;
|
||||
use std::path::Path;
|
||||
|
||||
// 更新配置
|
||||
let mut config = config_store.get_config().await?;
|
||||
config.video.width = 1280;
|
||||
config.video.height = 720;
|
||||
config_store.update_config(&config).await?;
|
||||
// 创建配置存储
|
||||
let config_store = ConfigStore::new(Path::new("./data/config.db")).await?;
|
||||
|
||||
// 获取单个配置项
|
||||
let video: Option<VideoConfig> = config_store.get("video").await?;
|
||||
// 获取配置(无锁,零拷贝)
|
||||
let config = config_store.get();
|
||||
println!("视频设备: {:?}", config.video.device);
|
||||
println!("是否已初始化: {}", config.initialized);
|
||||
|
||||
// 设置单个配置项
|
||||
config_store.set("video", &video_config).await?;
|
||||
// 检查是否已初始化
|
||||
if !config_store.is_initialized() {
|
||||
println!("系统尚未初始化,请完成初始设置");
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 更新配置
|
||||
|
||||
```rust
|
||||
// 方式 1: 使用闭包更新(推荐)
|
||||
config_store.update(|config| {
|
||||
config.video.width = 1280;
|
||||
config.video.height = 720;
|
||||
config.video.fps = 60;
|
||||
}).await?;
|
||||
|
||||
// 方式 2: 整体替换
|
||||
let mut new_config = (*config_store.get()).clone();
|
||||
new_config.stream.mode = StreamMode::WebRTC;
|
||||
new_config.stream.encoder = EncoderType::Rkmpp;
|
||||
config_store.set(new_config).await?;
|
||||
```
|
||||
|
||||
### 7.3 订阅配置变更
|
||||
|
||||
```rust
|
||||
// 在组件中监听配置变更
|
||||
let config_store = state.config.clone();
|
||||
let mut rx = config_store.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok(change) = rx.recv().await {
|
||||
tracing::info!("配置 {} 已变更", change.key);
|
||||
|
||||
// 重新加载配置
|
||||
let config = config_store.get();
|
||||
|
||||
// 执行重载逻辑
|
||||
if let Err(e) = reload_component(&config).await {
|
||||
tracing::error!("重载组件失败: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 7.4 在 Handler 中使用
|
||||
|
||||
```rust
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::VideoConfig;
|
||||
use crate::state::AppState;
|
||||
|
||||
// 获取视频配置
|
||||
pub async fn get_video_config(
|
||||
State(state): State<Arc<AppState>>
|
||||
) -> Json<VideoConfig> {
|
||||
let config = state.config.get();
|
||||
Json(config.video.clone())
|
||||
}
|
||||
|
||||
// 更新视频配置
|
||||
pub async fn update_video_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(update): Json<VideoConfig>,
|
||||
) -> Result<Json<VideoConfig>> {
|
||||
// 更新配置
|
||||
state.config.update(|config| {
|
||||
config.video = update;
|
||||
}).await?;
|
||||
|
||||
// 返回更新后的配置
|
||||
let config = state.config.get();
|
||||
Ok(Json(config.video.clone()))
|
||||
}
|
||||
```
|
||||
|
||||
### 7.5 访问数据库连接池
|
||||
|
||||
```rust
|
||||
// ConfigStore 还提供数据库连接池访问
|
||||
// 用于用户管理、会话管理等功能
|
||||
|
||||
let pool = config_store.pool();
|
||||
|
||||
// 查询用户
|
||||
let user: Option<User> = sqlx::query_as(
|
||||
"SELECT * FROM users WHERE username = ?"
|
||||
)
|
||||
.bind(username)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 默认配置
|
||||
|
||||
系统首次运行时会自动创建默认配置:
|
||||
|
||||
```rust
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
initialized: false, // 需要通过初始设置向导完成
|
||||
auth: AuthConfig {
|
||||
session_timeout_secs: 86400, // 24小时
|
||||
totp_enabled: false,
|
||||
totp_secret: None,
|
||||
},
|
||||
video: VideoConfig {
|
||||
device: None,
|
||||
format: None,
|
||||
device: None, // 自动检测
|
||||
format: None, // 自动检测或使用 MJPEG
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
fps: 30,
|
||||
@@ -285,13 +694,62 @@ impl Default for AppConfig {
|
||||
},
|
||||
stream: StreamConfig {
|
||||
mode: StreamMode::Mjpeg,
|
||||
bitrate_kbps: 2000,
|
||||
gop_size: 60,
|
||||
encoder: EncoderType::H264,
|
||||
..Default::default()
|
||||
encoder: EncoderType::Auto,
|
||||
bitrate_preset: BitratePreset::Balanced,
|
||||
stun_server: Some("stun:stun.l.google.com:19302".to_string()),
|
||||
turn_server: None,
|
||||
turn_username: None,
|
||||
turn_password: None,
|
||||
auto_pause_enabled: false,
|
||||
auto_pause_delay_secs: 10,
|
||||
client_timeout_secs: 30,
|
||||
},
|
||||
// ...
|
||||
hid: HidConfig {
|
||||
backend: HidBackend::None, // 需要用户手动启用
|
||||
otg_keyboard: "/dev/hidg0".to_string(),
|
||||
otg_mouse: "/dev/hidg1".to_string(),
|
||||
otg_udc: None, // 自动检测
|
||||
otg_descriptor: OtgDescriptorConfig::default(),
|
||||
ch9329_port: "/dev/ttyUSB0".to_string(),
|
||||
ch9329_baudrate: 9600,
|
||||
mouse_absolute: true,
|
||||
},
|
||||
msd: MsdConfig {
|
||||
enabled: true,
|
||||
images_path: "./data/msd/images".to_string(),
|
||||
drive_path: "./data/msd/ventoy.img".to_string(),
|
||||
virtual_drive_size_mb: 16384, // 16GB
|
||||
},
|
||||
atx: AtxConfig {
|
||||
enabled: false, // 需要用户配置硬件绑定
|
||||
power: AtxKeyConfig::default(),
|
||||
reset: AtxKeyConfig::default(),
|
||||
led: AtxLedConfig::default(),
|
||||
wol_interface: String::new(), // 自动检测
|
||||
},
|
||||
audio: AudioConfig {
|
||||
enabled: false,
|
||||
device: "default".to_string(),
|
||||
quality: "balanced".to_string(),
|
||||
},
|
||||
web: WebConfig {
|
||||
http_port: 8080,
|
||||
https_port: 8443,
|
||||
bind_address: "0.0.0.0".to_string(),
|
||||
https_enabled: false,
|
||||
ssl_cert_path: None,
|
||||
ssl_key_path: None,
|
||||
},
|
||||
extensions: ExtensionsConfig::default(),
|
||||
rustdesk: RustDeskConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**配置初始化流程**:
|
||||
1. 用户首次访问 Web UI,系统检测到 `initialized = false`
|
||||
2. 重定向到初始设置向导(`/setup`)
|
||||
3. 用户设置管理员账户、选择视频设备等
|
||||
4. 完成设置后,`initialized` 设为 `true`
|
||||
5. 后续可通过设置页面(`/settings`)修改各项配置
|
||||
|
||||
1170
docs/modules/hid.md
1170
docs/modules/hid.md
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -17,14 +17,16 @@ WebRTC 模块提供低延迟的实时音视频流传输,支持多种视频编
|
||||
```
|
||||
src/webrtc/
|
||||
├── mod.rs # 模块导出
|
||||
├── webrtc_streamer.rs # 统一管理器 (34KB)
|
||||
├── webrtc_streamer.rs # 统一管理器 (35KB)
|
||||
├── universal_session.rs # 会话管理 (32KB)
|
||||
├── unified_video_track.rs # 统一视频轨道 (15KB)
|
||||
├── video_track.rs # 视频轨道 (19KB)
|
||||
├── rtp.rs # RTP 打包 (24KB)
|
||||
├── h265_payloader.rs # H265 RTP (15KB)
|
||||
├── peer.rs # PeerConnection (17KB)
|
||||
├── config.rs # 配置 (3KB)
|
||||
├── signaling.rs # 信令 (5KB)
|
||||
├── session.rs # 会话基类 (8KB)
|
||||
└── track.rs # 轨道基类 (11KB)
|
||||
```
|
||||
|
||||
@@ -710,7 +712,57 @@ for (const candidate of ice_candidates) {
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题
|
||||
## 10. 管道重启机制
|
||||
|
||||
当码率或编码器配置变更时,视频管道需要重启。WebRTC 模块实现了自动重连机制:
|
||||
|
||||
### 10.1 重启流程
|
||||
|
||||
```
|
||||
用户修改码率/编码器
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ set_bitrate_preset │
|
||||
│ 1. 保存 frame_tx │ ← 关键:在停止前保存
|
||||
│ 2. 停止旧管道 │
|
||||
│ 3. 等待清理 │
|
||||
│ 4. 恢复 frame_tx │
|
||||
│ 5. 创建新管道 │
|
||||
│ 6. 重连所有会话 │
|
||||
└─────────────────────┘
|
||||
│
|
||||
▼
|
||||
所有 WebRTC 会话自动恢复
|
||||
```
|
||||
|
||||
### 10.2 关键代码
|
||||
|
||||
```rust
|
||||
pub async fn set_bitrate_preset(self: &Arc<Self>, preset: BitratePreset) -> Result<()> {
|
||||
// 保存 frame_tx (监控任务会在管道停止后清除它)
|
||||
let saved_frame_tx = self.video_frame_tx.read().await.clone();
|
||||
|
||||
// 停止管道
|
||||
pipeline.stop();
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
// 恢复 frame_tx 并重建管道
|
||||
if let Some(tx) = saved_frame_tx {
|
||||
*self.video_frame_tx.write().await = Some(tx.clone());
|
||||
let pipeline = self.ensure_video_pipeline(tx).await?;
|
||||
|
||||
// 重连所有会话
|
||||
for session in sessions {
|
||||
session.start_from_video_pipeline(pipeline.subscribe(), ...).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
### Q: 连接超时?
|
||||
|
||||
@@ -729,3 +781,9 @@ for (const candidate of ice_candidates) {
|
||||
1. 检查时间戳同步
|
||||
2. 调整缓冲区大小
|
||||
3. 使用 NTP 同步
|
||||
|
||||
### Q: 切换码率后视频静止?
|
||||
|
||||
1. 检查管道重启逻辑是否正确保存了 `video_frame_tx`
|
||||
2. 确认会话重连成功
|
||||
3. 查看日志中是否有 "Reconnecting session" 信息
|
||||
|
||||
Reference in New Issue
Block a user