protocol.md 32 KB

IM TCP 对接协议

默认监听地址以 server/config/config.ini [server] 为准(当前 0.0.0.0:6000)。传输为 裸 TCP,应用层自行组包,不是 HPSocket PACK 模型。

本文与 Qt 客户端界面一一对应:登录、注册、验证码、找回/修改密码、会话列表、好友列表、群聊、发消息、退出登录。

1. 数据包

每个消息 = 12 字节包头 + body。多包可粘在同一 TCP 流里,按 length 切包。

偏移 长度 字段 说明
0 2 magic 固定 0x494D(ASCII IM),配置里写十六进制字符串 0x494D
2 1 type 业务大类,见第 2 节
3 1 cmd 该 Type 下的命令号
4 4 seq 调用方自增序号,响应原样带回;服务端推送为 0
8 4 length 仅 body 字节数,不含包头
12 length body UTF-8 JSON,可为 0 字节
  • 字节序:大端(网络序)
  • length 上限:config.ini [protocol] max_packet,默认 1048576。聊天图片/文件走 COS 直传,不再占用 TCP 包体。
  • magic 不对,或 length > max_packet服务端立即断开
  • 未知 type / cmd:不回包、不断开(仅打日志)

1.1 组包伪代码

out = []
out += be16(0x494D)
out += be8(type)
out += be8(cmd)
out += be32(seq)
out += be32(len(body))
out += body   # UTF-8 bytes

1.2 收包

  1. 至少读满 12 字节
  2. 校验 magic == 0x494D
  3. 再读 length 字节 body
  4. seq 匹配请求;可并发多个未完成请求
  5. 推送包 seq=0,按 type+cmd 处理,不要当请求应答

2. 命令一览

线上为 1 字节 type + 1 字节 cmd(原 16 位 cmd 槽拆开)。Dispatcher 按 type 进业务 Handler,Handler 再解析 cmd

Type Handler Cmd
System 1 系统 Heartbeat(1),SetConfig(13)(应答带 limits),推送 Kicked(101)
Account 2 账户 Login(2) Logout(3) Register(5) SendCode(6) ResetPassword(7) ChangePassword(8) GetPrivacy(17) SetPrivacy(18) SetProfile(27) GetAvatar(28)
Friend 3 好友 FriendList(9) AddFriend(12) SearchUser(14) FriendRequestList(15) HandleFriendRequest(16) DeleteFriend(24) SetRemark(25) GetProfile(26),推送 FriendRequestNotify(102) FriendAcceptedNotify(103) PresenceNotify(104)
Message 4 消息 Chat(4) ConvList(10) MsgHistory(11) DeleteConv(22) SetUnread(23) PutFile(29) GetFile(30) DeleteMsg(36),推送 ChatNotify(100)。群消息带 group_id
Favorite 6 收藏夹 FavoriteAdd(31) FavoriteList(32) FavoriteGet(33) FavoriteUpdate(34) FavoriteDelete(35)
Group 7 群聊 GroupCreate(37) GroupInfo(38) GroupMembers(39) GroupUpdate(40) GroupInvite(41) GroupSearch(42) GroupJoin(43) GroupLeave(44) GroupDismiss(45) GroupKick(46) GroupSetAdmin(47) GroupTransfer(48) GroupRequestList(49) GroupHandleRequest(50) GroupList(51),推送 GroupRequestNotify(105) GroupChangeNotify(106)

与 Client 界面的对应关系:

