# 步骤 02：Bridge 层实现（✅ 已完成）

> 实现 Go ↔ Kotlin 桥接层，包括 Go 侧 TSClient 导出、Kotlin 友好封装（TsClient.kt + TsModels.kt）、应用层桥接（TSBridge.kt）。

---

## 一、目标

- [x] Go 侧 TSClient 结构体及方法导出（`go/teamspeak/bridge.go`）
- [x] gomobile 编译为 AAR（`android/app/libs/teamspeak.aar`）
- [x] Kotlin 友好封装层（`TsClient.kt` + `TsModels.kt`）
- [x] 应用层桥接（`TSBridge.kt`）

---

## 二、架构总览

```
┌─────────────────────────────────────────────────────────────┐
│  Kotlin 应用层                                               │
│                                                             │
│  ViewModel ──→ TSBridge (object) ──→ TsClient (object)      │
│                    │                       │                 │
│                    │ TsListener 回调        │ Kotlin 友好 API  │
│                    ↑                       │                 │
│                    └── TsListener ─────────┘                 │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  gomobile 自动生成层                                         │
│                                                             │
│  teamspeak.TSClient (Java) ← Go TSClient struct             │
│  teamspeak.EventCallback (Java) ← Go EventCallback interface│
│  teamspeak.* 数据类 (Java) ← Go 导出结构体                   │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  Go 层                                                      │
│                                                             │
│  bridge.go (TSClient + EventCallback) ──→ teamspeak-go SDK  │
│       │                                         │           │
│       │ 事件队列 + 单消费者 goroutine              │ On* 回调  │
│       └──→ JNI 回调 ──→ EventCallback            │           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

**数据流**：

| 方向 | 路径 | 说明 |
| --- | --- | --- |
| Kotlin → Go | `TSBridge` → `TsClient` → gomobile `TSClient` → Go `Client` | 用户操作 |
| Go → Kotlin | Go `On*` → 事件队列 → JNI → `EventCallback` → `TsClient` 转换 → `TsListener` → `TSBridge` | 事件推送 |

---

## 三、三层架构详解

### 3.1 Go 层（bridge.go）

**文件**：`go/teamspeak/bridge.go`

Go 侧 Bridge 是 gomobile 导出的核心，将 teamspeak-go SDK 封装为可供 Kotlin 调用的 `TSClient` 类。

#### 导出的接口与结构

```go
// EventCallback — gomobile 导出的回调接口
type EventCallback interface {
    OnConnected()
    OnDisconnected(message string)
    OnTextMessage(msg *TextMsg)
    OnClientEnter(client *ServerClientView)
    OnClientLeave(client *ServerClientView)
    OnClientMoved(client *ClientMoved)
    OnKicked(reason *ServerError)
    OnTalkStatusChanged(talker *TalkStatusChange)
    OnClientIDsDone()
    OnServerError(error *ServerError)
}

// TSClient — gomobile 导出的桥接客户端
type TSClient struct {
    client   *teamspeak.Client
    callback EventCallback
}

// 导出的数据结构（适配 gomobile 限制）
type ServerClientView struct { ... }  // 频道成员视图
type ClientInfo struct { ... }        // 完整客户端信息
type ChannelListItem struct { ... }   // 频道列表项
type TextMsg struct { ... }           // 文本消息
type ClientMoved struct { ... }       // 客户端移动事件
type ServerError struct { ... }       // 服务器错误
type TalkStatusChange struct { ... }  // 说话状态变化
type ChannelListResult struct { ... } // 频道列表结果（含数组）
```

#### 关键设计决策

1. **字符串传递 ID**：gomobile 不支持 `uint64` → Kotlin Long 的安全映射，统一用 `string`
2. **结构体包装**：gomobile 不支持 `[]*T`，用 `ChannelListResult` 包装 `ChannelListItem[]`
3. **错误用字符串**：gomobile 不支持 `error` 返回值，用空串=成功
4. **事件队列**：SDK 内部 `evtQueue` + 单消费者 goroutine 串行分发，JNI 回调线程安全

### 3.2 Kotlin 友好封装层（TsClient.kt + TsModels.kt）

**文件**：`android/app/src/main/java/com/tsmobile/app/TsClient.kt`、`TsModels.kt`

这一层将 gomobile 生成的 Java 对象转为 Kotlin 友好接口，解决：
- 所有 API 为 getter/setter 而非属性
- `ChannelListItem[]` 需要手动转 `List<TsChannel>`
- 事件回调参数是 `Object` 类型需要强转
- group 类型是 `Int` 需要映射为 `ChanGroupType` 枚举

#### TsClient 对象

```kotlin
object TsClient {
    // 连接
    fun connect(identity, host, nickname, password, defaultChannel): Boolean
    fun disconnect()

