技术文档

联系人、好友申请与个人群

好友关系、屏蔽关系、聊天可见性和群成员是四类独立状态。删除聊天不等于删除好友,群静音不等于屏蔽群成员,个人删除消息也不是全员撤回。本页给出这些状态的操作顺序、权限与恢复边界;全部业务请求使用聊天 Bearer 会话。

三种添加方式

POST /friend-requests 的 JSON 在下列三个字符串字段中恰选一个。不要同时发送空字符串的备用字段,因为服务端按字段是否为字符串计数。

字段 规范化与验证 额外条件
friendId 删除空格、短横线并大写;11位字母数字 查找唯一聊天好友ID;不是Account用户ID
phone 删除空格、短横线;大陆11位号码补+86;国际号码带+ 只查登录时取得、尚未过期的已验证手机号摘要
temporaryCode 删除空格、短横线并大写;12位十六进制 对方用POST /friend-codes生成,10分钟有效

临时码按账号保存HMAC摘要,生成新码替换旧码;生成每用户每10分钟最多10次。手机号与临时码查找各自每10分钟最多20次,好友ID路径没有在该层调用同一计数器。不能由此推导全部联系人请求都有统一限流。依据:chat-service/src/server.mjs:832-898、chat-service/src/database.mjs:371-400。

临时码不是“成功成为好友才消费”:服务端找到有效码,排除添加自己和双方屏蔽后就删除该码,然后执行申请状态判断。因此同向pending、已是好友、存在反向申请,也可能已经消费临时码。重试前先刷新申请和会话列表,不应反复提交同一码。依据:chat-service/src/database.mjs:500-509。

申请与接受的完整顺序

  1. 发送申请前展示目标添加方式,提交单一字段。
  2. 201 {status:"requested",peer,requestId}表示新建申请;当前还不是好友。接收者收到friend_request事件,刷新GET /friend-requests。
  3. 同向申请已存在返回200 {status:"pending",...},requestId可能为null,应从outgoing列表取得现有记录。
  4. 对方已有反向申请返回409 INCOMING_REQUEST,转到incoming列表接受;已是好友为409 ALREADY_FRIENDS,任一方屏蔽为403 CONTACT_BLOCKED。
  5. 接收者调用POST /friend-requests/{requestId}/accept。服务端在事务中验证接收者、删除申请、建立或恢复双方唯一会话,并返回conversationId。
  6. 双方按friend_accepted事件重读会话。取消或拒绝统一用DELETE申请,双方才有权限,响应status=deleted。

重新成为好友会复用原有双方会话ID,不重新建立另一份单聊;原来的个人历史可见边界也不会因此自动重置。依据:chat-service/src/database.mjs:476-564。

合成成功例:{"temporaryCode":"A1B2C3D4E5F6"}对应一次申请,随后由对方按返回requestId接受。合成失败例:同时提交{"friendId":"ABC123DEF45","phone":""}会得到400 INVALID_INPUT,不能把空phone当作未传。票据、好友码和用户ID示例仅用于解释数据形状,不应拿真实用户资料排错。

手机号、资料与竞争更新

手机号添加使用Account最近登录返回的已验证号码摘要,当前聊天服务将可查找期限设为24小时;它与个人资料中自行填写、按private/selected/friends/everyone显示的contactPhone是不同字段。隐藏资料联系电话并不等同于关闭上述已验证号码查找路径,修改资料电话也不更新Account身份号码。界面应按实际字段解释行为,不能从一个开关推断另一个功能已经关闭。依据:chat-service/src/database.mjs:279-287,324-375。

申请列表只是快照:在用户点击接受前,对方可能取消申请,或任一方完成屏蔽。服务端事务重新校验并决定结果,客户端不能仅凭卡片曾显示就假定成功。接受请求若响应丢失,重试可能404,因为原申请已经删除;此时重读conversations确认双方关系。正常情况下,同一对用户始终复用唯一会话,客户端应按conversationId合并,而不是给每次接受创建重复条目。

屏蔽与删除好友

PUT /blocks/{userId}是有方向的本人屏蔽;业务检查会合并任一方向屏蔽。成功后清掉双方未完成申请及对应单聊的全部实时位置记录,写入block_changed事件,但不将is_friend改为false,也不删除已有聊天正文。DELETE /blocks/{userId}只解除本人的屏蔽,不恢复已删除的申请或位置共享,也不能解除对方对自己的屏蔽。