cmd 名称 登录 Client 说明
1 Heartbeat 建议 30s 一次 心跳
2 Login 登录窗 账号(username)+ 密码登录
3 Logout 设置 → 退出登录 清会话,不断开 TCP
4 Chat 聊天输入框发送 文本 / 表情 / 文件 / 名片;群聊带 group_id
5 Register 注册账号 注册
6 SendCode 获取验证码 短信验证码
7 ResetPassword 忘记密码 用验证码重置
8 ChangePassword 设置 → 通用 → 修改密码 校验旧密码后改密
9 FriendList 好友页 好友列表
10 ConvList 消息页会话列表 会话摘要 + 未读
11 MsgHistory 点开会话 历史消息,并清未读
12 AddFriend 好友页 + 按账号发起申请,对方同意后才成为好友
14 SearchUser 添加好友对话框 按账号/手机号搜索
15 FriendRequestList 新的朋友 待处理申请
16 HandleFriendRequest 新的朋友 同意/拒绝 1同意 2拒绝
17 GetPrivacy 设置 → 隐私 读取隐私开关
18 SetPrivacy 设置 → 隐私 保存隐私开关
19 StickerPackList 聊天表情面板 表情包列表
20 StickerPack 聊天表情面板 某包下表情元数据
21 GetSticker 表情缓存 下载表情图片
22 DeleteConv 会话右键 → 删除 删除自己的会话摘要,并标记自己侧消息已删除
23 SetUnread 会话右键 → 已读/未读 unread=0 已读,unread=1 未读
24 DeleteFriend 好友右键 → 删除 双向解除好友,并标记自己侧消息已删除
25 SetRemark 好友右键 → 备注 只改自己这一侧的备注
26 GetProfile 好友右键 → 查看名片 按 uid 拉资料
27 SetProfile 设置 → 资料 改昵称、签名、头像
28 GetAvatar 头像缓存 uidgroup_id 下载头像 JPEG
29 PutFile 聊天 → 图片/文件 申请 COS 临时上传凭证,返回 file_id + token
30 GetFile 点击文件气泡 按 file_id 下载
31 FavoriteAdd 消息右键 → 收藏 / 新建笔记 收藏会话内容或自定义笔记
32 FavoriteList 左侧收藏夹 收藏列表
33 FavoriteGet 点开一条收藏 收藏详情
34 FavoriteUpdate 编辑笔记 只允许改文本/笔记
35 FavoriteDelete 收藏右键 → 删除 删除自己的收藏及 COS 副本
36 DeleteMsg 聊天记录右键 → 删除 / 多选删除 仅标记自己侧消息已删除
37 GroupCreate 好友页 + → 发起群聊 选好友建群
38 GroupInfo 群资料 群名、进群方式、人数、头像版本、自己的角色
39 GroupMembers 群资料成员网格 成员列表
40 GroupUpdate 群资料 改群名 / join_mode / 群头像
41 GroupInvite 群资料 → 邀请 拉好友进群
42 GroupSearch 添加好友对话框搜群号 按群号精确搜索
43 GroupJoin 群名片 / 搜群号 join_mode 直接进或提交申请
44 GroupLeave 群资料 → 退出 群主须先转让或解散
45 GroupDismiss 群资料 → 解散 仅群主
46 GroupKick 群成员右键 → 移除 群主可踢管理员/成员,管理员只能踢成员
47 GroupSetAdmin 群成员右键 仅群主设置/取消管理员
48 GroupTransfer 群资料 → 转让 仅群主
49 GroupRequestList 群资料 → 入群申请 待处理申请
50 GroupHandleRequest 入群申请 同意/拒绝 1同意 2拒绝
51 GroupList 好友页 → 群聊 我加入的群
100 ChatNotify 推送 聊天区收消息 对方/群成员在线时推送
101 Kicked 推送 被挤下线 同一账号在别处登录
102 FriendRequestNotify 推送 好友申请提示 收到加好友申请
103 FriendAcceptedNotify 推送 好友同意提示 对方同意申请
104 PresenceNotify 推送 会话/好友头像绿点 好友上线/离线
105 GroupRequestNotify 推送 入群申请提示 群主/管理员收到申请
106 GroupChangeNotify 推送 群资料/会话 event: join / leave / kick / dismiss / role / info / invite

未登录可发:Heartbeat / SetConfig / Login / Register / SendCode / ResetPassword。 连接建立后客户端应立刻发送 SetConfig,服务端按该连接语言返回 msg,并在应答中下发 [limits](账号/密码长度、文件头像上限等)。客户端不再在本地 config.ini 保留这些限制。其余请求若未登录,回 code=2

3. 统一响应

请求的应答 body 均为 JSON 对象:

字段 类型 说明
code number 0 成功,非 0 失败,见 3.1
msg string 文本说明
其它 按命令扩展