    // 事件监听
    fun setListener(listener: TsListener?)

    // 查询（返回 Kotlin 友好类型）
    fun getChannelList(): List<TsChannel>
    fun getClientList(): List<TsClientInfo>
    fun getClientId(): Long
    fun getClientInfo(clientId: Long): TsClientInfo?
    fun getChannelInfo(channelId: Long): TsChannel?
    fun getSelf(): TsSelf
    fun getChannelIdsByUid(uid: String, maxDepth: Int): List<Long>

    // 属性
    val serverVersion: String
    val serverIp: String
    val serverPlatform: String
    val serverName: String
    val serverCreated: Long
    val serverUptime: Long
    val maxClients: Int
    val clientsOnline: Int
    val channelsOnline: Int

    // 操作
    fun sendTextMessage(targetMode: Int, targetId: Long, msg: String): Boolean
    fun clientMove(clientId: Long, channelId: Long, password: String): Boolean
    fun clientPoke(clientId: Long, msg: String): Boolean
    fun kickClient(clientId: Long, reasonId: Int, reasonMsg: String): Boolean
    fun banClient(uid: String, timeInSeconds: Long, reasonMsg: String): Boolean
    fun channelCreate(name: String, properties: Map<String, String>, permissions: List<TSPermission>): Long
    fun channelUpdate(channelId: Long, properties: Map<String, String>): Boolean
    fun channelDelete(channelId: Long, force: Boolean): Boolean
    fun sendVoice(clientId: Long, codec: Int, data: ByteArray): Boolean
}
```

#### TsListener 接口

```kotlin
interface TsListener {
    fun onConnected()
    fun onDisconnected(error: String?)
    fun onClientEnter(client: TsClientInfo)
    fun onClientMoved(moved: TsClientMoved)
    fun onClientLeave(client: ServerClientView)
    fun onTalkStatusChanged(talker: TsTalker)
    fun onClientIDsDone()
    fun onTextMessage(msg: TsTextMessage)
    fun onPoked(msg: TsTextMessage)
    fun onKicked(reason: ServerError?)
    fun onServerError(error: ServerError)
    fun onChannelListChanged()
}
```

#### TsModels 数据类

```kotlin
// 频道
data class TsChannel(val channelListItem: ChannelListItem) {
    val channelId get() = channelListItem.getChannelID()
    val parentId get() = channelListItem.getParentChannelID()
    val name get() = channelListItem.getName()
    val order get() = channelListItem.getOrder()
    val isPermanent get() = channelListItem.getIsPermanent()
    val totalClients get() = channelListItem.getTotalClients()
    // ... 更多属性
}

// 客户端
data class TsClientInfo(val serverClientView: ServerClientView) {
    val clientId get() = serverClientView.getID()
    val channelId get() = serverClientView.getChannelID()
    val nickname get() = serverClientView.getNickname()
    val uid get() = serverClientView.getUid()
    val isTalker get() = serverClientView.getIsTalker()
    // ... 更多属性
}

// 自身信息（可写）
class TsSelf internal constructor(
    private val view: ServerClientView,
    private val info: ClientInfo?
) {
    var nickname
        get() = view.getNickname()
        set(value) { info?.setNickname(value) }
    var isTalker
        get() = view.getIsTalker()
        set(value) { info?.setIsTalker(value) }
    var inputMuted
        get() = info?.getInputMuted() ?: false
        set(value) { info?.setInputMuted(value) }
    var outputMuted
        get() = info?.getOutputMuted() ?: false
        set(value) { info?.setOutputMuted(value) }
    // ... 更多可写属性
}

