CI/CD / Build Frontend (push) Failing after 11s
CI/CD / Test (macos-latest) (push) Has been cancelled
CI/CD / Test (windows-latest) (push) Has been cancelled
CI/CD / Build Desktop (linux) (push) Has been cancelled
CI/CD / Build Desktop (macos) (push) Has been cancelled
CI/CD / Build Desktop (windows) (push) Has been cancelled
CI/CD / Release (push) Has been cancelled
CI/CD / Test (ubuntu-latest) (push) Failing after 2s
- tscore: Protocol implementation (packets, crypto, connection handshake) - tsaudio: Audio engine (capture, playback, codec, VAD, jitter buffer) - tsdb: SQLite database (identities, bookmarks, messages, settings) - shared: Core types and events - tauri-app: Tauri v2 desktop application with React frontend - docs: SRS, SAD, SDD documentation - CI/CD: GitHub Actions workflow - 32 unit tests passing
713 lines
19 KiB
Markdown
713 lines
19 KiB
Markdown
# 组件设计文档 (SDD)
|
|
|
|
## 1. 概述
|
|
|
|
本文档详细描述系统各组件的设计,包括功能、接口、数据结构和实现细节。
|
|
|
|
## 2. 核心组件
|
|
|
|
### 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 验证 |