# 切换频道

> 用户流程：选择目标频道 → （频道有密码时输入密码） → 发送切换请求 → 等待服务端确认 → 进入目标频道
> 对应程序流程：TsClient.clientMove(selfID, targetID, password) → 等待 onClientMoved 服务端事实 → 比对 clientID → 提交新频道

---

## 一、时序：当前用户切换频道

```mermaid
sequenceDiagram
    participant UI as 频道选择 UI<br/>表达用户切换意图
    participant FSM as 切换状态机<br/>管理确认、请求和等待事实
    participant BRIDGE as TSBridge<br/>调用 TsClient API
    participant SDK as Go SDK (gomobile)<br/>发送 ClientMove 命令
    participant TS as TeamSpeak 服务器<br/>验证权限、密码并执行移动
    participant EVT as EventBus<br/>处理 ClientMoved 服务端事实
    participant STORE as 当前用户状态<br/>保存已确认频道

    UI->>FSM: ChannelJoinRequested(targetID)
    FSM->>STORE: 读取 confirmedChannelID
    FSM-->>UI: 显示确认或密码输入
    UI->>FSM: ChannelJoinConfirmed(password)
    FSM->>FSM: 状态改为 requesting
    FSM->>BRIDGE: clientMove(selfID, targetID, password)
    BRIDGE->>SDK: gomobile ClientMove
    SDK->>TS: clientmove

    alt 命令被服务器拒绝
        TS-->>SDK: error
        SDK-->>BRIDGE: 命令失败
        BRIDGE-->>FSM: 返回错误
        FSM->>FSM: 状态改为 failed
        FSM-->>UI: 显示失败原因
    else 命令请求成功
        TS-->>SDK: command ok
        SDK-->>BRIDGE: 命令成功
        BRIDGE-->>FSM: 返回成功（不代表状态已提交）
        FSM->>FSM: 状态改为 waitingServerEvent
        TS-->>SDK: notifyclientmoved
        SDK->>EVT: onClientMoved(TsClientMoved)
        EVT->>STORE: 比对 clientID == selfID
        EVT->>STORE: 提交 channelID = targetID
        STORE-->>FSM: SelfMoved(targetID)
        FSM->>FSM: 状态恢复 idle
        FSM-->>UI: 导航到目标房间
    end
```

---

## 二、事件依赖

### 频道切换前置依赖矩阵

| 操作或事件 | 必须依赖 | 建议依赖 | 依赖失败时的处理 |
| --- | --- | --- | --- |
| `TsClient.clientMove` | 已连接、目标频道 ID、目标客户端 ID | 已同步频道和成员 | 命令失败保留原频道事实 |
| 提交自己新频道 | 自己的 `onClientMoved` | 匹配目标频道和请求上下文 | 命令响应不能直接提交频道 |

### 命令响应与事件事实的区别

```mermaid
flowchart LR
    INTENT[用户意图<br/>例如发送消息或切换频道] --> COMMAND[客户端请求<br/>调用 SDK 命令方法]
    COMMAND --> RESPONSE{命令响应<br/>判断服务器是否接受请求}
    RESPONSE -- 失败 --> FAILED[请求失败<br/>保留原事实并展示原因]
    RESPONSE -- 成功 --> WAITFACT[等待事实事件<br/>仅对存在对应推送的操作适用]
    WAITFACT --> EVENT[服务端推送事件<br/>例如 OnClientMoved 或 OnTextMessage]
    EVENT --> COMMIT[提交服务器事实<br/>更新频道位置或消息实体]

    RESPONSE -- 无对应事实事件 --> LOCALDONE[完成本次调用<br/>仅记录调用结果而不猜测远端效果]
```

**关键区分：**

- `TsClient.clientMove` 返回成功：命令执行成功；当前用户频道应由自己的 `onClientMoved` 最终确认。
- 命令成功 ≠ 状态已提交。必须等待服务端推送的 `onClientMoved` 事件才能更新本地频道事实。

### 权威性划分

| 数据 | 推荐权威来源 | 原因 |
| --- | --- | --- |
| 当前用户频道 | 自己的 `onClientMoved` 事件 | 命令响应只表示服务器接受请求，不表示状态已生效 |