// 移动事件
data class TsClientMoved(
    val theClient: ServerClientView?,
    val oldChannel: ChannelListItem?,
    val newChannel: ChannelListItem?,
    val visibility: Int
)

// 说话状态
data class TsTalker(
    val client: ServerClientView?,
    val isTalking: Boolean
)

// 文本消息
data class TsTextMessage(
    val message: String,
    val invokerUid: String,
    val invokerName: String,
    val invokerId: Long,
    val targetMode: Int,
    val targetClientId: Long,
    val targetChannelId: Long
)

// 频道组/服务器组类型
enum class ChanGroupType(val value: Int) { ... }
enum class ChannelGroupType(val value: Int) { ... }
```

### 3.3 应用层桥接（TSBridge.kt）

**文件**：`android/app/src/main/java/com/tsmobile/app/TSBridge.kt`

TSBridge 是 ViewModel 层访问 Go 桥接的唯一入口，采用 `object` 单例模式。

#### 核心职责

1. **连接管理**：封装 `TsClient.connect()` / `disconnect()`
2. **监听注册**：在 `connect()` 时自动设置 `TsListener`
3. **查询转发**：所有查询方法委托给 `TsClient`
4. **操作转发**：所有操作方法委托给 `TsClient`

#### 完整 API

```kotlin
object TSBridge {
    // === 连接管理 ===
    fun connect(identity: Any, host: String, nickname: String,
                password: String = "", defaultChannel: String = "",
                listener: TSBridgeListener? = null): Boolean
    fun disconnect()

    // === 查询 ===
    fun getChannelList(): List<TsChannel>
    fun getClientList(): List<TsClientInfo>
    fun getClientId(): Long
    fun getSelf(): TsSelf
    fun getChannelInfo(channelId: Long): TsChannel?
    fun getClientInfo(clientId: Long): TsClientInfo?
    fun getChannelIdsByUid(uid: String): List<Long>

    // === 操作 ===
    fun sendTextMessage(targetMode: Int, targetId: Long, msg: String): Boolean
    fun clientMove(clientId: Long, channelId: Long, password: String = ""): Boolean
    fun clientPoke(clientId: Long, msg: String): Boolean
    fun kickClient(clientId: Long, reasonId: Int, reasonMsg: String): Boolean
    fun banClient(uid: String, timeInSeconds: Long, reasonMsg: String): Boolean
    fun channelCreate(name: String, properties: Map<String, String> = emptyMap(),
                      permissions: List<TSPermission> = emptyList()): Long
    fun channelUpdate(channelId: Long, properties: Map<String, String>): Boolean
    fun channelDelete(channelId: Long, force: Boolean = false): Boolean
    fun sendVoice(clientId: Long, codec: Int, data: ByteArray): Boolean

    // === 服务器属性 ===
    val serverVersion: String
    val serverIp: String
    val serverPlatform: String
    val serverName: String
    // ...
}

// 回调接口（简化版，供 ViewModel 使用）
interface TSBridgeListener {
    fun onConnected()
    fun onDisconnected(error: String?)
    fun onClientEnter(client: TsClientInfo)
    fun onClientMoved(moved: TsClientMoved)
    fun onClientLeave(client: ServerClientView)
    fun onTalkStatusChanged(talker: TsTalker)
    fun onClientIDsDone()
    fun onTextMessage(msg: TsTextMessage)
    fun onPoked(msg: TsTextMessage)
    fun onKicked(reason: ServerError?)
    fun onServerError(error: ServerError)
}
```

---

## 四、数据流转详解

### 4.1 连接流程

```
ViewModel: TSBridge.connect(identity, host, nickname, password)
    ↓
TSBridge: TsClient.connect(identity, host, nickname, password)
    ↓
TsClient: TSClient.connect(...)  // gomobile Java 对象
    ↓
Go: TSClient.Connect() → NewClient → registerHandlers → Connect → WaitConnected
    ↓