响应 typecmdseq 与请求相同(推送除外)。

3.1 错误码

code 含义
0 成功
1 参数/JSON 错误
2 未登录
3 账号或密码错误 / 手机号不匹配
4 用户不存在
5 账号已存在
6 手机号已注册
7 验证码错误或过期
8 密码长度不符合 6-20
9 不允许(系统账号、加自己等)
10 数据库不可用或 SQL 失败
11 账号格式不符合 4-16 位字母或数字
12 验证码发送过于频繁
13 账号已禁用

字段:登录用 uid;注册仍用 account 作为用户名(展示/加好友资料,不用于登录)。


4. 命令详情

4.1 Heartbeat(cmd=1)

请求 body:空,或任意 JSON(服务端不解析)。

{"code":0,"msg":"pong","ts":1788146838644}
字段 类型 说明
ts number 服务端毫秒时间戳

4.1b SetConfig(cmd=13)

Type=System。连接成功后立即发送,上报客户端语言并拉取服务端配置。

请求:

{"lang":"zh"}

langzhen(决定本连接回包 msg 语言)。

成功:

{
  "code":0,
  "msg":"成功",
  "lang":"zh",
  "limits":{
    "account_min":4,
    "account_max":16,
    "password_min":6,
    "password_max":20,
    "phone_len":11,
    "nickname_max":32,
    "signature_max":64,
    "content_max":4096,
    "preview_max":120,
    "history_default":50,
    "history_max":100,
    "avatar_max":81920,
    "file_max":524288,
    "group_max_members":200,
    "group_name_max":32,
    "group_max_admins":10,
    "code_len":6,
    "max_packet":1048576,
    "heartbeat_sec":30,
    "interval_sec":60
  },
    "cdn":{
      "domain":"https://cdn.example.com"
    },
    "update":{
      "version":"1.0.1",
      "url":"https://cdn.example.com/NIM-1.0.1.zip",
      "notes":"修复文件上传"
    }
}

limits 与服务端 config.ini[limits][protocol] max_packet[client] heartbeat_sec[sms] code_len / interval_sec 一致。客户端用其做输入长度和本地预校验;最终仍以服务端校验为准。

cdn.domain 只来自 [cdn] domain,不会按 COS 自动推导。未配置时下发空串,客户端不走 HTTP 下载。

update 来自服务端 [update]version 为最新客户端版本,url 为 ZIP 安装包地址,notes 为说明。客户端用本地 [app] version 比较,更高则提示更新。ZIP 须把安装目录内容放在根(NIM.exe 在压缩包根目录);客户端先下载 ZIP,再复制一份自身到临时目录并以 --apply-update 用自带 3rdparty/7z/x64/7za.exe 解压覆盖原目录(失败再回退系统 tar / Expand-Archive),然后拉起新程序并清理临时副本。url 为空时客户端忽略。

4.2 Login(cmd=2)

对应登录窗:账号(im_user.username)+ 密码。请求字段为 account(兼容 username),不再使用 uid 登录。

请求:

{"account":"alice","password":"******"}

成功:

{"code":0,"msg":"ok","uid":10002,"account":"alice","nickname":"Alice","phone":"13800138000","signature":"","avatar_ver":0}

同一账号在新连接登录成功时,旧连接会收到 Kicked 推送并被断开。系统账号(文件传输助手)不可登录。

4.3 Logout(cmd=3)

请求 body 可空。成功后该连接变为未登录,TCP 不断开。关连接无需发 Logout。

4.4 Register(cmd=5)

对应注册窗:昵称、账号、手机号、验证码、密码。须先对同一手机号发送 scene=register 的验证码。

请求:

{
  "nickname":"Alice",
  "account":"alice",
  "phone":"13800138000",
  "sms_code":"123456",
  "password":"******"
}

约束(与客户端提示一致,长度见 [limits]):

  • 账号:4-16 位字母或数字
  • 密码:6-20 位
  • 手机号:11 位,以 1 开头

成功:

{"code":0,"msg":"ok","uid":10002,"account":"alice","nickname":"Alice"}

注册成功后自动加好友 文件传输助手uid=1)。

4.5 SendCode(cmd=6)

