refactor: remove legacy tauri stack and update iced docs
This commit is contained in:
+15
-709
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user