# wecloud-im 前端Websocket对接文档

## 文档描述

本项目有两个文档: websocket对接文档 和 HTTP协议的API文档

**本文档为websocket对接文档** 

HTTP协议的API以Swagger实时生成为准



## HTTP协议API文档

Swagge文档地址:

**本地环境 api文档(实时更新 开发查看此文档):**
IP可能有变化  联系后端

```
http://192.168.1.89/api/doc.html#/home
```
账号密码admin admin

**测试外网 api文档:**

```
https://wstest.im199.com/api/doc.html#/home
```

只包含api接口文档  websocket对接说明在此文档中


## 适用对象

 web端. 安卓端. ios端. flutter等一切支持websocket的语言

## 核心概念说明

### clientId、用户和登录

即时通讯服务中的每一个终端称为一个「Client」。Client 拥有一个在应用内唯一标识自己的 ID（`clientId`）。这个 ID 由应用自己定义，必须是 **由任意英文字母、数字、半角下划线与半角短横线组成，可以纯数字，不超过 64个字符的字符串组成**。在大部分场合，Client 都可以对应到应用中的某个「用户」，但是并不是只有真的用户才能作为「Client」，你完全可以把一个探测器当成一个「Client」，把它收集到的数据通过即时通讯服务广播给更多「人」。

要使用即时通讯服务，每一个终端设备需要首先建立与即时通讯云服务端的 WebSocket 长连接，并使用唯一的 `clientId` 来加入即时通讯服务，我们把这一过程称为「登录」。请注意这里的登录仅仅指客户端登录即时通讯服务，与应用层面的用户账户注册登录是不一样的。

### appKey与appSecret

appKey为应用在蔚可云的唯一标识, appSecret为安全密钥, 均由蔚可云控制台或联系客服下发,给应用接入方安全验证密钥,生产环境appSecret不建议保存在接入方前端客户端, appKey与appSecret总是成对出现和使用

### 生成sign签名

sign 由接入方后端生成, 进行加签的参数:  MD5{timestamp + clientId + appKey + appSecret} 
timestamp:毫秒时间戳
clientId:由后端生成

**在生产环境中，你需要自行部署服务器签发 sign**

### 对话（Conversation）

用户登录之后，与其他人(client)进行消息沟通，即为开启了一个「对话（Conversation）」。在即时通讯服务中，「对话」包含了沟通的用户群体（成员），也是所有消息依托的媒介：消息都是由某一个 Client 发往一个「对话」。终端用户在开始聊天之前，需要先创建或者加入一个对话，然后再邀请其他人进来（可选），之后所有参与者在这个对话内进行交流。

### 已读/已接收

**已接收**为客户端接收到该消息,已经保存到本地缓存中,用户还未打开会话查看该条消息的情况. 服务器将不会再次下发已接收状态的离线消息,未接收的消息会在拉取离线消息时返回

**已读**为客户端已经查看该消息

## ping / pong心跳机制

1. 客户端接收到服务端下发"ping"字符串时, 必须立即回应"pong"字符串;

2. 客户端可完善定时向服务端发起"ping"字符串,来保持websocket连接状态, 服务端会立即回应"pong"字符串,如果连续3次未及时响应"pong",即可认定websocket已经断开,进入重新连接流程;
3. 建议客户端发"ping"间隔时间为5秒



## 客户端连接流程

