
# 客户端与接入模型

*一套统一模型接入所有终端：硬件设备、手机/电脑 App、网页都是「客户端」，用同一种身份 Token 连接，房间由平台分配*

Hearo 把所有外部参与者统一抽象成 **客户端（Client）**。无论是 IoT 硬件、手机 App、电脑软件还是网页，接入方式都一样：拿一个**客户端身份 Token** 连上来，进入哪个**房间**由平台的 **客户端 → 房间映射**决定。本页讲清整套模型与端到端开发流程——面向**用户**（控制台操作）、**方案商**（设备接入）、**开发者**（App/固件集成）。

## 核心概念

| 概念 | 持有方 | 说明 |
|---|---|---|
| 客户端 Client | — | 任意外部参与者（hardware/app/web）。有唯一 clientId、可分配房间、有在线状态。 |
| 工作空间 API Key | 服务端持有 | 在控制台「API Key」创建。用于服务端调用管理接口、签发客户端 Token。绝不下发到设备。 |
| 客户端 Token | 客户端持有 | 只证明「你是哪个客户端」，不含房间。用它连接 MQTT、WSS 或 WebRTC（实验）。可有有效期或长期。 |
| 客户端→房间映射 | 平台维护 | 每个客户端有一个房间号。改它即切换房间（在线秒切）。多个客户端同房间 = 群聊/通话。 |
| 在线状态 presence | 连接生命周期 | 连上=在线、断开=离线。即使设备静默也能知道在不在线。 |

> ℹ️ **两层凭据，别混淆**：① 工作空间 API Key（强权限，服务端）：管理客户端 + 签发 Token，放在你的后端。② 客户端 Token（仅身份，设备端）：用来建立连接，由你的后端用 API Key 现签发给设备。设备**只**拿到 Token，**永不**持有 API Key。

## 端到端接入流程

典型方案商/开发者集成步骤（服务端用 API Key，设备只拿 Token）：

```text
① 控制台 → API Key → 创建一把（只显示一次，复制保存）
② 你的后端：用 API Key 创建客户端           POST /v1/clients
③ 你的后端：为该客户端签发 Token             POST /v1/clients/{id}/token
④ 把 Token 下发到设备/App/网页
⑤ 设备用 Token 连接（MQTT/WSS/WebRTC（实验））→ 自动进入它绑定的房间
⑥ 需要时：改客户端房间号即可实时切房          PATCH /v1/clients/{id}
   在线状态由连接自动维护（presence）
```

### 第 1 步 · 创建 API Key（控制台）

控制台 → **API Key** → 创建。密钥形如 `hk_xxx`，**只显示一次**，保存到你的后端环境变量。后续所有管理调用用 `Authorization: Bearer hk_xxx`。

### 第 2 步 · 创建客户端 + 签发 Token（服务端）

```bash
API=https://hearo.apps.aisp24.com   # 本部署的 API 基址
KEY=hk_xxxxxxxx         # 工作空间 API Key

# 1) 创建一个客户端（kind: hardware|app|web；room 可选，先留空也行）
curl -s -X POST $API/api/v1/clients \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"clientId":"M4T62816063057","kind":"hardware","name":"门口设备","room":"lobby"}'
# → {"client":{"id":"ckd...","clientId":"M4T62816063057","room":"lobby",...}}

# 2) 用上一步返回的 id 签发 Token
#    省略 ttlSeconds = 长期 Token（写死固件）；填了 = 有效期 Token（到期前续）
curl -s -X POST $API/api/v1/clients/ckd.../token \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"ttlSeconds":2592000}'
# → {"token":"<CLIENT_TOKEN>","clientId":"M4T62816063057","expiresAt":"..."}
```

> 💡 **网页/App 一步直连**：临时场景（网页访客、App 会话）不必预建客户端：一次调用拿到「临时客户端 + 房间 + Token」，会话结束自动回收。

