refactor: remove legacy tauri stack and update iced docs

This commit is contained in:
ReTeamSpeak
2026-05-13 13:35:22 +09:00
parent 4a54f6667c
commit abf380476f
94 changed files with 789 additions and 13393 deletions
+15 -709
View File
@@ -1,713 +1,19 @@
# 组件设计文档 (SDD)
# Components
## 1. 概述
## Shipped Components
本文档详细描述系统各组件的设计,包括功能、接口、数据结构和实现细节。
- `iced-app/src/main.rs` - app state, views, TeamSpeak actions
- `iced-app/src/audio.rs` - local audio capture/playback/encoding helpers
- `iced-app/src/noise_cancel.rs` - noise reduction backends
- `iced-app/src/identity.rs` - TeamSpeak identity import
- `iced-app/src/theme.rs` - UI theme styles
## 2. 核心组件
## External Core Dependencies
### 2.1 协议库组件 (tsclientlib)
#### 2.1.1 tsproto-types (基础类型)
**职责**: 定义 TeamSpeak 协议中使用的基础类型、枚举和加密原语
**关键类型**:
```rust
pub struct ClientId(pub u16); // 客户端 ID
pub struct ChannelId(pub u64); // 频道 ID
pub struct UidBuf(pub Vec<u8>); // 用户唯一标识
pub struct Permission(pub u32); // 权限 ID
pub enum ClientType { Normal, Query { admin: bool } }
pub enum MaxClients { Unlimited, Inherited, Limited(u16) }
```
**加密模块**:
```rust
pub struct EccKeyPubP256(p256::PublicKey); // P-256 公钥
pub struct EccKeyPrivP256(p256::SecretKey); // P-256 私钥
pub struct EccKeyPubEd25519(CompressedEdwardsY); // Ed25519 公钥
pub struct EccKeyPrivEd25519(Scalar); // Ed25519 私钥
```
**错误码枚举**: 从 CSV 自动生成,包含所有 TeamSpeak 错误码
#### 2.1.2 tsproto-structs (声明式数据)
**职责**: 提供协议的机器可读声明数据
**声明文件**:
- `Book.toml` - 服务器状态数据模型
- `Messages.toml` - 协议消息结构
- `Enums.toml` - 枚举定义
- `Errors.csv` - 错误码列表
- `Versions.csv` - 版本信息
#### 2.1.3 tsproto-packets (包解析)
**职责**: 解析和序列化 TeamSpeak 网络包和命令
**关键类型**:
```rust
pub enum PacketType { Voice, VoiceWhisper, Command, CommandLow, Ping, Pong, Ack, AckLow, Init }
pub enum Direction { S2C, C2S }
// 输入包(零拷贝)
pub struct InPacket<'a> { header: InHeader<'a>, content: &'a [u8] }
pub struct InCommand<'a> { packet: InPacket<'a> }
pub struct InAudio<'a> { packet: InPacket<'a>, data: AudioData<'a> }
// 输出包
pub struct OutPacket { dir: Direction, data: Vec<u8> }
pub struct OutCommand(pub OutPacket);
```
**命令解析器**:
```rust
pub struct CommandParser<'a> { data: &'a [u8], index: usize }
pub enum CommandItem<'a> { Argument(CommandArgument<'a>), NextCommand }
```
#### 2.1.4 ts-bookkeeping (状态管理)
**职责**: 维护 TeamSpeak 服务器的完整状态模型
**核心数据模型**:
```rust
pub struct Connection {
pub own_client: ClientId,
pub server: Server,
pub clients: HashMap<ClientId, Client>,
pub channels: HashMap<ChannelId, Channel>,
pub channel_groups: HashMap<ChannelGroupId, ChannelGroup>,
pub server_groups: HashMap<ServerGroupId, ServerGroup>,
}
pub struct Server { /* 名称、版本、最大客户端数、加密模式等 */ }
pub struct Channel { /* 名称、类型、编解码器、权限等 */ }
pub struct Client { /* 名称、频道、静音状态、权限等 */ }
```
**事件系统**:
```rust
pub enum Event {
PropertyAdded { id: PropertyId, invoker: Option<Invoker>, extra: ExtraInfo },
PropertyChanged { id: PropertyId, old: PropertyValue, invoker: Option<Invoker>, extra: ExtraInfo },
PropertyRemoved { id: PropertyId, old: PropertyValue, invoker: Option<Invoker>, extra: ExtraInfo },
Message { target: MessageTarget, invoker: Invoker, message: String },
}
```
#### 2.1.5 tsproto (协议引擎)
**职责**: 实现 TeamSpeak 3 协议的底层网络通信
**核心类型**:
```rust
pub struct Identity {
key: EccKeyPrivP256,
counter: u64, // Hash Cash 计数器
max_counter: u64,
}
pub struct Client {
con: Connection,
pub private_key: EccKeyPrivP256,
}
pub struct Connection {
pub is_client: bool,
pub params: Option<ConnectedParams>,
pub address: SocketAddr,
pub resender: Resender,
pub codec: PacketCodec,
pub udp_socket: Box<dyn Socket + Send>,
}
```
**连接握手流程**:
```
Client Server
│ │
│──── Init0 (version, ts) ────>│
│<─── Init1 (random1) ─────────│
│──── Init2 (random1_r) ──────>│
│<─── Init3 (RSA puzzle) ──────│
│──── Init4 (solve + ECDH) ───>│
│<─── initivexpand2 (license) ─│
│──── clientek (ephemeral key) >│
│<─── initserver ──────────────│
│ (connected) │
```
#### 2.1.6 tsclientlib (高层客户端库)
**职责**: 提供用户友好的客户端 API
**核心类型**:
```rust
pub struct Connection {
state: ConnectionState,
options: ConnectOptions,
stream_items: VecDeque<Result<StreamItem>>,
}
enum ConnectionState {
Connecting(BoxFuture<...>, bool),
IdentityLevelIncreasing { recv, state },
Connected { con: ConnectedConnection, book: data::Connection },
}
pub enum StreamItem {
BookEvents(Vec<events::Event>),
MessageEvent(InMessage),
Audio(InAudioBuf),
IdentityLevelIncreasing(u8),
IdentityLevelIncreased,
DisconnectedTemporarily(TemporaryDisconnectReason),
MessageResult(MessageHandle, Result<(), CommandError>),
FileDownload(...), FileUpload(...), FiletransferFailed(...),
NetworkStatsUpdated,
AudioChange(AudioEvent),
}
```
**地址解析**:
解析优先级:
1. 直接 IP 地址
2. 服务器昵称 (HTTP 查询)
3. DNS SRV 记录
4. TSDNS 服务
5. 系统 DNS 解析
**音频处理**:
```rust
pub struct AudioHandler<Id> {
queues: HashMap<Id, AudioQueue>,
avg_buffer_samples: usize,
}
pub struct AudioQueue {
decoder: Decoder, // Opus 解码器
packet_buffer: VecDeque<QueuePacket>,
decoded_buffer: Vec<f32>,
last_buffer_size_min: SlidingWindowMinimum<u8>,
}
```
### 2.2 客户端组件 (Qint)
#### 2.2.1 前端组件
**连接状态管理 (connection.ts)**:
```typescript
class Connection {
private book: Book;
private backend: IBackendConnection;
private state: ConnectionState;
// 状态机: Uninitialized -> Connecting -> Connected -> ChannelListFinished -> Disconnected
async connect(onMsg, onError, onClose): Promise<void>;
sendMessage(target, message): void;
switchChannel(channelId): void;
startWhispering(whisperData): void;
}
```
**数据状态镜像 (book.ts)**:
```typescript
class Book {
channels: Map<ChannelId, Channel>;
clients: Map<ClientId, Client>;
serverGroups: Map<ServerGroupId, ServerGroup>;
channelGroups: Map<ChannelGroupId, ChannelGroup>;
processEvent(event: InBookChangeMsg): void;
}
```
**后端抽象层 (backend/)**:
```typescript
interface IBackend {
createNewConnection(returnCodes: ReturnCodeTracker): IBackendConnection;
graphql<T>(query: string, variables?: Record<string, unknown>): Promise<{data: T}>;
get_settings(): Promise<Record<string, unknown>>;
set_settings(diff: Record<string, unknown>): Promise<void>;
}
interface IBackendConnection {
id: string;
connect(onMsg, onError, onClose): Promise<void>;
send(data: OutMsg): void;
close(): void;
fetch_image(req: IFileRequest): Promise<string | undefined>;
upload_bytes(req: IFileRequest, data: Blob): Promise<TransferResult>;
}
```
#### 2.2.2 壳层组件
**Tauri 命令处理器 (cmd.rs)**:
```rust
#[command]
async fn create_ws(state: State<'_, QintCore>, window: Window, con: String) -> Result<(), String>
#[command]
async fn pass_ws_msg(state: State<'_, QintCore>, con: String, msg: MessageF2P) -> Result<(), String>
#[command]
async fn db(state: State<'_, QintState>, query: String, variables: String) -> Result<String, String>
```
**WebSocket 处理器 (websocket.rs)**:
```rust
struct Ws {
state: QintState,
id: String,
addr: Addr<QintConnection>,
}
impl Ws {
fn handle_message(&mut self, msg: F2PMsg) {
match msg.cmd {
"create_ws" => { /* 创建连接 */ }
"pass_ws_msg" => { /* 转发消息 */ }
"get_settings" => { /* 获取设置 */ }
_ => { /* 其他命令 */ }
}
}
}
```
#### 2.2.3 代理层组件
**全局状态管理 (QintState)**:
```rust
pub struct QintState {
pub connections: Mutex<HashMap<String, Addr<QintConnection>>>,
pub audio_data: Arc<AudioData>,
pub hotkeys: HotkeyManager,
pub settings: RwLock<Settings>,
pub database: Addr<DbHandler>,
pub graphql_schema: Schema<QueryRoot, MutationRoot, EmptySubscription>,
pub file_cache: FileCache,
pub link_previewer: LinkPreviewer,
pub secret: chacha20poly1305::Key,
pub search_index: SearchIndex,
}
```
**连接管理器 (QintConnection)**:
```rust
pub struct QintConnection {
state: QintState,
id: String,
con: Option<tsclientlib::Connection>,
bridge: Box<dyn AppToFrontendBridge>,
audio_to_ts: Addr<AudioToTs>,
ts_to_audio: Addr<TsToAudio>,
database: Addr<DbHandler>,
}
impl Actor for QintConnection {
type Context = Context<Self>;
}
impl Handler<MessageF2PWrapper> for QintConnection {
fn handle(&mut self, msg: MessageF2PWrapper, ctx: &mut Self::Context) {
match msg.0 {
MessageF2P::Connect(options) => { /* 建立连接 */ }
MessageF2P::Disconnect(options) => { /* 断开连接 */ }
MessageF2P::SendMessage { target, message, return_code } => { /* 发送消息 */ }
// ...
}
}
}
```
**数据库管理器 (DbHandler)**:
```rust
pub struct DbHandler {
pool: SqlitePool,
}
impl Actor for DbHandler {
type Context = Context<Self>;
}
impl Handler<GetIdentityAndServerMsg> for DbHandler {
fn handle(&mut self, msg: GetIdentityAndServerMsg, ctx: &mut Self::Context) -> Self::Result {
// 从数据库获取身份和服务器信息
}
}
impl Handler<WriteMessageMsg> for DbHandler {
fn handle(&mut self, msg: WriteMessageMsg, ctx: &mut Self::Context) {
// 写入聊天消息到数据库
}
}
```
**音频管道**:
```rust
// AudioToTs - 麦克风采集和编码
pub struct AudioToTs {
connections: Vec<Addr<QintConnection>>,
encoder: OpusEncoder,
vad: VadDetector,
loudness_meter: LoudnessMeter,
}
impl Handler<SendPacketMsg> for AudioToTs {
fn handle(&mut self, msg: SendPacketMsg, ctx: &mut Self::Context) {
// 编码并发送音频数据
}
}
// TsToAudio - 音频解码和播放
pub struct TsToAudio {
queues: HashMap<String, AudioQueue>,
output_device: AudioDevice,
}
impl Handler<PlayMsg> for TsToAudio {
fn handle(&mut self, msg: PlayMsg, ctx: &mut Self::Context) {
// 解码并播放音频
}
}
```
### 2.3 机器人组件 (SimpleBot)
**核心结构**:
```rust
pub struct Bot {
base_dir: PathBuf,
settings_path: PathBuf,
actions: ActionList,
settings: Settings,
rate_limiting: Vec<Instant>,
list: Vec<String>,
should_reload: Cell<bool>,
}
```
**动作系统**:
```rust
pub struct ActionDefinition {
contains: Option<String>,
regex: Option<String>,
chat: Option<String>,
response: Option<String>,
command: Option<String>,
shell: Option<String>,
}
pub struct Action {
matchers: Vec<Matcher>,
reaction: Option<Reaction>,
}
pub enum Matcher {
Regex(Regex),
Mode(Option<TextMessageTargetMode>),
}
pub enum Reaction {
Plain(String),
Command(String),
Shell(String),
Function(ReactionFunction),
}
```
**配置系统**:
```rust
pub struct Settings {
key_file: String,
dynamic_actions: String,
address: String,
channel: Option<ChannelDefinition>,
name: String,
disconnect_message: String,
rate_limit: u8,
prefix: String,
actions: ActionFile,
}
pub struct ActionFile {
include: Vec<String>,
on_message: Vec<ActionDefinition>,
}
```
### 2.4 统计工具组件 (ts3stats)
**核心类**:
```python
class DiagramCreator:
env: jinja2.Environment
diagramTemplate: jinja2.Template
htmlTemplate: jinja2.Template
users: dict[int, User]
vip: list[User]
tabs: list[Tab]
def load_meta(self): pass
def load_data(self): pass
def create_diagrams(self): pass
def fun_per_connected_slot(self, users, callback): pass
class User:
name: str
lastConnected: list[datetime]
connections: list[Connection]
botPlays: list[tuple]
botCommands: list[tuple]
class Connection:
start: datetime
end: datetime
timeout: bool
class Diagram:
filename: str
title: str
plots: list[str]
def render(self): pass
class Tab:
name: str
diagrams: list[Diagram]
```
**插件架构**:
每个 `diags/*.py` 文件暴露一个函数:
```python
def create_diag(dc: DiagramCreator) -> None
```
**配置系统**:
```python
# Settings.py
vips = ["MyName", "friend42"]
merges = [["MyName", "MyNameLaptop"]]
maxUsers = 50
botStats = True
inputFolder = "Logs"
outputFolder = "Result"
```
## 3. 接口设计
### 3.1 Tauri IPC 接口
| 命令 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `create_ws` | `con: String` | `()` | 创建 WebSocket 连接 |
| `close_ws` | `con: String` | `()` | 关闭 WebSocket 连接 |
| `pass_ws_msg` | `con: String, msg: MessageF2P` | `()` | 转发前端消息 |
| `db` | `query: String, variables: String` | `String` | GraphQL 查询 |
| `get_settings` | | `Record<string, unknown>` | 获取设置 |
| `set_settings` | `diff: Record<string, unknown>` | `()` | 更新设置 |
### 3.2 WebSocket 协议
**前端发送**:
```json
{"cmd": "create_ws", "returnCode": "0", "args": {"con": "uuid"}}
{"cmd": "pass_ws_msg", "returnCode": "1", "args": {"con": "uuid", "msg": {"Connect": {...}}}}
{"cmd": "get_settings", "returnCode": "2", "args": {}}
```
**后端发送**:
```json
{"cmd": "resp", "returnCode": "2", "msg": {...}}
{"cmd": "resp_err", "returnCode": "1", "msg": "error"}
{"cmd": "ws", "con": "uuid", "msg": {"Connected": {...}}}
{"cmd": "ws_close", "con": "uuid"}
{"cmd": "loudness", "msg": [0.5, 0.3]}
```
### 3.3 GraphQL 查询
```graphql
type Query {
bookmarks: [Bookmark!]!
servers: [Server!]!
channels(serverId: ID!): [Channel!]!
clients(serverId: ID!): [Client!]!
identities: [Identity!]!
chats(serverId: ID!): [Chat!]!
messages(chatId: ID!, limit: Int): [Message!]!
}
type Mutation {
updateIdentity(id: ID!, input: IdentityInput!): Identity!
updateBookmark(id: ID!, input: BookmarkInput!): Bookmark!
}
```
## 4. 数据流
### 4.1 连接建立流程
```
前端 代理 TeamSpeak 服务器
│ │ │
│── Connect(addr, name) ───>│ │
│ │── GetIdentityAndServerMsg ────> DbHandler
│ │<── (identity, server) ──────────│
│ │── Connection::build().connect ─>│
│ │ │
│ │<── TsStreamItem::BookEvents ────│
│ │ (PropertyAdded: Server) │
│ │ │
│<── Connected {server, own} │ │
│ │── ConnectedMsg ────────────────> DbHandler
│ │ │
│<── Events [PropertyAdded..]│ │
```
### 4.2 消息发送流程
```
前端 代理 TeamSpeak 服务器
│ │ │
│── SendMessage {target, msg}│ │
│ │── state.send_message(target) ──>│
│ │── WriteMessageMsg ─────────────> DbHandler
│ │ │
│ │<── InMessage (from other) ──────│
│ │── JsInMessage ─────────────────>│
│<── Message(JsInMessage) ──│ │
│ │── WriteMessageMsg ─────────────> DbHandler
```
### 4.3 音频流程
```
麦克风 → AudioToTs (Actor)
├── VAD 检测
├── 响度测量
├── Opus 编码
└── 发送到服务器
服务器 → tsclientlib::Connection
├── AudioData::S2C
└── TsToAudio (Actor)
├── Opus 解码
├── 每客户端音量
├── 混音
├── 噪声抑制
└── SDL2/Oboe 输出
```
## 5. 错误处理
### 5.1 协议错误
```rust
pub enum CommandError {
TsError(Ts3ErrorCode),
ConnectionClosed,
Timeout,
InvalidResponse,
// ...
}
```
### 5.2 连接错误
```rust
pub enum TemporaryDisconnectReason {
Timeout,
ServerShutdown,
ConnectionLost,
// ...
}
```
### 5.3 前端错误
```typescript
interface ErrorMessage {
type: "error";
message: string;
code?: string;
}
```
## 6. 配置管理
### 6.1 应用配置
```rust
pub struct Settings {
pub name: String,
pub away: Option<String>,
pub input_muted: bool,
pub output_muted: bool,
pub hotkeys: Vec<HotkeyAction>,
pub client_volumes: HashMap<String, f32>,
pub theme: String,
pub language: String,
}
```
### 6.2 连接配置
```rust
pub struct ConnectOptions {
address: ServerAddress,
local_address: Option<SocketAddr>,
identity: Option<Identity>,
server: Option<UidBuf>,
name: String,
version: Version,
channel: Option<String>,
channel_password: Option<String>,
server_password: Option<String>,
default_token: Option<String>,
}
```
## 7. 性能考虑
### 7.1 音频处理
- Opus 编码/解码使用硬件加速(如果可用)
- 自适应抖动缓冲减少延迟
- 噪声抑制减少带宽使用
### 7.2 状态同步
- 增量更新减少数据传输
- 事件批处理减少 IPC 调用
- 懒加载减少初始加载时间
### 7.3 数据库
- SQLite WAL 模式支持并发读取
- 连接池减少连接开销
- 索引优化查询性能
## 8. 安全设计
### 8.1 身份加密
```rust
// ChaCha20-Poly1305 加密身份私钥
fn encrypt_identity(key: &[u8; 32], identity: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(key.into());
let nonce = generate_nonce();
cipher.encrypt(&nonce, identity)
}
```
### 8.2 传输加密
- 所有命令和语音数据使用 AES-128-EAX 加密
- ECDH 密钥交换确保前向保密
- RSA 拼图防止 DoS 攻击
### 8.3 输入验证
- 所有用户输入进行验证和转义
- SQL 查询使用参数化语句
- WebSocket 消息进行 JSON 验证
- `tsclientlib`
- `tsproto-packets`
- `iced`
- `cpal`
- `audiopus`
- `nnnoiseless`
- `sonora`