若存在该单聊通话,屏蔽路由先删除媒体房间并结束通话;媒体处理失败返回503,不能把界面按钮点击当作已完成屏蔽。已有MLS范围会被阻塞。依据:chat-service/src/server.mjs:845-864、chat-service/src/database.mjs:672-725。

DELETE /friends/{conversationId}中的路径参数是会话ID。它将is_friend改false、清理双方申请与单聊实时位置,并通知双方;历史消息与会话记录继续存在。恢复联系需重新申请并接受。非好友不能继续普通发送、开始或接听单聊通话、开始或更新单聊实时位置。

历史读取需要单独判断:普通聊天媒体的所有者可直接下载;非所有者凭仍可见的消息授权,受个人历史边界和单条隐藏过滤。单聊媒体查询不重新检查is_friend或block,因此屏蔽、删好友不保证旧的可见媒体立即失效。群消息授权分支要求当前群成员,但媒体所有者分支仍独立存在。删除本人消息也不会物理删除自己上传的文件。依据:chat-service/src/database.mjs:239-254。

个人群权限与成员历史

动作 请求字段 权限与结果
创建群 name为1–40字;memberIds为1–49个不同好友用户id(UUID),不含自己;不是11位friendId 发起者成为群主;包含自己后最多50人
邀请成员 memberIds为1–49个不同好友用户id(UUID),不是11位friendId 仅群主;只把不在群中的对象当新增,总数最多50
改名 name为1–40字 仅群主;产生group_renamed事件
移除成员 路径目标用户ID 仅群主;不能移除自己,应走leave
退出群 无业务字段 当前成员;群主退出会自动转移归属
解散群 无业务字段 仅群主;先处理进行中的通话,再清理群相关数据

创建时所有成员从新群开始;后续加入者的visible_after_message_id及last_read_message_id都设为加入当时最新群消息ID,所以不能阅读入群前历史。群主退出时,服务端按joined_at、user_id排序选最早剩余成员为新群主;最后成员退出删除群核心记录。leave与disband是不同清理分支,不能将最后成员退出描述成执行了完整解散清理。依据:chat-service/src/database.mjs:764-777,950-1033。

移除成员、退出与解散会处理相应通话及位置共享;移除或退出使当前MLS范围blocked。群主的个人身份与MLS创建者设备是不同角色,群主转移并不自动转移MLS创建者权限。企业组织和企业频道另见工作台,不是此处的个人群功能。

隐藏、删除与恢复

操作 服务端变化 能否恢复旧历史
DELETE某条messages/{messageId} 给当前用户插入hidden_messages 没有公开单条取消隐藏接口;其他人仍可见
DELETE单聊 本人is_hidden=true,历史边界推进到当时最后消息 restore只取消隐藏,不倒退边界
DELETE个人群视图 本人隐藏、历史边界与已读位置都推进 restore不恢复边界前消息,也不等于重新入群
POST群mute 只修改本人is_muted 可再次设false;不改变成员关系、正文或已读

includeHidden=1用于获取自己仍有资格访问的隐藏会话或群。普通新消息会重新显示隐藏聊天,但旧历史边界仍有效。群静音更新没有在该方法中写入普通events,不能把它承诺为通过事件即时同步的跨设备设置。依据:chat-service/src/database.mjs:635-652,730-760,855-880,904-925。

已读与异常处理

单聊/read和群/read发送{lastMessageId},位置只前进;目标消息必须属于当前聊天,群还要求消息晚于该成员历史边界。0虽然通过整数格式检查,但并不对应实际消息,仍会INVALID_MESSAGE。单聊用peerReadMessageId表示对方已读,群用其他成员已读位置最小值allReadMessageId;不能通过未读数算出逐人阅读时间。

正常例:连续提交同一有效lastMessageId均可返回status=read,第二次不重复推进;失败例:把单聊消息ID提交给群read得到400 INVALID_MESSAGE。收到404时先刷新当前成员和申请状态,403先处理权限与屏蔽,409 CLIENT_ID_REUSED不得更改内容后继续沿用原发送ID。完整参数见API参考,错误与重试见限制与错误。本页依据源码阅读,未构成真实双端验收。

源码核对范围:2026-10-23 的当前实现。本轮未执行生产业务请求或原生设备验收。

Markdown 原文完整文档文本