```bash
curl -s -X POST $API/api/v1/clients/ephemeral \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"room":"demo-1","kind":"web","ttlSeconds":3600}'
# → {"token":"<CLIENT_TOKEN>","clientId":"eph_...","room":"demo-1","expiresAt":"..."}
```

### 第 3 步 · 设备用 Token 连接

同一个 Token 适用于三种 IP 传输，连上即进入该客户端绑定的房间：

| 传输 | 本部署地址 | 说明 |
|---|---|---|
| MQTT | `124.174.6.145:2883` | password=Token 或**设备接入密码**。见 [MQTT 接入](/docs/mqtt) / [aispea 接入](/docs/aispea-mqtt)。 |
| WebSocket | `wss://hearo-gw.apps.aisp24.com/v1/connect?token=…` | 见 [WebSocket](/docs/websocket)。 |
| WebRTC（实验） | `POST https://hearo-gw.apps.aisp24.com/v1/webrtc/offer`，请求头 `Authorization: Bearer <Token>` | 见 [WebRTC（实验）](/docs/webrtc)。 |

> 💡 **MQTT 设备的第二种凭据**：固件密码字段放不下 JWT？在 控制台 → 设备 页给设备生成**设备接入密码**（`hkd_` 开头的短密码，可反复查看、可一键重置立即失效），MQTT 连接用它当 password 即可，等价于该设备的身份凭据。设备页也能直接「生成 Token」，无需走 API。

> ⚠️ **务必用 TLS**：WSS 的 Token 使用连接 query 参数，WebRTC（实验）的 Token 只能使用 `Authorization: Bearer` 请求头，MQTT 使用 password。WSS 与 WebRTC（实验）**必须走 TLS**（wss/https）。MQTT broker 默认为明文 TCP，生产建议部署侧加 TLS 终结，或用可随时重置的设备接入密码降低泄露影响。

### 第 4 步 · 房间与分组（切房 / 群聊）

房间由客户端的 `room` 字段决定，**不在 Token 里**。所以可以在设备保持在线的情况下随时切换房间——改映射即可，无需重连：

```bash
# 把设备移到另一个房间（在线秒切）
curl -s -X PATCH $API/api/v1/clients/ckd... \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"room":"meeting-2"}'

# 群聊/通话：把多台设备的 room 设成同一个值，它们就在一个房间里实时互通
```

控制台 → **房间 / 分组** 页可视化地批量分配房间；改动对在线设备实时生效。房间还能登记成**受管房间**并配置默认 AI 智能体、用「网页进入」一键测试、生成**匿名分享链接**——见 [房间与分组](/docs/rooms)。

## 管理 API 参考

全部用工作空间 API Key 鉴权（`Authorization: Bearer hk_xxx`）。

- `GET /api/v1/clients` — 列出客户端，支持查询参数 `room` / `kind` / `status` 筛选。
- `POST /api/v1/clients` — 创建/登记客户端。Body：`{clientId, name?, kind?, room?}`。
- `GET /api/v1/clients/{id}` — 详情（含在线状态）。
- `PATCH /api/v1/clients/{id}` — 改名 / 设置房间（设置房间 = 实时切房）。Body：`{name?, room?, kind?}`。
- `DELETE /api/v1/clients/{id}` — 删除。
- `POST /api/v1/clients/{id}/token` — 签发客户端 Token。Body：`{ttlSeconds?}`——省略=长期。
- `POST /api/v1/clients/ephemeral` — 一步创建临时客户端 + 绑房间 + 返回 Token。Body：`{room, kind?, ttlSeconds?}`，ttlSeconds 默认 3600（1 小时）。

## 在线状态（presence）

连接建立即标记客户端 **在线**，断开/超时即 **离线**（由网关在连接生命周期自动上报）。在 控制台 → 设备 / 房间分组 页可看到 ONLINE/OFFLINE 与最近在线时间。这套对 MQTT、WSS 与 WebRTC（实验）通用。