![wc_im_client连接流程-Page-1](https://tva1.sinaimg.cn/large/008i3skNly1gvbhmdzg3kj60zt0u0juf02.jpg)

1. appKey, appSecret为蔚可云下发给客户方安全保护密钥,不建议保存在前端客户端;
2. 第三方应用服务端需提供获取sign的接口, sign 由MD5{timestamp + clientId + appKey + appSecret},其中clientId由后端生成;
3. 前端拿到sign后,调用验证sign接口进行获取token;
4. websocket连接初始化需要带上token即可连接成功;
5. sign 需要在你的应用服务端生成;

### 第三方应用后端生成sign接口示例

本文展示如何在服务端部署一个 sign 生成器。

 **java**示例代码 ,供客户应用后端参考

```java
import org.springframework.util.DigestUtils;
private void getSign(String timestamp, String clientId, String appKey, String appSecret) {
  
  String data = timestamp + clientId + appKey + appSecret;
	String sign = DigestUtils.md5DigestAsHex(data.getBytes());
}

public static void main(String[] args)   {
  String clientId = "client_123123";
  String appKey = "elLwpel1gWCHDqZy";
  String appSecret = "68809bb5a9077a83631aeb0b17b5965d6b2302faf2ab3737";
  String timestamp = String.valueOf(new Date().getTime());
  getSign(timestamp, clientId, appKey, appSecret);
}
```

第三方应用后端需要响应的参数:

```json
{
	"timestamp": "1628838135066",
	"clientId": "client_3334444",
	"appKey": "D13ug9jsWbJbeVx1",
	"sign": "c15a886fe4114dba2c8f078369e6bec9"
}
```

### 连接WebSocket

ws://localhost:8899/ws?token=xxxxxx&platform=1

示例:

```text
ws://localhost:8899/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImhhaGFoXzMwIiwiaXNzIjoid2VjbG91ZF9pbSIsImFwcEtleSI6ImVsTHdwZWwxZ1dDSERxWnkiLCJleHAiOjE2MjkwOTY0MzksImlhdCI6MTYyMDQ1NjQzOSwianRpIjoiNDA1YzE3MWM2Njc5NGJmMDllNGRjZDdhNzA0ZjY3YTgifQ.7g2J_0q9UnuWszpuapSJUXJEwVevvI8Rm2Srg3594Lk&platform=1
```



#### 请求参数

| 字段名   | 字段类型 | 是否可空 | 说明                                  |
| -------- | -------- | -------- | ------------------------------------- |
| token    | String   | 否       | token                                 |
| platform | int      | 是       | 设备平台类型:  1 安卓;  2 ios;  3 web |

#### 不同环境连接

**本地环境**

```
ws://192.168.1.89:8899/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImFhYWFhMyIsImlzcyI6IndlY2xvdWRfaW0iLCJhcHBLZXkiOiJRTnRQM0VqdEx3MjZla3QwIiwiZXhwIjoxNjc2NjYzNTkyLCJpYXQiOjE2MjY2ODQ1ODQsImp0aSI6ImY3NWVkODljMGZlZDQ0OWViZTczNzdiNmRlZTNhMDNlIn0.QxzGhpIpzYmJDaFGmdrMynWBkM7umtT5SnSSBKu46UY
```



 **测试外网**

```
wss://wstest.im199.com/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImFiY2QxIiwiaXNzIjoid2VjbG91ZF9pbSIsImFwcEtleSI6IkpLdE5IZnJWVXdzaGF4ek4iLCJleHAiOjE2MjgzMjMxNDMsImlhdCI6MTYyMzEzOTE0MywianRpIjoiNWU3NzU5ZjM2ODQ3NDFiMzg4MGEyYjkwMjQ0OWZjZmYifQ.CC-iuGjNwQLH4VxFI2wZEPuP4AGabOUOiRh9snp3IB4
```

_______



 **生产环境**

```
wss://ws.im199.com/ws?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJjbGllbnRJZCI6ImFiY2QxIiwiaXNzIjoid2VjbG91ZF9pbSIsImFwcEtleSI6IkpLdE5IZnJWVXdzaGF4ek4iLCJleHAiOjE2MjgzMjMxNDMsImlhdCI6MTYyMzEzOTE0MywianRpIjoiNWU3NzU5ZjM2ODQ3NDFiMzg4MGEyYjkwMjQ0OWZjZmYifQ.CC-iuGjNwQLH4VxFI2wZEPuP4AGabOUOiRh9snp3IB4
```



## 请求CMD指令说明

- 1: 发送IM消息请求
- 3: 单人WebRTC请求

## 响应CMD指令说明

- 1:响应请求(用于im聊天)

- 2:下发在线用户消息

- 3:下发在线消息事件类型

- 4:下发单人在线RTC事件通知

- 5: 会话中的事件

- 6:下发透传消息

  

## 消息type说明

- 文本消息	-1

- 图像消息	-2

- 音频消息	-3

- 视频消息	-4

- 位置消息	-5

- 文件消息	-6

## 事件类型type 

(xx表示为某客户端)

  - xx邀请xx加入会话 -1007
  - xx被xx移出会话 -1008
  - xx已接收某消息 -1009
  - xx已读某条消息 -1010
  - 你被xx拉入新会话 -1011
  - 主动退出会话 -1012
  - 成为新群主 -1013

以上基础平台固定类型均使用负数，所有正数留给自定义扩展类型使用，0 作为「没有类型」被保留起来。

## code错误码

- 401 用户token无效

## websocket客户端请求参数说明

**reqId** 为每次客户端通过websocket通道发送请求的唯一request Id, 用来标识每次请求,方便定位问题,以及用于绑定服务端响应对此请求request id的数据.

websocket像服务端发送数据不同于http请求接口.
 http发送请求 会一个请求对应一个响应,客户端接收也知道该响应数据是针对哪个请求的.

websocket是异步的 有可能你很快速的发送了几条消息,服务器响应的数据不一定是按你发送的消息顺序响应,  所以你每个请求要自己定一个id, 服务器就算乱序响应数据,客户端也能知道响应的数据对应哪个请求

**cmd** 为请求指令

**data** 为数据,所有需要发送的数据都在此参数下

**data内**的参数: **toConversation**(会话id)与**type**(消息类型)为固定的,其它参数名与参数值皆可由使用方自定义



## websocket服务器响应参数说明

**reqId** 一个请求唯一的id,服务端响应时,会将对应的id与数据一同返回

**cmd**  为响应指令

![image-20210521104503728](https://tva1.sinaimg.cn/large/008i3skNly1gqpurt4tgnj30py0dc3zd.jpg)

## 消息收发流程

![IM_client在线消息发送流程](https://tva1.sinaimg.cn/large/008i3skNgy1guv1rkeif7j60ix0hlgmc02.jpg)

![IM_client在线消息接收流程](https://tva1.sinaimg.cn/large/008i3skNgy1guv1rdrnq3j60fw0dg3yx02.jpg)

### 1. 客户端发送文本消息

**示例一: 必传参数:**

```json
{
"reqId":"123123123",
"cmd":1,
"data":{
  "toConversation":1402147846261706752,
  "type":-1,
  "text":"你好,这是一123个纯文本消息"
	}
}
```

**示例二: 全部可选参数:**

```json
{
"reqId":"123123123",
"cmd":1,
"data":{
  "push":{
    "title":"收到一条新消息",
    "subTitle":"点击查看"
   },
  "diyAbcd":"aaaa自已定义字段的值",
  "toConversation":1402147846261706752,
  "type":-1,
  "text":"你好,这是一123个纯文本消息",
  "attrs":{
    "a":"用户自定义的一些键值对",
    "b":"用户自定义的一些键值对"
   }
	}
}
```
**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| cmd                | String   | 否       | 指令码         |
| data                | Object   | 否       | 除toConversation与type为固定的参数,其它参数皆可由使用方自定义         |
| attrs              | Object   | 是       | 自定义拓展字段 |
| toConversation     | Long     | 否       | 发送到会话id     |
| diyParam自定义字段 | Object   | 是       | 自定义拓展字段 |
| push            | String   | 否       | 客户端自定义系统推送内容         |


### 2. 服务端响应给发送者

```json
{
  "cmd":1,
  "code":200,
  "msg":"成功",
  "data":{
    "msgId":"1394207796915998720"
  },
  "reqId":"123123123"
}
```

**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| cmd                | String   | 否       | 指令码         |
| msgId                | String   | 否       | 消息唯一ID         |


### 3. 接收方收到在线消息

```json
{
    "cmd":2,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":"1394207796915998720",
        "createTime":1621240016587,
        "withdrawTime":null,
        "sender":"hahah_30",
        "content":{
            "diyAbcd":"aaaa自已定义字段的值",
            "text":"你好,这是一123个纯文本消息",
            "type":-1,
            "attrs":{
                "a":"用户自定义的一些键值对",
                "b":"用户自定义的一些键值对"
            }
        },
        "withdraw":false,
        "event":false,
        "system":false,
        "at":null,
        "conversationId":"1394188055950266368"
    },
    "reqId":null
}
```

**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| cmd                | String   | 否       | 指令码         |
| data                | Object   | 否       | 除toConversation与type为固定的参数,其它参数皆可由使用方自定义         |
| attrs              | Object   | 是       | 自定义拓展字段 |
| toConversation     | Long     | 否       | 发送到会话id     |
| diyParam自定义字段 | Object   | 是       | 自定义拓展字段 |
| push            | String   | 否       | 客户端自定义系统推送内容         |
| sender            | String   | 否       | 发送方         |
| withdrawTime            | time   | 是       | 撤回时间戳         |
| createTime            | time   | 否       | 消息发送时间戳         |
| at            | String   | 否       | 提到了其他客户端,以英文逗号分开         |
| withdraw            | Boolean   | 是       | 是否撤回         |
| event            | Boolean   | 是       | 是否为事件类型         |
| withdraw            | Boolean   | 是       | 是否撤回         |
| system            | Boolean   | 是       | 是否为系统消息        |


### 4.接收方收到,需要回执

已接收回执需参考API对接文档

## 发送图片

```json
{
"reqId":"123123123",
"cmd":1,
"data":{
  "toConversation":1394188055950266368,
  "type":-2,
  "file": {
    "url":      "http://ac-p2bpmgci.clouddn.com/246b8acc-2e12-4a9d-a255-8d17a3059d25", // 必要参数
    "objId":    "54699d87e4b0a56c64f470a4", // 文件对应的AVFile.objectId
    "metaData": {
      "name":   "IMG_20141223.jpeg",   // 图像的名称
      "format": "png",                 // 图像的格式
      "height": 768,                   // 单位：像素
      "width":  1024,                  // 单位：像素
      "size":   18                     // 单位：b
    }
  },
  "attrs":{}
	}
}
```



上面是完整的例子，如果只想简单的发送图像 URL：

```json
{
"reqId":"123123123",
"cmd":1,
"data":{
  "toConversation":1394188055950266368,
  "type":-2,
  "file": {
    "url":      "http://ac-p2bpmgci.clouddn.com/246b8acc-2e12-4a9d-a255-8d17a3059d25", // 必要参数
    "objId":    "54699d87e4b0a56c64f470a4", // 文件对应的AVFile.objectId
    "metaData": {
      "name":   "IMG_20141223.jpeg",   // 图像的名称
      "format": "png",                 // 图像的格式
      "height": 768,                   // 单位：像素
      "width":  1024,                  // 单位：像素
      "size":   18                     // 单位：b
    }
	}
}
```

## 其他富媒体消息 

如视频 位置 音频 发送红包  好友验证等等

可参考:https://leancloud.cn/docs/realtime_v2.html#hash939050100

```json
{
"reqId":"123123123",
"cmd":1,
"data":{
  "toConversation":1394188055950266368,
  "type":由应用自定义,
  "应用自定义参数": 应用自定义值
	}
}
```



## 服务端下发透传消息

```json
{
"reqId":"1",
"cmd":6,
"data":{
  "toConversation":1394188055950266368,
  "createTime":1621240016587,
  "content":"json字符串,或任意字符数据"
	}
}
```



## 客户端在线接收事件类型消息

### 事件类型type (xx表示为某客户端)

- xx邀请xx加入会话 -1007
- xx被xx移出会话 -1008
- xx已接收某消息 -1009
- xx已读某条消息 -1010
- 你被xx拉入新会话 -1011
- xx主动退出会话 -1012
- xx成为新群主 -1013
- 群拓展字段变动事件  -1014
- 会话名称字段变动事件 -1015

### 下发 会话名称字段变动事件



该event事件消息类型不需要回执,该消息不会存入离线消息列表

```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "createTime":1629086007054,
 
        "content":{
            "type":-1015,
          	"name":"xxxxxxx"
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```



### 下发 会话拓展字段变动事件



该event事件消息类型不需要回执,该消息不会存入离线消息列表

```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "createTime":1629086007054,
 
        "content":{
            "type":-1014,
          	"attributes":"xxxxxxx"
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```



### 下发 xx成为新群主

 "cmd":5

该event事件消息类型需要已读和已接收回执,该消息会存入离线消息列表和消息历史记录列表




```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007054,
        "sender":"aaaaa1",
        "content":{
            "type":-1013
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名 | 字段类型 | 是否可空 | 说明              |
| ------ | -------- | -------- | ----------------- |
| sender | String   | 否       | 新群主的client ID |



### 下发 xx主动退出会话

 "cmd":5

该event事件消息类型需要已读和已接收回执,该消息会存入离线消息列表和消息历史记录列表




```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007054,
        "sender":"aaaaa1",
        "content":{
            "type":-1012
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名 | 字段类型 | 是否可空 | 说明            |
| ------ | -------- | -------- | --------------- |
| sender | String   | 否       | 退出的client ID |



### 下发 xx被xx移出会话

type为-1008

"cmd":5

该event事件消息类型需要已读和已接收回执,该消息会存入离线消息列表和消息历史记录列表




```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007054,
        "sender":"aaaaa1",
        "content":{
            "operator":"aaaaa1",
          	"passivityOperator":"aaaaa2",
            "type":-1008
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名            | 字段类型 | 是否可空 | 说明              |
| ----------------- | -------- | -------- | ----------------- |
| operator          | String   | 否       | 操作的client ID   |
| passivityOperator | String   | 否       | 被操作的client ID |



###   下发 xx邀请xx加入会话



type为-1007 

"cmd":5

该event事件消息类型需要已读和已接收回执,该消息会存入离线消息列表和消息历史记录列表




```json
{
    "cmd":5,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007054,
        "sender":"aaaaa1",
        "content":{
            "operator":"aaaaa1",
          	"passivityOperator":"aaaaa2",
            "type":"-1007"
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| operator        | String   | 否       | 操作的client ID   |
| passivityOperator | String | 否       | 被操作的client ID |






### 服务端在线下发 某消息*已接收*状态

receiverId:接收方客户端id 

conversationId:会话id

type为-1009 : 某消息已接收状态

msgId: 消息id;

该event事件消息类型不需要已读和已接收回执,不会保存进离线,仅下发给当前在线会话客户端. 


```json
{
    "cmd":3,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007054,
        "withdrawTime":null,
        "sender":"aaaaa1",
        "content":{
            "receiverId":"aaaaa1",
            "type":"-1009"
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| cmd                | String   | 否       | 指令码         |
| conversationId     | Long     | 否       | 会话id     |
| sender            | String   | 否       | 发送方         |
| createTime            | time   | 否       | 消息发送时间戳         |
| event            | Boolean   | 是       | 是否为事件类型         |


### 服务端在线下发 某消息*已读*状态

receiverId:接收方客户端id 

conversationId:会话id

type为-1010 : 某消息已读状态

msgId: 消息id;

该event事件消息类型不需要回执,不会保存进离线,仅下发给当前在线会话客户端. 


```json
{
    "cmd":3,
    "code":200,
    "msg":"成功",
    "data":{
        "msgId":1427109835333308416,
        "createTime":1629086007084,
        "sender":"aaaaa1",
        "content":{
            "receiverId":"aaaaa1",
            "type":"-1010"
        },
        "event":true,
        "conversationId":1427109730563788800
    },
    "reqId":null
}
```

**参数描述**

| 字段名             | 字段类型 | 是否可空 | 说明           |
| ------------------ | -------- | -------- | -------------- |
| cmd                | String   | 否       | 指令码         |
| conversationId     | Long     | 否       | 会话id     |
| sender            | String   | 否       | 发送方         |
| createTime            | time   | 否       | 消息发送时间戳         |
| event            | Boolean   | 是       | 是否为事件类型         |
