联系人、好友申请与个人群
好友关系、屏蔽关系、聊天可见性和群成员是四类独立状态。删除聊天不等于删除好友,群静音不等于屏蔽群成员,个人删除消息也不是全员撤回。本页给出这些状态的操作顺序、权限与恢复边界;全部业务请求使用聊天 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。
申请与接受的完整顺序
- 发送申请前展示目标添加方式,提交单一字段。
201 {status:"requested",peer,requestId}表示新建申请;当前还不是好友。接收者收到friend_request事件,刷新GET /friend-requests。- 同向申请已存在返回
200 {status:"pending",...},requestId可能为null,应从outgoing列表取得现有记录。 - 对方已有反向申请返回409 INCOMING_REQUEST,转到incoming列表接受;已是好友为409 ALREADY_FRIENDS,任一方屏蔽为403 CONTACT_BLOCKED。
- 接收者调用
POST /friend-requests/{requestId}/accept。服务端在事务中验证接收者、删除申请、建立或恢复双方唯一会话,并返回conversationId。 - 双方按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 的当前实现。本轮未执行生产业务请求或原生设备验收。