对应「获取验证码」。

{"phone":"13800138000","scene":"register"}
字段 说明
scene register 注册;reset 找回密码。缺省为 register
reset 要求该手机号已注册

成功:

{"code":0,"msg":"ok","ttl":300,"sms_code":"123456"}

sms_code 仅在 config.ini [sms] echo_code=1 时返回,便于客户端联调。生产环境必须改为 0。默认 60 秒内不可对同一手机号+场景重复发送。

4.6 ResetPassword(cmd=7)

对应忘记密码:uid + 绑定手机号 + 验证码 + 新密码。验证码 scene 必须是 reset

{"uid":10002,"phone":"13800138000","sms_code":"123456","password":"newpass"}

4.7 ChangePassword(cmd=8)

对应设置里的修改密码,须已登录。

{"old_password":"oldpass","password":"newpass"}

4.8 FriendList(cmd=9)

对应左侧「好友」列表。请求 body 可空。

{
  "code":0,
  "msg":"ok",
  "pending":1,
  "list":[
    {"uid":1,"account":"filehelper","nickname":"文件传输助手","signature":"文件助手","online":0},
    {"uid":10003,"account":"bob","nickname":"Bob","signature":"","online":1}
  ]
}

online1 在线,0 离线。这是拉取瞬间的快照;好友上线/离线时服务端会再推 PresenceNotify(cmd=104),客户端应立刻更新会话列表与好友列表头像绿点。pending 为待处理的好友申请数。

4.9 SearchUser(cmd=14)

按账号精确搜索,11 位手机号则按手机号搜。受对方隐私设置约束:不允许被搜到时返回用户不存在。须已登录。

{"keyword":"alice"}

成功:

{
  "code":0,
  "uid":10003,
  "account":"alice",
  "nickname":"Alice",
  "signature":"",
  "relation":0,
  "allow_add":1
}

relation0 非好友,1 已发出申请,2 已是好友,3 对方已申请加我。

4.10 AddFriend(cmd=12)

按账号或 uid 发起好友申请,须对方同意后才成为好友。对方在线会收到 FriendRequestNotify 推送。

{"account":"alice","message":"我是同事,请通过"}

message 为选填验证信息,最长约 50 字。

成功时 relation=1 表示申请已发出。若对方刚好也申请了自己,则直接成为好友(relation=2)。

4.11 FriendRequestList(cmd=15) / HandleFriendRequest(cmd=16)

列出待处理申请;action=1 同意,action=2 拒绝。同意后双向写入好友关系,并向对方推送 FriendAcceptedNotify

{"id":12,"action":1}

4.12 GetPrivacy(cmd=17) / SetPrivacy(cmd=18)

设置是否允许通过账号、手机号被搜索,以及是否允许他人添加。缺省字段保持原值。

{"allow_search_account":1,"allow_search_phone":1,"allow_add_friend":1}

4.13 StickerPackList(cmd=19) / StickerPack(cmd=20) / GetSticker(cmd=21)

Type=Sticker(5)。表情包由服务端下发,客户端按 sha256 缓存。发消息时 msg_type=2content{"pack_id":1,"code":"smile"}

GetSticker 成功:

{"id":1,"pack_id":1,"code":"smile","name":"微笑","sha256":"...","mime":"image/png","data":"<base64>"}

4.10 ConvList(cmd=10)

对应左侧「消息」会话列表。

{
  "code":0,
  "msg":"ok",
  "list":[
    {
      "conv_type":0,
      "peer_uid":10003,
      "account":"bob",
      "nickname":"Bob",
      "signature":"",
      "last_msg_id":88,
      "preview":"hello",
      "ts":1788146838644,
      "unread":2,
      "online":1,
      "avatar_ver":1
    },
    {
      "conv_type":1,
      "group_id":5,
      "name":"周末局",
      "member_count":6,
      "last_msg_id":12,
      "preview":"hello",
      "ts":1788146838644,
      "unread":1,
      "avatar_ver":1
    }
  ]
}

conv_type0 私聊(peer_uid),1 群(group_id + name)。缺省按私聊处理。unread 对应会话头像角标。私聊 online1 在线,0 离线。实时变化见 PresenceNotify。群会话无 online。群会话的 avatar_ver 为群头像版本,0 表示未设置。

