# 流程文档总览

> 本文档按用户流程组织，将 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[上层应用层<br/>负责 UI、业务状态和用户意图]
        UI[界面与交互<br/>发起连接、切换、聊天和 PTT]
        SM[业务状态机<br/>管理连接、同步、成员和语音状态]
        STORE[实体与消息存储<br/>保存频道、客户端和聊天事实]
    end

    subgraph BRIDGE[桥接层<br/>TSBridge 单例<br/>负责 Kotlin 友好封装与生命周期管理]
        TSB[TSBridge<br/>connect、getChannelsJSON、moveToChannel 等 API]
        CB[JNI EventCallback<br/>Go 回调转 EventBus 事件]
    end

    subgraph EB[事件总线层<br/>EventBus<br/>负责事件收集、合并与分发]
        BUS[EventBus<br/>SharedFlow 事件流]
        MERGE[事件合并器<br/>debounce 高频成员变化]
        THREAD[线程调度<br/>JNI 线程 → Main 线程]
    end

    subgraph SDK[teamspeak-go SDK (gomobile AAR)<br/>负责协议、连接和事件转换]
        API[Go 公开方法<br/>Connect、GetChannelsJSON、SendVoice 等]
        CMDMW[命令中间件链<br/>拦截发送前的客户端请求]
        CMDBUS[命令通道<br/>构建、转义并发送协议命令]
        EVTBUS[事件分发器<br/>接收并解析 notify* 通知]
        EVTMW[事件中间件链<br/>拦截分发前的服务端事件]
        HANDLER[Go 回调 → gomobile 转换<br/>将 Go 结构体转为 Java 对象]
        VOICE[语音通道<br/>发送原始 Opus 语音帧]
        FILE[文件传输协调器<br/>初始化上传或下载会话]
    end

    subgraph NET[网络与服务器层<br/>负责 TeamSpeak 实际通信]
        UDP[UDP 协议连接<br/>承载握手、命令、事件和语音]
        TCP[文件传输 TCP 连接<br/>承载上传和下载数据]
        SERVER[TeamSpeak 服务器<br/>提供频道、成员、消息和权限]
    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[上层业务命令<br/>通过 TSBridge 调用 API] --> B[CommandMiddleware 外层<br/>执行日志、鉴权或限流]
    B --> C[CommandMiddleware 内层<br/>修改或检查协议命令]
    C --> D[SDK 命令发送器<br/>编码并发送 TeamSpeak 命令]
    D --> E[TeamSpeak 服务器<br/>执行命令并返回响应]

    E --> F[SDK 事件解析器<br/>解析 notify* 服务端通知]
    F --> G[EventMiddleware 内层<br/>检查或转换原始事件]
    G --> H[EventMiddleware 外层<br/>记录事件或统一过滤]
    H --> I[gomobile 回调 → TSBridge<br/>JNI 回调转 EventBus 事件]
    I --> J[EventBus<br/>合并/节流后分发给 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 合并，避免事件风暴。
