1. json形式的对象内容替换,扩展性和兼容性都很高
例子:
成员的各种资料设置共用一个实体,只需要不断从json请求中merge对应key的value即可。
设置指定群成员的管理员身份
{
"GroupId": "@TGS#2CLUZEAEJ", // 要操作的群组(必填)
"Member_Account": "bob", // 要操作的群成员(必填)
"Role": "Admin" // 设置管理员
}
设置成员消息屏蔽位
{
"GroupId": "@TGS#2CLUZEAEJ", // 要操作的群组(必填)
"Member_Account": "bob", // 要操作的群成员(必填)
"MsgFlag": "AcceptAndNotify" // AcceptAndNotify、Discard或者AcceptNotNotify,消息屏蔽类型
}
设置成员的群名片
{
"GroupId": "@TGS#2CLUZEAEJ", // 要操作的群组(必填)
"Member_Account": "bob", // 要操作的群成员(必填)
"NameCard": "鲍勃" // 群名片(选填)
}
设置成员自定义字段
{
"GroupId": "@TGS#2CLUZEAEJ", // 要操作的群组(必填)
"Member_Account": "bob", // 要操作的群成员(必填)
"AppMemberDefinedData": [ // 要操作的成员自定义字段(选填)
{
"Key":"MemberDefined1", // 要操作的群成员自定义字段Key
"Value":"ModifyData1" // 要设置的数据内容
},
{
"Key":"MemberDefined3",
"Value":"ModifyData3"
}
]
}
2. 对象层次化设计很到位,业务逻辑逐层展开,错落有致
顶层内容
批量拉取群消息已读回执信息,包含已读数和未读数。
{
"GroupId":"@TGS#2TTV7VSII",
"MsgSeqList":[ // 拉取消息的 seq 列表
{"MsgSeq":1},
{"MsgSeq":2}
]
}
字段 类型 属性 说明
GroupId String 必填 要拉取已读回执详情的群组 ID
MsgSeqList Array 必填 拉取消息的 seq 列表
MsgSeq Integer 必填 拉取消息的 seq
{
"ActionStatus": "OK",
"ErrorCode": 0,
"ErrorInfo": "",
"GroupMsgReceiptList": [ // 已读回执信息
{
"Code": 0,
"MsgSeq": 1,
"ReadNum": 1, // 群消息已读数
"UnreadNum": 6 // 群消息未读数
},
{
"Code": 0,
"MsgSeq": 2,
"ReadNum": 1,
"UnreadNum": 6
}
]
}
内容加细
分批拉取群消息已读或未读成员列表。
{
"GroupId":"@TGS#2TTV7VSII",
"MsgSeq": 1, // 拉取消息的 seq
"Filter": 1, // 0表示拉取已读成员列表,1表示拉取未读列表
"Cursor":"", // 上一次拉取到的成员位置,第一次填写""
"Count":5 // 最多拉取多少个成员
}
{
"ActionStatus": "OK",
"Cursor": "144115213529088617", // 下一次请求应该传的 Cursor 值
"ErrorCode": 0,
"ErrorInfo": "",
"IsFinish": 0, // 未拉取完毕,需要续拉
"MsgSeq": 1,
"UnreadList": [ // 未读成员列表
{
"Unread_Account": "test"
},
{
"Unread_Account": "test6"
},
{
"Unread_Account": "test3"
},
{
"Unread_Account": "test5"
},
{
"Unread_Account": "test4"
}
]
}
3. 分页设计
带有范围的分页
例子:
管理员按照时间范围,以会话其中一方的角度查询单聊会话的消息记录。
例如,用户 user1 和 user2 聊天,现在需要以 user2 的角度查询该会话在2020-03-20 10:00:00 - 2020-03-20 11:00:00内的聊天记录。
{
"Operator_Account":"user2",
"Peer_Account":"user1",
"MaxCnt":100,
"MinTime":1584669600,
"MaxTime":1584673200,
"LastMsgKey":"" // 这里可以不写,为了展示完全协议结构,写上了
}
请求包字段说明
字段 类型 属性 说明
Operator_Account String 必填 会话其中一方的 UserID,以该 UserID 的角度去查询消息。同一个会话,分别以会话双方的角度去查询消息,结果可能会不一样,请参考本接口的接口说明
Peer_Account String 必填 会话的另一方 UserID
MaxCnt Integer 必填 请求的消息条数
MinTime Integer 必填 请求的消息时间范围的最小值(单位:秒)
MaxTime Integer 必填 请求的消息时间范围的最大值(单位:秒)
LastMsgKey String 选填 上一次拉取到的最后一条消息的 MsgKey,续拉时需要填该字段
应答示例
{
"ActionStatus": "OK",
"ErrorInfo": "",
"ErrorCode": 0,
"Complete": 0, // 没有结束,后面还有内容
"MsgCnt": 12, //本次拉取返回了12条消息
"LastMsgTime": 1584669680,
"LastMsgKey": "549396494_2578554_1584669680",
"MsgList": [
{
"From_Account": "user1",
"To_Account": "user2",
"MsgSeq": 549396494,
"MsgRandom": 2578554,
"MsgTimeStamp": 1584669680,
"MsgFlagBits": 0,
"IsPeerRead": 0,
"MsgKey": "549396494_2578554_1584669680",
"MsgBody": [
{
"MsgType": "TIMTextElem",
"MsgContent": {
"Text": "msg 1"
}
}
],
"CloudCustomData": "your cloud custom data"
}
{ ... } //为节省篇幅,余下的10条消息未列出
]
}
应答中的"Complete": 0表示该时间范围的消息未全部拉取,需要续拉。
续拉请求中的 MaxTime 应改成应答的 LastMsgTime 字段值,同时填上应答的 LastMsgKey 字段,如下所示:
{
"Operator_Account":"user2",
"Peer_Account":"user1",
"MaxCnt":100,
"MinTime":1584669600,
"MaxTime":1584669680,
"LastMsgKey": "549396494_2578554_1584669680"
}
应答示例
{
"ActionStatus": "OK",
"ErrorInfo": "",
"ErrorCode": 0,
"Complete": 1, // 内容结束了,不用续拉消息
"MsgCnt": 5, // 本次拉取返回了5条消息
"LastMsgTime": 1584669601,
"LastMsgKey": "1456_23287_1584669601",
"MsgList": [
{
"From_Account": "user1",
"To_Account": "user2",
"MsgSeq": 1456,
"MsgRandom": 23287,
"MsgTimeStamp": 1584669601,
"MsgFlagBits": 0,
"IsPeerRead": 1,
"MsgKey": "1456_23287_1584669601",
"MsgBody": [
{
"MsgType": "TIMTextElem",
"MsgContent": {
"Text": "msg 13"
}
}
],
"CloudCustomData": "your cloud custom data"
},
{ ... } //为节省篇幅,余下的3条消息未列出
]
}
应答中的"Complete": 1表示该时间范围的消息已全部拉取。
若续拉的应答里 Complete 值为0,表明还需要继续续拉,直到 Complete 值为1。
全量内容的分页
分页拉取所有黑名单。
{
"From_Account": "id",
"StartIndex": 0,
"MaxLimited": 30,
}
字段 类型 属性 说明
From_Account String 必填 需要拉取该 UserID 的黑名单
StartIndex Integer 必填 拉取的起始位置
MaxLimited Integer 必填 每页最多拉取的黑名单数
应答示例
{
"BlackListItem": [
{
"To_Account": "id1",
"AddBlackTimeStamp": 1430000001
},
{
"To_Account": "id2",
"AddBlackTimeStamp": 1430000002
}
],
"Complete": "0" // 这个可以有,也可以隐含表达在其他字段,比如下面的字段
"StartIndex": 100, // 下页拉取的起始位置,0表示已拉完
"ActionStatus": "OK",
"ErrorCode": 0,
"ErrorInfo": "",
"ErrorDisplay": ""
}
4. 不同维度的业务交叉
这里的不同成员加入不同分组的操作,符合业务交叉的表现形式,是api设计时候应该考虑的复杂形式。
例子:
批量添加分组,并将指定好友加入到新增分组中。
{
"From_Account":"id",
"GroupName":["group1","group2","group3"],
"To_Account":["id1","id2","id3"]
}
字段 类型 属性 说明
From_Account String 必填 需要为该 UserID 添加新分组
GroupName Array 必填 新增分组列表
To_Account Array 选填 需要加入新增分组的好友的 UserID 列表
5. 回调设计的类型与作用
从处理角度来看,回调可以分为以下两大类:
事件发生之前回调:回调的主要目的在于让 用户 可以干预该事件的处理逻辑,SDK会根据回调返回码确定后续处理流程(例如发送群消息之前回调)。
事件发生之后通知:回调的主要目的在于让 用户 实现必要的数据同步,SDK忽略回调返回码(例如群组成员退群之后通知)。
事件发生之前回调超时的处理策略
如果事件发生之前回调超时,默认策略是消息正常下发。
SDK也支持自助配置“事件发生之前回调失败的处理策略”,例如,假设“发送群消息之前回调”超时,后续处理策略是消息正常下发还是不下发。
网络回调的安全性考虑
即时通信 IM 支持三种回调类型:
HTTP 回调。
HTTPS 回调,App 后台的 WebServer 配置的是 CA 机构签发的证书或即时通信 IM 免费签发的证书。
HTTPS 双向认证回调,App 后台的 WebServer 配置的是 CA 机构签发的证书或即时通信 IM 免费签发的证书,且启用双向认证能力。
三种方案的安全性逐步递增:
HTTP 回调存在两个缺陷:一是明文传输的数据容易被窃听,二是第三方 App 无法判断回调请求是否真正来自于即时通信 IM。
对于 HTTPS 回调,如果不启用双向认证,可以解决数据的加密问题,但依然无法确保回调的请求来源是即时通信 IM。
只有 HTTPS 与双向认证结合,才能确保第三方回调的安全性。
6. api设计的领域语义——RESTful
GET、POST、PUT、DELETE
GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源
7. 大规模会议下消息命令的幂等性设计
question, // host permission
answer, // host permission + question exist[客户端做]
upvote // host permission + question exist[客户端做] + upvote exist[客户端做]
这里的upvote重复来,也只点赞一次。
之所以需要幂等性设计,主要是因为在大规模数据量中,是不可能存在上帝视角的全局checker的,比如在几千万个点赞中,找到某个人的点赞是否存在,
以此决定某个点赞可以产生与否,在一个filter之中是不会有这种检查的。
那么这种检查只能留给数据库或者客户端,留给数据库就是耗时比较高,留给客户端则是当客户端接收到点赞的时候,如果是重复的,就什么都不发生即可。
这个事情也告诉我们,大数据之中的全局map不存在,全局map只在客户端一次一次接收到所有消息的过程中,不断形成一个全局map,这个全局往往也不会绝对全局,
因为接收到的量总是不够多的,从这个层面上,没有绝对全局的这个事情还是有哲学意义的。
幂等-个体组装-个体的无绝对全局观-所有个体的最终一致。