4.11 MsgHistory(cmd=11)

点开会话时拉取历史,并将会话未读清零。

{"peer_uid":10003,"last_id":0,"limit":50}

群会话用 group_id 替代 peer_uid

{"group_id":5,"last_id":0,"limit":50}
字段 说明
last_id 0 表示最新一页;大于 0 时拉比该 id 更早的消息
limit 默认与上限见 [limits] history_default / history_max(50 / 100)
{
  "code":0,
  "msg":"ok",
  "list":[
    {"msg_id":87,"from_uid":10003,"to_uid":10002,"msg_type":1,"content":"hi","ts":1788146800000},
    {"msg_id":88,"from_uid":10002,"to_uid":10003,"msg_type":1,"content":"hello","ts":1788146838644}
  ]
}

列表按时间正序(旧 → 新)。from_uid 等于当前用户即自己发的气泡。

4.12 Chat(cmd=4)

对应输入框发送。msg_type=1 文本;msg_type=2 表情,content{"pack_id":1,"code":"smile"}msg_type=3 文件/图片,content{"file_id":12,"name":"a.pdf","size":1024,"mime":"application/pdf"}(须先 PutFile,再用 token 直传 COS,对象落盘后再发 Chat;服务端会 HEAD 校验);msg_type=4 名片,好友名片 content{"uid":10003,"account":"bob","nickname":"Bob"},群名片为 {"kind":"group","group_id":5,"name":"周末局","member_count":6}(缺省 kind 视为好友名片);msg_type=5 群系统通知(服务端写入,客户端只展示)。文本最长、文件体积上限见服务端 [limits] content_max / file_max(连接后经 SetConfig 下发给客户端)。

私聊:

{"to_uid":10003,"msg_type":1,"content":"hello"}

群聊(须为成员;有 group_id 时忽略 to_uid):

{"group_id":5,"msg_type":1,"content":"hello"}

成功:

{"code":0,"msg":"ok","msg_id":88,"ts":1788146838644}

对方在线时,服务端另推 ChatNotify(cmd=100,seq=0):

{
  "msg_id":88,
  "from_uid":10002,
  "from_account":"alice",
  "from_nickname":"Alice",
  "to_uid":10003,
  "msg_type":1,
  "content":"hello",
  "ts":1788146838644
}

双方会话预览会更新;接收方 unread+1。群消息 ChatNotify 另带 group_id,向群内其他在线成员 fan-out。

4.14 DeleteConv(cmd=22)

删除当前用户的会话列表项,并标记该用户侧的消息为已删除(对方聊天记录不受影响)。再次聊天会从空白开始。

{"peer_uid":10003}

群会话:{"group_id":5}。只清自己的会话摘要(群 inbox)并隐藏自己侧消息,不退群。

4.14b DeleteMsg(cmd=36)

删除当前用户聊天记录中的若干条消息(软删除,对方仍可见)。ids 最多 100 个,且须属于该 peer_uid 会话。

{"peer_uid":10003,"ids":[87,88]}

群会话用 group_id 替代 peer_uid

成功:peer_uidids、以及更新后的会话预览 last_msg_idpreviewts(无剩余消息时为空/0)。

4.15 SetUnread(cmd=23)

{"peer_uid":10003,"unread":0}

群会话:{"group_id":5,"unread":0}

unread=0 标记已读;unread>=1 标记未读。点开会话拉历史(MsgHistory)仍会清未读。

4.16 DeleteFriend(cmd=24)

{"uid":10003}

删除 im_friend 双向关系,并标记当前用户侧消息为已删除、清除自己的会话摘要。对方聊天记录不受影响。不是好友时 code=4

4.17 SetRemark(cmd=25)

{"uid":10003,"remark":"老王"}

remark 最长 32 字节,空字符串表示清除备注。会话列表展示优先用备注。

4.18 GetProfile(cmd=26)

{"uid":10003}

成功:

{"code":0,"uid":10003,"account":"bob","nickname":"Bob","signature":"","avatar_ver":1,"relation":2,"remark":"老王"}

