# 流程文档总览 > 本文档按用户流程组织,将 SDK 技术行为拆分为七个主流程文件和一个独立的状态同步流程文件。 > 依据:`docs/sdk-bridge-api.md` > SDK:`github.com/honeybbq/teamspeak-go` → gomobile AAR → `TSBridge.kt` 应用层桥接 --- ## 一、完整用户流程 ``` 进入应用 │ ├─ 已有最近服务器? ──是──→ 显示最近服务器列表 ──→ 选择服务器 │ │ └─ 否 ──→ 输入服务器地址 ──→ 输入昵称 ──→ 输入密码(可选) ──→┘ │ 点击"进入服务器" │ ┌─────────────────────┘ ▼ 连接中(显示进度) │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ 连接成功 连接失败 超时 │ │ │ │ 显示错误原因 显示超时提示 │ 允许重试 允许重试 │ │ │ │ └───────→ 返回输入页面 ▼ 分配到默认频道(首次同步 ①) │ ▼ 浏览频道列表 ──→ 浏览频道中的人员分布 │ │ │ poke 服务器内成员 │ │ ▼ ▼ ┌──→ 进入/切换频道 ←──────────┘ │ │ ← 自身状态同步 ④ │ │ ← 增量同步 ② / 补偿同步 ③ │ │ │ ├─ 频道有密码? ──是──→ 输入密码 │ │ │ ├─ 收听语音 ──→ 发送语音(PTT) │ │ │ ├─ 收到文本消息 ──→ 消息归档同步 ⑤ ──→ 发送文本消息 │ │ │ ├─ 浏览频道文件 ──→ 上传文件 ──→ 下载文件 │ │ │ ├─ poke 服务器内成员 ──→ 被 poke │ │ │ ▼ │ 切换频道 / 留在当前频道 │ │ └────┘ │ ▼ ┌─── 离开原因 ───┐ │ │ │ ▼ ▼ ▼ 主动断开 网络异常 被踢出 │ │ │ ▼ ▼ ▼ 发送 显示错误 显示被踢原因 disconnect 允许重连 允许重连 │ │ │ │ 重连后全量同步 ⑥ │ │ │ └────────┼─────────┘ ▼ 清理会话状态 │ ▼ 返回应用主页 ``` > **① 首次同步** ② **增量同步** ③ **补偿同步** ④ **自身状态同步** ⑤ **消息归档同步** ⑥ **重连后全量同步** > 详见 [08_状态同步.md](08_状态同步.md) --- ## 二、用户流程对应的程序流程概览 ### 2.1 连接服务器 ``` 读取本地配置 │ 检查输入是否合法 │ 创建 Identity(首次生成,后续读取) │ TSBridge.connect(host, nickname, password, defaultChannel, defaultChannelPassword, callbacks) │ callbacks 内部注册 EventBus.emit()(将 JNI 回调转为 EventBus 事件) │ TSClient.connect() → 建立 UDP 连接 → 身份认证 → 服务器密码验证 │ ├── 失败 → 连接失败处理 → 显示错误 │ └── 成功 → │ EventBus 收到 Connected 事件 │ 开始同步服务器数据 │ ├── 获取频道列表 TSBridge.getChannelsJSON() ├── 获取成员列表 TSBridge.getClientsJSON() └── 获取自身 ID TSBridge.getClientID() │ 建立本地状态(频道基线 + 成员基线) │ UI 切换到服务器页面 ``` ### 2.2 浏览频道与成员 ``` 已同步的频道列表 → 构建树形结构 → 渲染频道树 │ 已同步的成员列表 → 按 channelID 索引 → 派生每个频道的成员列表 │ 持续接收 EventBus 事件(debounce 合并后处理): ├── ClientEnter → 新增成员到成员表 ├── ClientMoved → 更新成员所在频道 ├── ClientLeave → 从成员表移除 └── Poked → 显示 Poke 通知 │ 未知 clientID → 触发 TSBridge.getClientsJSON() 补偿同步 未知 channelID → 触发 TSBridge.getChannelsJSON() 补偿同步 ``` ### 2.3 切换频道 ``` 用户选择目标频道 │ ├── 频道有密码? → 弹出密码输入 │ ├── TsClient.clientMove(selfID, targetID, password) │ ├── 命令被拒绝 → 显示失败原因 → 允许重试 │ └── 命令成功 → 等待 onClientMoved 服务端事实 │ 比对 clientID == selfID │ 提交新频道事实 → UI 导航到目标频道 ``` ### 2.4 发送与接收文本消息 ``` 发送: 用户输入消息 → 选择目标模式(频道/私聊/服务器) │ TSBridge.sendTextMessage(targetMode, targetId, message) │ ├── 失败 → 标记消息发送失败 → 允许重试 │ └── 成功 → 标记发送完成 接收: 服务器推送 notifytextmessage │ EventBus 收到 TextMessage 事件(立即分发,不合并) │ 按 targetMode 与 target 归档(不用当前页面猜测) │ 更新目标会话和未读状态 ``` ### 2.5 语音通信 ``` 用户按下 PTT 按钮 │ ├── 前置条件检查(已连接 + 已在频道中) │ └── 不满足 → 显示无法发言原因 │ └── 满足 → 启动麦克风采集 + Opus 编码(Dispatchers.IO) │ 循环发送: └── 每帧 → TSBridge.sendVoice(data, codec) │ ├── 用户松开 → StopCapture → 恢复静默 │ └── 连接断开 → StopCapture → 进入 Blocked 接收语音(绕过 EventBus,延迟敏感): VoiceService.handleVoiceData() → Opus 解码 → AudioTrack 播放 ``` ### 2.6 文件传输 ``` 上传: 选择文件 → FileTransferInitUpload(channelID, path, size, overwrite) │ 获得 host/port/key │ DialFileTransfer(host, port, key) → 建立 TCP 连接 │ UploadFileData(host, info, reader) → 传输文件字节流 下载: 选择文件 → FileTransferInitDownload(channelID, path, password) │ 获得 host/port/key │ DialFileTransfer(host, port, key) → 建立 TCP 连接 │ DownloadFileData(host, info, writer) → 接收文件字节流 ``` ### 2.7 断开连接 ``` 主动断开: 用户点击离开 → 状态改为 disconnecting → 阻止新操作 │ TSBridge.disconnect() → 通知服务器客户端主动离开 │ EventBus 收到 Disconnected 事件 → 停止语音和文件传输 → 清理会话状态 → 返回主页 被动断开: UDP 会话中断 → EventBus 收到 Disconnected(error) → 显示错误原因 → 允许重连 被踢出: notifyclientleftview reasonid=4/5 → EventBus 收到 Kicked(reason) → 显示被踢原因 → 允许重连 ``` ### 2.8 状态同步(独立于主流程的横切关注点) ``` 贯穿所有主流程的同步机制: ① 首次同步(连接建立后) 触发:EventBus 收到 Connected 事件 动作:TSBridge.getChannelsJSON() + TSBridge.getClientsJSON() + TSBridge.getClientID() 目标:建立完整的频道基线和成员基线 ② 增量同步(事件驱动) 触发:EventBus 收到 ClientEnter / ClientMoved / ClientLeave 事件 动作:debounce 合并后 refreshClientList() 全量刷新 目标:维护实时成员位置和频道人数 ③ 补偿同步(数据不一致时) 触发:事件引用了未知的 clientID 或 channelID 动作:重新调用 TSBridge.getClientsJSON() 或 TSBridge.getChannelsJSON() 目标:用完整列表修复丢失的基线数据 ④ 自身状态同步(频道切换后) 触发:EventBus 收到自己的 ClientMoved 事件 动作:比对 clientID == selfID → 更新自身所在频道 目标:确认当前用户的实际频道位置 ⑤ 消息归档同步(收到消息时) 触发:EventBus 收到 TextMessage 事件 动作:按 targetMode + target 归档到正确的会话 目标:维护消息与目标频道的对应关系 ⑥ 重连后全量同步(断开重连后) 触发:重新收到 Connected 事件 动作:清空旧会话状态 → 重新执行首次同步 目标:确保重连后数据与服务器完全一致 ⑦ 同步失败处理 触发:TSBridge.getChannelsJSON() / TSBridge.getClientsJSON() 请求失败 动作:标记同步失败 → 不进入业务就绪 → 允许重试 目标:防止在不完整数据上执行业务操作 ``` --- ## 三、文件索引 ### 主流程 | 文件 | 用户流程 | 主要内容 | | --- | --- | --- | | [01_连接服务器.md](01_连接服务器.md) | 输入信息 → 建立连接 → 首次同步 | 生命周期、初始化配置、构造连接时序、状态树、事件依赖 | | [02_浏览频道.md](02_浏览频道.md) | 浏览频道列表 → 查看成员 → Poke | 同步状态、成员状态、成员事件时序、成员实体状态树 | | [03_切换频道.md](03_切换频道.md) | 选择频道 → 输入密码 → 等待确认 | 频道切换时序、切换状态机 | | [04_文本消息.md](04_文本消息.md) | 发送消息 → 接收消息 → 消息归档 | 文本消息时序、聊天状态树 | | [05_语音通信.md](05_语音通信.md) | PTT 按下 → 发送语音帧 → 松开 | 语音发送时序、语音状态树 | | [06_文件传输.md](06_文件传输.md) | 初始化 → TCP 连接 → 数据传输 | 文件传输时序、文件状态树 | | [07_断开连接.md](07_断开连接.md) | 主动断开 / 被动断开 / 被踢 | 断开时序、清理流程 | | [09_EventBus架构.md](09_EventBus架构.md) | TS 事件与渲染线程分离 | 事件定义、合并策略、线程模型 | > **注**:流程文档中的 API 调用通过 `TSBridge` 单例,事件通过 `EventBus` 接收。 > 底层通过 gomobile AAR 调用 Go SDK。详见 [sdk-bridge-api.md](../sdk-bridge-api.md) 和 [09_EventBus架构.md](09_EventBus架构.md)。 ### 横切流程 | 文件 | 说明 | 触发时机 | | --- | --- | --- | | [08_状态同步.md](08_状态同步.md) | 独立于主流程之外的频道/成员/消息状态同步 | 连接建立、事件到达、数据不一致、重新连接时触发 | > 所有流程文档中的 API 调用通过 `TSBridge` 单例,事件通过 `EventBus` 接收。 > 底层通过 gomobile AAR 调用 Go SDK。 --- ## 四、图例 本文将 SDK 行为分成三类: | 类型 | 含义 | Kotlin API 示例 | | --- | --- | --- | | 本地调用 | 只在客户端进程内执行,不直接产生网络通信 | `TsClient.getClientId()`、`TsClient.getSelf()` | | 客户端请求 | 客户端主动向 TeamSpeak 服务器发送命令并等待响应 | `TsClient.connect()`、`TsClient.getChannelList()`、`TsClient.clientMove()` | | 服务端推送 | TeamSpeak 服务器主动下发通知,由 SDK 转换成 EventBus 事件 | `ClientEnter`、`ClientMoved`、`TextMessage` | 所有 Mermaid 图块均使用"技术名称 + 中文注释"的形式。箭头表示调用、数据流或前置依赖,不表示所有步骤都能互换顺序。 --- ## 五、运行架构 ### SDK 内部与上层应用的运行关系 ```mermaid flowchart TB subgraph APP[上层应用层
负责 UI、业务状态和用户意图] UI[界面与交互
发起连接、切换、聊天和 PTT] SM[业务状态机
管理连接、同步、成员和语音状态] STORE[实体与消息存储
保存频道、客户端和聊天事实] end subgraph BRIDGE[桥接层
TSBridge 单例
负责 Kotlin 友好封装与生命周期管理] TSB[TSBridge
connect、getChannelsJSON、moveToChannel 等 API] CB[JNI EventCallback
Go 回调转 EventBus 事件] end subgraph EB[事件总线层
EventBus
负责事件收集、合并与分发] BUS[EventBus
SharedFlow 事件流] MERGE[事件合并器
debounce 高频成员变化] THREAD[线程调度
JNI 线程 → Main 线程] end subgraph SDK[teamspeak-go SDK (gomobile AAR)
负责协议、连接和事件转换] API[Go 公开方法
Connect、GetChannelsJSON、SendVoice 等] CMDMW[命令中间件链
拦截发送前的客户端请求] CMDBUS[命令通道
构建、转义并发送协议命令] EVTBUS[事件分发器
接收并解析 notify* 通知] EVTMW[事件中间件链
拦截分发前的服务端事件] HANDLER[Go 回调 → gomobile 转换
将 Go 结构体转为 Java 对象] VOICE[语音通道
发送原始 Opus 语音帧] FILE[文件传输协调器
初始化上传或下载会话] end subgraph NET[网络与服务器层
负责 TeamSpeak 实际通信] UDP[UDP 协议连接
承载握手、命令、事件和语音] TCP[文件传输 TCP 连接
承载上传和下载数据] SERVER[TeamSpeak 服务器
提供频道、成员、消息和权限] end UI --> SM SM --> TSB TSB --> API API --> CMDMW CMDMW --> CMDBUS CMDBUS --> UDP UDP <--> SERVER SERVER --> UDP UDP --> EVTBUS EVTBUS --> EVTMW EVTMW --> HANDLER HANDLER --> CB CB --> BUS BUS --> MERGE MERGE --> THREAD THREAD --> SM SM --> STORE API --> VOICE VOICE --> UDP API --> FILE FILE --> TCP TCP <--> SERVER ``` ### 三条核心运行通道 **命令通道** ```text TSBridge → gomobile → Go SDK API → CommandMiddleware → 协议命令 → UDP → 服务器响应 ``` 适用:`TSBridge.getChannelsJSON()`、`TSBridge.getClientsJSON()`、`TSBridge.moveToChannel()`、`TSBridge.sendChannelMessage()`、`TSBridge.sendVoice()`。 **事件通道** ```text 服务器 notify* → UDP → Go SDK 解析 → EventMiddleware → gomobile 回调 → TSBridge EventCallback → EventBus → ViewModel → StateFlow → UI ``` 适用:`Connected`、`Disconnected`、`ClientEnter`、`ClientLeave`、`ClientMoved`、`TextMessage`、`Poked`、`Kicked`。 **语音通道(绕过 EventBus)** ```text Go 语音包 → JNI OnVoiceData → VoiceService (Dispatchers.IO) → Opus 解码 → AudioTrack 播放 ``` 语音数据延迟敏感,不经过 EventBus,由 VoiceService 在 IO 线程直接处理。 **文件传输通道** ```text 初始化命令 → 获得 host/port/key → DialFileTransfer → TCP 数据传输 ``` 文件内容不直接复用普通命令通道,初始化结果是建立 TCP 传输连接的前置条件。 ### 中间件执行方向 ```mermaid flowchart LR A[上层业务命令
通过 TSBridge 调用 API] --> B[CommandMiddleware 外层
执行日志、鉴权或限流] B --> C[CommandMiddleware 内层
修改或检查协议命令] C --> D[SDK 命令发送器
编码并发送 TeamSpeak 命令] D --> E[TeamSpeak 服务器
执行命令并返回响应] E --> F[SDK 事件解析器
解析 notify* 服务端通知] F --> G[EventMiddleware 内层
检查或转换原始事件] G --> H[EventMiddleware 外层
记录事件或统一过滤] H --> I[gomobile 回调 → TSBridge
JNI 回调转 EventBus 事件] I --> J[EventBus
合并/节流后分发给 ViewModel] ``` --- ## 六、统一实现原则 1. **先注册事件,再连接。** 避免握手完成后的首批推送无人处理。 2. **先等待连接就绪,再发送业务命令。** `Connect` 不代替 `WaitConnected`。 3. **列表命令建立基线,服务端事件维护增量。** 两者缺一不可。 4. **成员表按 ClientID 幂等更新。** 不使用独立 `+1/-1` 长期维护频道人数。 5. **命令响应和事件事实分离。** 尤其是频道移动,必须等待 `OnClientMoved`。 6. **当前用户通过 ClientID 识别。** 不依赖昵称或 UI 当前页面判断自己。 7. **未知实体引用触发补偿同步。** 不静默忽略移动或离开事件。 8. **消息按 TargetMode 和 Target 归档。** 不使用当前页面频道代替真实目标。 9. **语音只在连接和频道前置条件满足时发送。** 断开或被踢必须立即停止。 10. **文件传输严格遵守"初始化 → TCP 连接 → 数据传输"。** 三个阶段不能跳过。 11. **中间件必须正确调用下一层。** 吞掉命令会导致请求不发送,吞掉事件会导致状态缺失。 12. **断开时统一清理当前会话资源。** 防止旧成员、频道和 Pending 污染下一次连接。 13. **事件通过 EventBus 分发,不在 JNI 回调中直接更新 UI 状态。** JNI 回调只负责 `emit()`,ViewModel 通过 `collect()` 响应。高频成员变化事件 debounce 合并,避免事件风暴。