Go SDK: 收到 welcome → 触发 OnConnected
    ↓
Go Bridge: EventCallback.OnConnected()  // JNI
    ↓
TsClient: listener?.onConnected()  // Kotlin 友好回调
    ↓
TSBridge: listener?.onConnected()
    ↓
ViewModel: _state.update { it.copy(connected = true) }
```

### 4.2 查询流程

```
ViewModel: TSBridge.getChannelList()
    ↓
TSBridge: TsClient.getChannelList()
    ↓
TsClient: TSClient.requestChannelList()  // gomobile
    ↓
TsClient: result.getChannels().map { TsChannel(it) }  // 转为 Kotlin 类型
    ↓
TSBridge: 返回 List<TsChannel>
    ↓
ViewModel: _state.update { it.copy(channels = list) }
```

### 4.3 事件推送流程

```
TeamSpeak 服务器: notifyclientmoved
    ↓
Go SDK: handleNotification → evtQueue → startEventLoop
    ↓
Go Bridge: EventCallback.OnClientMoved(clientMoved)
    ↓ JNI
TsClient: TsListener.onClientMoved(TsClientMoved(view, old, new, vis))
    ↓
TSBridge: listener?.onClientMoved(moved)
    ↓
ViewModel: 处理移动事件，更新状态
```

---

## 五、gomobile 约束与应对

| 约束 | 影响 | 应对方案 |
| --- | --- | --- |
| 不能导出 `[]string` | 服务器组列表无法直接传递 | `TsClientInfo` 封装为逗号分隔字符串 |
| 不能导出 `[]*T` | 频道/客户端列表无法返回切片 | `ChannelListResult` 包装 + `TsClient` 转 `List` |
| 不能导出 `map[string]string` | 属性列表无法传递 | `channelCreate`/`channelUpdate` 接受 `Map`，内部转 gomobile 类型 |
| 不能导出 `error` | 方法无法返回错误 | `Boolean` 返回值（true=成功） |
| `uint64` 映射为 `long` | 频道 ID 可能溢出 | 统一用 `string` 传递 ID |
| 回调在 JNI 线程 | 不能直接操作 UI | `TsListener` 回调 → ViewModel + StateFlow 中转 |
| 所有字段为 getter/setter | Kotlin 不友好 | `TsModels` 包装为 `val`/`var` 属性 |

---

## 六、文件清单

| 文件 | 说明 |
| --- | --- |
| `go/teamspeak/bridge.go` | Go 侧桥接，导出 TSClient + EventCallback |
| `android/app/libs/teamspeak.aar` | gomobile 编译产物 |
| `android/app/src/main/java/com/tsmobile/app/TsClient.kt` | Kotlin 友好封装（TsClient 对象） |
| `android/app/src/main/java/com/tsmobile/app/TsModels.kt` | Kotlin 数据类（TsChannel, TsClientInfo 等） |
| `android/app/src/main/java/com/tsmobile/app/TSBridge.kt` | 应用层桥接（单例） |

---

## 七、验收标准

| # | 验证项 | 验证方法 |
| --- | --- | --- |
| 1 | bridge.go 可编译 | `cd go && go build ./teamspeak` 无报错 |
| 2 | gomobile 生成 AAR | `gomobile bind ...` 成功 |
| 3 | TsClient.connect() 可调用 | 编译通过，连接测试服务器成功 |
| 4 | TsListener 回调正常 | 连接后收到 `onConnected`、`onClientEnter` 等 |
| 5 | 查询返回 Kotlin 类型 | `getChannelList()` 返回 `List<TsChannel>` |
| 6 | 操作方法正常 | `sendTextMessage`、`clientMove` 等返回 true |
| 7 | 断开连接无崩溃 | `disconnect()` 后应用正常退出 |

---

## 八、参考文档

- `docs/sdk-bridge-api.md` — TsClient API 完整参考
- `docs/流程/00_总览.md` — 运行架构、三条核心通道
- `docs/流程/01_连接服务器.md` — 连接时序、事件依赖
- `CLAUDE.md` — gomobile 限制说明、JNI 线程注意事项