4.20 SetProfile(cmd=27)

改自己的昵称、签名;可选带 JPEG 头像(base64)。头像须为 JPEG,体积不超过 avatar_max

{"nickname":"Alice","signature":"hello","avatar":"/9j/..."}

成功返回 nicknamesignatureavatar_ver

4.21 GetAvatar(cmd=28)

按用户或群下载 JPEG。group_id 优先于 uid

{"uid":10003}
{"group_id":5}

成功:uidgroup_idvermimeurl(COS 对象键;无 CDN 时另带 data JPEG base64)。未设置头像时 code=4

4.22 PutFile(cmd=29)

聊天图片/文件不再经 TCP 上传。PutFile 只申请对象键和限时凭证,客户端用返回的 token 直接 PUT 到腾讯云 COS,然后再用 Chat msg_type=3 发送该 file_id

请求(只传元数据,不要带 data):

{"name":"a.pdf","mime":"application/pdf","size":1024}
字段 类型 说明
name string 文件名,服务端会去掉路径并截断
mime string 可选,缺省 application/octet-stream
size number 必填,字节数,1 … file_max
sha256 string 可选,十六进制,最长 64
purpose string 可选。favorite 时对象键走 {prefix}/favorites/{uid}/{file_id},供收藏笔记插附件

成功:

{
  "code":0,
  "msg":"ok",
  "file_id":12,
  "name":"a.pdf",
  "size":1024,
  "mime":"application/pdf",
  "url":"im/files/12.pdf",
  "token":{
    "tmp_secret_id":"AKIDxxx",
    "tmp_secret_key":"xxx",
    "session_token":"xxx",
    "start_time":1788146838,
    "expired_time":1788148638,
    "bucket":"examplebucket-1250000000",
    "region":"ap-beijing",
    "key":"im/files/12.pdf",
    "host":"examplebucket-1250000000.cos.ap-beijing.myqcloud.com",
    "upload_url":"https://examplebucket-1250000000.cos.ap-beijing.myqcloud.com/im/files/12.pdf",
    "method":"PUT",
    "authorization":"q-sign-algorithm=sha1&q-ak=...&q-signature=...",
    "headers":{
      "Host":"examplebucket-1250000000.cos.ap-beijing.myqcloud.com",
      "Authorization":"q-sign-algorithm=sha1&...",
      "Content-Type":"application/pdf",
      "Content-Length":"1024",
      "x-cos-security-token":"xxx"
    }
  }
}

客户端上传(二选一,推荐 HTTP PUT):

  1. HTTP PUT(无需 COS SDK):PUT token.upload_url,请求体为原始文件字节,请求头必须与 token.headers 逐项一致(含 Content-LengthContent-Type;有 session_token 时必须带 x-cos-security-token)。不要改用 chunked,不要增删已签名头。HTTP 200/204 即成功。
  2. COS SDK:用 tmp_secret_id / tmp_secret_key / session_tokenregion+bucket,对象键 key,PutObject 上传。

url 仍是对象键,与 CDN 拼接方式不变:cdn.domain + "/" + url。凭证默认约 30 分钟有效。STS 申请失败时服务端会回退为永久密钥签发的限时 PUT 签名,此时 tmp_secret_* / session_token 可能为空,客户端仍可用 headers + upload_url 直传。

对象上传完成前不要发 Chat;服务端对 msg_type=3 会 HEAD COS,对象不存在则 code=4

GetFile 仍可用于无 CDN 时的回退下载;配置了 [cdn] domain 时客户端应走 HTTP 下载,不要再经 TCP 拉文件。

4.23 GetFile(cmd=30)

{"file_id":12}

成功:file_idnamesizemimedata(base64)。

4.24 FavoriteAdd(cmd=31,Type=6)

从会话收藏,或新建笔记。图片/文件会 Copy 到 COS {prefix}/favorites/{uid}/{file_id},与聊天文件独立。

会话收藏:

{"msg_type":1,"content":"一段话","src_peer_uid":10003,"src_msg_id":88}

msg_type:1 文本、3 图片/文件(content 为 Chat 同款 JSON,含 file_id)、4 名片。

