# 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秒

## 客户端连接流程

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

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

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

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

    ximport 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);}

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

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

### 连接WebSocket

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

示例:

    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:响应请求(用于RTC音视频通话)

## 消息type说明

- 文本消息 -1
- 图像消息 -2
- 音频消息 -3
- 视频消息 -4
- 位置消息 -5
- 文件消息 -6

## 事件类型type

(xx表示为某客户端)

- xx邀请xx加入会话 -1007
- xx被xx移出会话 -1008
- xx已接收某消息 -1009
- xx已读某条消息 -1010
- 你被xx拉入新会话 -1011

以上基础平台固定类型均使用负数，所有正数留给自定义扩展类型使用，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 为响应指令

## 消息收发流程

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

示例一: 必传参数:

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

示例二: 全部可选参数:

    x{"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. 服务端响应给发送者

    xxxxxxxxxx{  "cmd":2,  "code":200,  "msg":"成功",  "data":{    "msgId":"1394207796915998720"  },  "reqId":"123123123"}

参数描述

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

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

    xxxxxxxxxx{    "cmd":1,    "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对接文档

## 发送图片

    xxxxxxxxxx{"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：

    xxxxxxxxxx{"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

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

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

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

- xx邀请xx加入会话 -1007
- xx被xx移出会话 -1008
- xx已接收某消息 -1009
- xx已读某条消息 -1010
- 你被xx拉入新会话 -1011

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

receiverId:接收方客户端id

conversationId:会话id

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

msgId: 消息id;

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

    xxxxxxxxxx{    "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事件消息类型不需要回执,不会保存进离线,仅下发给当前在线会话客户端.

    xxxxxxxxxx{    "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 | 是 | 是否为事件类型