← API | 列表 | IM规范
提示信息
# 同伴X IM 系统规范与信号对接指南 (SSOT)

> 本文档为同伴X **聊天(IM)系统**与**信号系统**对接的唯一真理之源。涵盖 UI 规范、API 边界、消息结构、信号转换机制及历史兼容机制。所有涉及会话与聊天界面的迭代,必须以本文档为准。
>
> **最后更新**:2026-03-17

---

## 一、 职责边界:PHP 后台 vs OpenIM SDK

### 1. 必须走 PHP 业务后台的接口
- **发送消息 (`/chat/message/send`)**:客户端 **严禁** 直接调 OpenIM 的 `sendMsg`。所有消息必须经 PHP 端代发,以进行风控、计费及关系校验。
- **获取 OSS Media 签名 URL (`/upload/oss/sign`)**:多媒体文件通过返回的 `objectKey` 调用 PHP 换取有时效的 CDN 地址。
- **获取 IM Token (`/auth/im/token`)**:登录时获取长连接凭证。

### 2. 直连 OpenIM SDK 的动作
- **监听长链接事件**:新消息推送、会话已读通知、对方正在输入状态。
- **拉取历史**:`getAdvancedHistoryMessageList` 直接拉取本地或云端消息。
- **会话管理**:置顶、删除、清空未读数等本地会话状态维护。

---

## 二、 消息发送业务规则

### 2.1 关系链与黑名单
- **拦截逻辑**:若发送者在接收者的黑名单中,接口返回 `4002`。
- **隐私设置**:隐身模式(`is_profile_hidden`)影响在线状态展示。

### 2.2 陌生人聊天(打招呼)限制
- **解锁机制**:在对方未回复前,最多连续发送 **2条** 消息。只要对方回复一条,限制即永久解除。
- **每日限额**:每人每天最多向 10 个不同的陌生人发起聊天。

---

## 三、 信号与 IM 对接逻辑

当用户从信号页面开启聊天时,流程如下:

### 1. 上下文构建
跳转聊天页时需构造 `Conversation` 对象,携带 `fromSignal: true`、`signalId`、`replyId` 及原始回复内容。

### 2. 首条消息处理(后端自动双发)

> **⚠️ 重要变更**:前端**只需调用一次** `/chat/message/send`,在 `ex` 字段携带 `signal_quote` 上下文,后端自动完成发两条消息 + 状态更新。无需再调用 `/signal/reply/collect`(该接口已删除)。

**前端调用示例:**
```json
POST /v1/chat/message/send
{
  "recvID": "100001",
  "contentType": 101,
  "content": "我也想去",
  "ex": "{\"type\":\"signal_quote\",\"signalId\":10,\"replyId\":88,\"replyContent\":\"我也想去\",\"signalContent\":\"今晚一起打球\"}"
}
```

**后端自动执行:**
1. 发送 `contentType: 110` 的信号引用卡片(`type: signal_quote`)。
2. 发送 `contentType: 101` 的文字消息(内容取请求根节点的 `content`)。
3. 将对应 `signal_replies.is_im_opened` 置为 `true`(通过 `replyId`)。

**响应额外返回:**
```json
{
  "clientMsgID": "文字消息ID",
  "quoteClientMsgID": "引用卡片消息ID",
  "serverMsgID": "..."
}
```

### 3. 技术实现细节
- **数据预注入 (`injectUser`)**:在跳转 `ChatPage` 前,应调用 `imUserInfoProvider.injectUser`,利用信号页已有的头像/昵称即时渲染,避免 SDK 数据未同步时出现头像闪烁。

### 4. UI 表现
- **上下文卡片**:在首条消息发送前,输入框上方会悬浮显示信号上下文提示卡片。
- **引用气泡**:`signal_quote` 类型消息以居中、带边框的系统纸条样式渲染。


---

## 四、 详尽消息结构 specs (Payload Specification)

IM 系统支持多种 `contentType`,业务层主要通过 `110 (Custom)` 实现扩展。

| 场景 | `contentType` | `content` 结构 / Payload | UI 表现 |
| :--- | :--- | :--- | :--- |
| **纯文本** | 101 | `"文本内容"` 或 `{"text": "..."}` | 标准气泡 |
| **图片** | 110 | `{"type":"image", "objectKey":"...", "url":"...", "w":800, "h":600}` | 图片预览 |
| **闪照** | 110 | `{"type":"flash_image", "objectKey":"...", "url":"...", "mime":"..."}` | 模糊遮罩,点击查看 |
| **语音** | 110 | `{"type":"audio", "objectKey":"...", "url":"...", "durationSec":10}` | 语音波纹进度条 |
| **多图** | 110 | `{"type":"multi_image", "multiObjectKeys": [...], "multiUrls": [...]}` | 九宫格/拼图 |
| **信号引用** | 110 | `{"type":"signal_quote", "signalId":1, "replyContent":"...", "signalContent":"..."}` | 居中系统卡片 |
| **撤回/系统** | 2101 | [SDK 内部对象] | 居中灰色小字 |

---

## 五、 后端 API 规格 (`POST /v1/chat/message/send`)

### 1. 请求参数
- `recvID`: 接收方 UID。
- `contentType`: 101 或 110。
- `content`: 见上表结构。
- `ex`: JSON 字符串,例如 `{"reply_id": 123}` 用于转换信号状态。

### 2. 业务错误码
- `4001`: 内容违规拦截。
- `4002`: 被对方拉黑。
- `4003`: 陌生人打招呼受限(超过2条)。
- `4004`: 每日打招呼名额耗尽。

---

## 六、 UI 交互与视觉标准

- **气泡颜色**:发送方 `context.themeAccent`,接收方 `context.themeCard`。
- **头像跳转**:点击头像统一跳转至 `/user/profile`。
- **菜单功能**:右侧 `ActionSheet` 包含:置顶、拉黑、清空记录、举报、查看主页。

---

## 七、 页面索引
- `messages_page.dart`: 会话列表。
- `chat_page.dart`: 聊天详情主容器。
- `chat_controller.dart`: 消息发送与同步核心逻辑。
- `chat_bubble.dart`: 各类消息气泡渲染。

---

> [!IMPORTANT]
> **多媒体签名机制**:
> 所有包含 `objectKey` 的自定义消息,后端在 `/chat/message/send` 返回及 SDK 抄送时,会自动将其中的 `url` 字段填充为带签名的有效链接。前端应优先使用 `url` 进行展示,若失效则应调用 `/upload/oss/sign` 重新签名。