新建笔记:

{"kind":5,"content":"{\"html\":\"<p>笔记</p>\",\"files\":[12]}"}

成功:idkindtitlekind:1文本 2图片 3文件 4名片 5笔记。

4.25 FavoriteList(cmd=32)

{"limit":200}

成功:list 数组,每项 idkindtitlets

4.26 FavoriteGet(cmd=33)

{"id":1}

成功:idkindtitlecontentsrc_msg_idsrc_peer_uidtsupdated_ts

4.27 FavoriteUpdate(cmd=34)

仅文本/笔记。笔记 content 格式同 FavoriteAdd。

{"id":1,"content":"{\"html\":\"<p>改过了</p>\",\"files\":[12]}"}

4.28 FavoriteDelete(cmd=35)

{"id":1}

只删当前用户这条收藏,以及其 favorites/ 下的 COS 副本。聊天 im/files/ 不动。

4.29 GroupCreate(cmd=37,Type=7)

{"name":"周末局","uids":[10003,10004]}

uids 为好友 uid,不含自己,至少 1 人。缺省 name 时服务端用成员昵称拼接。创建者为群主,被选好友直接入群。join_mode 默认 1(需审核)。

成功:group_idnamejoin_modemember_countrole

join_mode0 无需审核;1 群主或管理员审核;2 外人不可申请(群主/管理员仍可邀请好友)。

4.30 GroupInfo(cmd=38) / GroupMembers(cmd=39) / GroupList(cmd=51)

{"group_id":5}

GroupInfo 成功:group_idnameowner_uidjoin_modemember_countavatar_verrole(当前用户,非成员为 -1)、pending(群主/管理员可见的待审申请数)。

GroupMembers 须为成员。list 每项:uidaccountnicknameavatar_verrole(0 成员 / 1 管理员 / 2 群主)。

GroupList 请求可空。list 每项:group_idnamemember_countjoin_moderoleavatar_ver

4.31 GroupUpdate(cmd=40) / GroupInvite(cmd=41)

群主或管理员。Update 缺省字段保持原值;avatar 为 JPEG 的 base64(与 SetProfile 相同):

{"group_id":5,"name":"新名字","join_mode":0,"avatar":"/9j/..."}

成功返回与 GroupInfo 相同的群字段(含 avatar_ver)。GroupChangeNotify event=info 会带上最新 name / join_mode / avatar_ver

Invite:

{"group_id":5,"uids":[10005]}

被邀请人须是邀请者的好友。即使 join_mode=2 也可拉人。

4.32 GroupSearch(cmd=42) / GroupJoin(cmd=43)

按群号(group_id)精确搜索;Join 按 join_mode 直接入群或写入申请。

{"group_id":5,"message":"我是同事"}

Join 成功时 joined=1 表示已入群;joined=0pending=1 表示已提交申请。

4.33 GroupLeave(cmd=44) / GroupDismiss(cmd=45) / GroupKick(cmd=46)

{"group_id":5}

Kick 另带 uid。群主不能 Leave(须 Transfer 或 Dismiss)。管理员只能踢普通成员。

4.34 GroupSetAdmin(cmd=47) / GroupTransfer(cmd=48)

仅群主。admin=1 设为管理员,admin=0 取消。转让后原群主变为普通成员。

{"group_id":5,"uid":10003,"admin":1}
{"group_id":5,"uid":10003}

4.35 GroupRequestList(cmd=49) / GroupHandleRequest(cmd=50)

列出待处理入群申请。可带 group_id 只看某一群;不带则列出自己作为群主/管理员的全部待审。action=1 同意,action=2 拒绝。

{"id":8,"action":1}

4.36 GroupRequestNotify(cmd=105) / GroupChangeNotify(cmd=106,推送)

入群申请推给在线群主/管理员:

{"id":8,"group_id":5,"name":"周末局","from_uid":10006,"from_nickname":"Eve","message":""}

群变更推给在线成员(seq=0):

{"event":"kick","group_id":5,"name":"周末局","uid":10006,"nickname":"Eve"}

eventjoin / leave / kick / dismiss / role / info / invite

4.19 Kicked(cmd=101,推送)

{"code":0,"msg":"kicked"}

收到后应回到登录窗。随后 TCP 会被服务端断开。

4.19b PresenceNotify(cmd=104,Type=Friend,推送)

好友上线或离线时,向其在线好友推送(seq=0)。文件传输助手不参与。同一账号被踢后由新连接继续保持在线,不会误推离线。

{"uid":10003,"online":1}
字段 说明
uid 状态变化的用户
online 1 上线,0 离线

登录成功后推 online=1;主动 Logout、连接断开且该 uid 已无其它会话时推 online=0。客户端应同时刷新消息列表与好友列表头像绿点,不必为此再拉 FriendList。

5. 连接行为

  • 接入后即可发包,无握手包
  • 建议 30s 内发一次心跳,避免中间设备掐连接
  • 推送复用同一包格式:ChatNotify=100Kicked=101FriendRequestNotify=102FriendAcceptedNotify=103PresenceNotify=104GroupRequestNotify=105GroupChangeNotify=106seq=0
  • 关连接无需发 Logout;Logout 只清登录态
  • 密码在服务端以 SHA-256 十六进制存储,传输仍为明文 JSON,生产环境应叠 TLS 或另行加密

6. 配置与库表

客户端只关心 [server]host/port[protocol]magic/max_packetmagic 为十六进制字符串(如 0x494D)。改 magic 后客户端必须同步。

资源下载域名见 [cdn] domain,由 SetConfig 原样下发;未配置则为空。COS 对象键前缀见 [cos] prefix。聊天图片/文件由客户端持 PutFile 下发的临时 token 直传 COS,字节不经过 IM 服务端。

长度、条数、验证码间隔等见 [limits][sms]

[sms] echo_code:开发联调为 1 时,SendCode 响应带回 sms_code;上线改为 0

新库执行 server/sql/init.sql。已有 im_user / im_message 的库执行 server/sql/migrate.sql(已存在的列报错可忽略)。

用途
im_user 用户(含手机号、签名;status:1 正常 / 0 禁用 / 2 系统账号)
im_sms_code 验证码
im_friend 双向好友(含 remark 备注)
im_message 消息
im_conversation 会话摘要与未读
im_favorite 收藏(文本/图片/文件/名片/笔记)
im_group 群资料(join_mode:0 开放 / 1 审核 / 2 禁止)
im_group_member 群成员(role:0 成员 / 1 管理员 / 2 群主)
im_group_request 入群申请
im_group_message 群消息
im_group_msg_deleted 群消息自己侧隐藏
im_group_inbox 群会话摘要与未读

uid=1 为文件传输助手(username=filehelper,不可登录)。

7. 示例

7.1 Python 心跳 + 登录

import socket, struct, json

MAGIC = 0x494D
TYPE_SYSTEM, TYPE_ACCOUNT = 1, 2
CMD_HEARTBEAT, CMD_LOGIN = 1, 2

def pack(typ: int, cmd: int, seq: int, body: bytes = b"") -> bytes:
    return struct.pack(">HBBII", MAGIC, typ, cmd, seq, len(body)) + body

def recv_packet(sock: socket.socket):
    header = sock.recv(12)
    if len(header) < 12:
        raise ConnectionError("closed")
    magic, typ, cmd, seq, length = struct.unpack(">HBBII", header)
    if magic != MAGIC:
        raise ValueError("bad magic")
    body = b""
    while len(body) < length:
        chunk = sock.recv(length - len(body))
        if not chunk:
            raise ConnectionError("closed")
        body += chunk
    return typ, cmd, seq, body

s = socket.create_connection(("127.0.0.1", 6000))
s.sendall(pack(TYPE_SYSTEM, CMD_HEARTBEAT, 1))
print(recv_packet(s)[3])
s.sendall(pack(TYPE_ACCOUNT, CMD_LOGIN, 2, json.dumps(
    {"account": "alice", "password": "123456"}).encode()))
print(json.loads(recv_packet(s)[3]))
s.close()

7.2 十六进制

空 body 心跳(System/Heartbeat,seq=1):

49 4D 01 01 00 00 00 01 00 00 00 00